Electron 應用中實現調用外接攝像頭並拍照上傳
背景
基於Electron
實現的 pc 端智能驗機應用,近期迭代了一個新的功能,需求是通過電腦外接攝像頭
對手機屏幕進行拍照
,拍照後需將照片上傳
至服務端進行屏幕信息比對,確定被檢測屏幕是否爲原廠屏。
需求分析
根據上面的需求,分析大概要以下幾個步驟。
-
先實現將攝像頭的畫面實時展示在頁面視頻採集區域中;
-
將攝像頭中的視頻畫面採集一幀成圖片並回顯;
-
將生成的圖片上傳至 CDN 拿到圖片鏈接;
-
將圖片鏈接上傳到後端接口做處理;
確定了需要以上四個步驟後,接下來一步一步實現。
實現
視頻採集
由於
Electron
內置了Chromium
瀏覽器,該瀏覽器對各項前端標準都支持得非常好,所以基於 Electron 開發應用不會遇到瀏覽器兼容性問題。幾乎可以在 Electron 中使用所有HTML5
、CSS3
、ES6
標準中定義的API
。
所以基於WebRTC
提供的API
即可獲取到攝像頭的視頻流。
MediaDevices.getUserMedia()
代碼如下:
methods: {
getUserMedia() {
/* 可同時開啓video(攝像頭)和audio(麥克風) 這裏只請求攝像頭,所以只設置video爲true */
navigator.mediaDevices.getUserMedia({ video: true })
.then(function(stream) {
/* 使用這個 stream 傳遞到成功回調中 */
this.success(stream)
})
.catch(function(err) {
/* 處理 error 信息 */
this.error(error)
});
}
}
MediaDevices.getUserMedia()
會提示用戶給予使用媒體輸入的許可,媒體輸入會產生一個MediaStream
,裏面包含了請求的媒體類型的軌道。此流可以包含一個視頻軌道(來自硬件或者虛擬視頻源,比如相機、視頻採集設備和屏幕共享服務等等)、一個音頻軌道(同樣來自硬件或虛擬音頻源,比如麥克風、A/D 轉換器等等),也可能是其它軌道類型。
它返回一個 Promise
對象,成功後會resolve
回調一個 MediaStream
對象。若找不到滿足請求參數的媒體類型,promise
會reject
回調一個NotFoundError
。
現在已經成功獲取到視頻流,接下來就是將視頻流回顯到頁面。這裏使用 video 標籤完成,代碼如下:
<template>
<div class="video-page">
<div class="video-content">
<video ref="video" class="video-item"></video>
</div>
</div>
</template>
export default {
methods: {
getUserMedia() {
/* 可同時開啓video(攝像頭)和audio(麥克風) 這裏只請求攝像頭,所以只設置video爲true */
navigator.mediaDevices.getUserMedia({ video: true })
.then(function(stream) {
/* 使用這個 stream 傳遞到成功回調中 */
this.success(stream)
})
.catch(function(err) {
/* 處理 error 信息 */
this.error(error)
});
},
success(stream) {
console.log('成功', stream);
/* 將stream 分配給video標籤 */
this.$refs.video.srcObject = stream;
this.$refs.video.play();
}
}
}
這時,攝像頭中的畫面就可以顯示在頁面 video 標籤內,如下圖。
爲了用戶體驗,在進入頁面之前添加了判斷攝像頭是否已經接入並可用的邏輯,避免用戶的攝像頭未接入或者啓動,造成應用不可用的錯覺。
使用MediaDevices.enumerateDevices()
來獲取可用媒體輸入和輸出設備的列表,例如攝像頭、麥克風、耳機等。
navigator.mediaDevices.enumerateDevices().then(devicesList => {
console.log('------devicesList', deviceList)
})
得到的設備列表數據格式如下:
kind
類型有三種,分別是audioinput
、audiooutput
和videoinput
。分別代表音視頻的輸入和輸出。可在列表中查找目標媒體是否已經接入且可用。
若有選擇切換設備需求,可根據kind
類型進行媒體設備分類,選擇目標deviceId
,傳入navigator.mediaDevices.getUserMedia
,完成來源切換。
navigator.mediaDevices.getUserMedia({ video: { deviceId: xxxx } })
拍照生成圖片
拍照其實就是截取視頻中的某一幀,這裏使用canvas
來實現截取。getContext()
方法可返回一個對象,該對象提供了用於在畫布上繪圖的方法和屬性。其中drawImage()
方法用來向畫布上繪製圖像、畫布或視頻。
<template>
<div class="video-page">
<div class="video-content">
<video ref="video" class="video-item" v-if="showVideo"></video>
<canvas ref="canvas" v-else width="500" height="346"></canvas>
<div class="video-buttons">
<div @click="capture" class="button-item capture">拍照</div>
<div @click="submit" class="button-item submit"}">提交</div>
</div>
</div>
</template>
export default {
data: {
showVideo: true, // 是否展示攝像頭畫面
},
methods: {
/* 拍照按鈕點擊 */
capture() {
this.showVideo = false
var context = this.$refs.canvas.getContext('2d');
/* 要跟video的寬高一致 */
context.drawImage(this.$refs.video, 0, 0, 1000, 692, 0, 0, 500, 346);
}
}
}
拍照的圖片回顯至 canvas 標籤。
上傳圖片至 CDN
上個步驟已經完成了拍照,接下來就需要將圖片上傳至 CDN,拿到圖片鏈接。這裏有兩種方式可以實現獲取圖片數據。
1. 使用HTMLCanvasElement.toBlob()
HTMLCanvasElement.toBlob()
方法生成 Blob
對象,用以展示 canvas 上的圖片。因爲直接可以拿到圖片文件,所以無需再使用方法 2 中的函數來轉化base64
,直接可以獲取到圖片文件用來上傳。
語法
toBlob(callback, type, quality)
參數
callback
:回調函數,參數爲Blob
對象(目標圖片文件)。
type
:圖片格式,默認爲image/png
可選
。
quality
:0-1 的數字,表示圖片質量,可選
。
點擊提交按鈕按鈕時,先獲取圖片文件,爲上傳做準備。
methods: {
/* 提交按鈕點擊 */
submit() {
const base64Url = this.$refs.canvas.toBlob(blob => {
console.log('===blob', blob)
const data = new FormData()
data.append('file', blob)
request.post('https://XXXXX/upload', data)
}, "image/jpeg", 0.95)
}
}
console 的結果如下圖:
2. 使用HTMLCanvasElement.toDataURL()
HTMLCanvasElement.toDataURL() 方法返回一個包含圖片展示的 Data URL。
Data URL
,即前綴爲 data: 協議的 URL,其允許內容創建者向文檔中嵌入小文件。
語法
canvas.toDataURL(type, encoderOptions);
參數
type
圖片格式,默認爲image/png
。
encoderOptions
0 到 1 之間的值,用來選定圖片質量,默認值是 0.92,超出範圍會使用默認值。
返回值
base64
組成的圖片源數據,上傳前需轉爲圖片文件。這裏封裝了一個convertBase64UrlToImgFile
函數用來轉換。代碼如下:
<template>
<div class="video-page">
<div class="video-content">
<video ref="video" class="video-item" v-if="showVideo"></video>
<canvas ref="canvas" v-else width="500" height="346"></canvas>
<div class="video-buttons">
<div @click="capture" class="button-item capture">拍照</div>
<div @click="submit" class="button-item submit">提交</div>
</div>
</div>
</template>
export default {
data: {
/* 是否展示攝像頭畫面 */
showVideo: true,
},
methods: {
/* 將base64轉爲圖片文件 */
convertBase64UrlToImgFile(urlData, fileType) {
const imgData = urlData.split('base64,').splice(-1)[0]
/* 解碼使用 base-64 編碼的字符串 轉換爲byte */
const bytes = window.atob(imgData)
/* 處理異常,將ASCII碼小於0的轉換爲大於0 */
const ab = new ArrayBuffer(bytes.length)
const ia = new Int8Array(ab)
for (let i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i)
}
/* 轉換成文件,可以添加文件的type,lastModifiedDate屬性 */
const blob = new Blob([ab], { type: fileType })
blob.lastModifiedDate = new Date()
return blob
},
/* 提交按鈕點擊 */
async submit() {
const base64Url = this.$refs.canvas.toDataURL()
const imgFile = this.convertBase64UrlToImgFile(base64Url, 'image/jpg')
console.log('====imgFile', imgFile)
const data = new FormData()
data.append('file', imgFile)
/* 上傳 */
request.post('https://XXXXX/upload', data)
},
}
}
convertBase64UrlToImgFile
可用於在使用canvas
外的場景進行base64
轉換圖片文件。和HTMLCanvasElement.toBlob()
方法得到的結果一致。
以上兩種方法都可以完成圖片上傳,最終拿到 CDN 圖片鏈接後可傳給後端進行處理。獲取屏幕信息。
總結
通過以上四個步驟就完成了 Electron 應用中通過外接攝像頭拍照並上傳的功能。這裏基本用不到 Electron 的能力,和在 web 端的實現方式並無區別,Electron 在這裏起到的作用就是獲取攝像頭媒體流不需要獲取用戶權限。
Electron
是基於Chromium
和Node.js
實現的,這就使前端開發者可以使用JavaScript
、HTML
和CSS
輕鬆構建跨平臺的桌面應用。Electron
可以使用幾乎所有的 Web 前端生態領域及Node.js
生態領域的組件和技術方案。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Yb590oc6u94feS9s7FZWrQ