Electron調(diào)用外接攝像頭并拍照上傳實(shí)現(xiàn)詳解
目錄
- 背景
- 需求分析
- 實(shí)現(xiàn)
- 視頻采集
- MediaDevices.getUserMedia()
- 拍照生成圖片
- 上傳圖片至CDN
- 1. 使用HTMLCanvasElement.toBlob()
- 語(yǔ)法
- 參數(shù)
- 2. 使用HTMLCanvasElement.toDataURL()
- 語(yǔ)法
- 參數(shù)
- 返回值
- 總結(jié)
背景
基于Electron
實(shí)現(xiàn)的pc端智能驗(yàn)機(jī)應(yīng)用,近期迭代了一個(gè)新的功能,需求是通過(guò)電腦外接攝像頭對(duì)手機(jī)屏幕進(jìn)行拍照
,拍照后需將照片上傳
至服務(wù)端進(jìn)行屏幕信息比對(duì),確定被檢測(cè)屏幕是否為原廠屏。
需求分析
根據(jù)上面的需求,分析大概要以下幾個(gè)步驟。
- 先實(shí)現(xiàn)將攝像頭的畫(huà)面實(shí)時(shí)展示在頁(yè)面視頻采集區(qū)域中;
- 將攝像頭中的視頻畫(huà)面采集一幀成圖片并回顯;
- 將生成的圖片上傳至CDN拿到圖片鏈接;
- 將圖片鏈接上傳到后端接口 做處理;
確定了需要以上四個(gè)步驟后,接下來(lái)一步一步實(shí)現(xiàn)。
實(shí)現(xiàn)
視頻采集
由于 Electron
內(nèi)置了 Chromium
瀏覽器,該瀏覽器對(duì)各項(xiàng)前端標(biāo)準(zhǔn)都支持得非常好,所以基于 Electron 開(kāi)發(fā)應(yīng)用不會(huì)遇到瀏覽器兼容性問(wèn)題。幾乎可以在 Electron 中使用所有 HTML5
、CSS3
、ES6
標(biāo)準(zhǔn)中定義的 API
。
所以基于WebRTC
提供的API
即可獲取到攝像頭的視頻流。
MediaDevices.getUserMedia()
代碼如下:
methods: { getUserMedia() {/* 可同時(shí)開(kāi)啟video(攝像頭)和audio(麥克風(fēng)) 這里只請(qǐng)求攝像頭,所以只設(shè)置video為true */navigator.mediaDevices.getUserMedia({ video: true }) .then(function(stream) { /* 使用這個(gè) stream 傳遞到成功回調(diào)中 */ this.success(stream) }) .catch(function(err) { /* 處理 error 信息 */ this.error(error) }); }}
MediaDevices.getUserMedia()
會(huì)提示用戶(hù)給予使用媒體輸入的許可,媒體輸入會(huì)產(chǎn)生一個(gè)MediaStream
,里面包含了請(qǐng)求的媒體類(lèi)型的軌道。此流可以包含一個(gè)視頻軌道(來(lái)自硬件或者虛擬視頻源,比如相機(jī)、視頻采集設(shè)備和屏幕共享服務(wù)等等)、一個(gè)音頻軌道(同樣來(lái)自硬件或虛擬音頻源,比如麥克風(fēng)、A/D 轉(zhuǎn)換器等等),也可能是其它軌道類(lèi)型。
它返回一個(gè) Promise
對(duì)象,成功后會(huì)resolve
回調(diào)一個(gè) MediaStream
對(duì)象。若找不到滿(mǎn)足請(qǐng)求參數(shù)的媒體類(lèi)型,promise
會(huì)reject
回調(diào)一個(gè)NotFoundError
。
現(xiàn)在已經(jīng)成功獲取到視頻流,接下來(lái)就是將視頻流回顯到頁(yè)面。 這里使用video標(biāo)簽完成,代碼如下:
<template> <div><div> <video ref="video"></video></div> </div></template>export default { methods: { getUserMedia() { /* 可同時(shí)開(kāi)啟video(攝像頭)和audio(麥克風(fēng)) 這里只請(qǐng)求攝像頭,所以只設(shè)置video為true */ navigator.mediaDevices.getUserMedia({ video: true }).then(function(stream) { /* 使用這個(gè) stream 傳遞到成功回調(diào)中 */ this.success(stream)}).catch(function(err) { /* 處理 error 信息 */ this.error(error)});}, success(stream) { console.log("成功", stream); /* 將stream 分配給video標(biāo)簽 */ this.$refs.video.srcObject = stream; this.$refs.video.play();} }}
這時(shí),攝像頭中的畫(huà)面就可以顯示在頁(yè)面video標(biāo)簽內(nèi),如下圖。
為了用戶(hù)體驗(yàn),在進(jìn)入頁(yè)面之前添加了判斷攝像頭是否已經(jīng)接入并可用的邏輯,避免用戶(hù)的攝像頭未接入或者啟動(dòng),造成應(yīng)用不可用的錯(cuò)覺(jué)。
使用MediaDevices.enumerateDevices()
來(lái)獲取可用媒體輸入和輸出設(shè)備的列表,例如攝像頭、麥克風(fēng)、耳機(jī)等。
navigator.mediaDevices.enumerateDevices().then(devicesList => { console.log("------devicesList", deviceList)})
得到的設(shè)備列表數(shù)據(jù)格式如下:
kind
類(lèi)型有三種,分別是audioinput
、audiooutput
和videoinput
。分別代表音視頻的輸入和輸出。可在列表中查找目標(biāo)媒體是否已經(jīng)接入且可用。
若有選擇切換設(shè)備需求,可根據(jù)kind
類(lèi)型進(jìn)行媒體設(shè)備分類(lèi),選擇目標(biāo)deviceId
,傳入navigator.mediaDevices.getUserMedia
,完成來(lái)源切換。
navigator.mediaDevices.getUserMedia({ video: { deviceId: xxxx } })
拍照生成圖片
拍照其實(shí)就是截取視頻中的某一幀,這里使用canvas
來(lái)實(shí)現(xiàn)截取。getContext()
方法可返回一個(gè)對(duì)象,該對(duì)象提供了用于在畫(huà)布上繪圖的方法和屬性。其中drawImage()
方法用來(lái)向畫(huà)布上繪制圖像、畫(huà)布或視頻。
<template> <div><div> <video ref="video" v-if="showVideo"></video> <canvas ref="canvas" v-else width="500" height="346"></canvas><div> <div @click="capture">拍照</div> <div @click="submit"}">提交</div></div> </div></template>export default { data: { showVideo: true, // 是否展示攝像頭畫(huà)面 }, methods: {/* 拍照按鈕點(diǎn)擊 */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標(biāo)簽。
上傳圖片至CDN
上個(gè)步驟已經(jīng)完成了拍照,接下來(lái)就需要將圖片上傳至CDN,拿到圖片鏈接。 這里有兩種方式可以實(shí)現(xiàn)獲取圖片數(shù)據(jù)。
1. 使用HTMLCanvasElement.toBlob()
HTMLCanvasElement.toBlob()
方法生成 Blob
對(duì)象,用以展示 canvas 上的圖片。因?yàn)橹苯涌梢阅玫綀D片文件,所以無(wú)需再使用方法2中的函數(shù)來(lái)轉(zhuǎn)化base64
,直接可以獲取到圖片文件用來(lái)上傳。
語(yǔ)法
toBlob(callback, type, quality)
參數(shù)
callback
:回調(diào)函數(shù),參數(shù)為Blob
對(duì)象(目標(biāo)圖片文件)。
type
:圖片格式,默認(rèn)為image/png
可選
。
quality
:0-1的數(shù)字,表示圖片質(zhì)量,可選
。
點(diǎn)擊提交按鈕按鈕時(shí),先獲取圖片文件,為上傳做準(zhǔn)備。
methods: { /* 提交按鈕點(diǎn)擊 */ 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的結(jié)果如下圖:
2. 使用HTMLCanvasElement.toDataURL()
HTMLCanvasElement.toDataURL()方法返回一個(gè)包含圖片展示的Data URL。
Data URL
,即前綴為 data: 協(xié)議的 URL,其允許內(nèi)容創(chuàng)建者向文檔中嵌入小文件。
語(yǔ)法
canvas.toDataURL(type, encoderOptions);
參數(shù)
type
圖片格式,默認(rèn)為image/png
。
encoderOptions
0到1之間的值,用來(lái)選定圖片質(zhì)量,默認(rèn)值是0.92,超出范圍會(huì)使用默認(rèn)值。
返回值
base64
組成的圖片源數(shù)據(jù),上傳前需轉(zhuǎn)為圖片文件。這里封裝了一個(gè)convertBase64UrlToImgFile
函數(shù)用來(lái)轉(zhuǎn)換。代碼如下:
<template> <div><div> <video ref="video" v-if="showVideo"></video> <canvas ref="canvas" v-else width="500" height="346"></canvas><div> <div @click="capture">拍照</div> <div @click="submit">提交</div></div> </div></template>export default { data: { /* 是否展示攝像頭畫(huà)面 */ showVideo: true, }, methods: {/* 將base64轉(zhuǎn)為圖片文件 */convertBase64UrlToImgFile(urlData, fileType) { const imgData = urlData.split("base64,").splice(-1)[0] /* 解碼使用 base-64 編碼的字符串 轉(zhuǎn)換為byte */ const bytes = window.atob(imgData) /* 處理異常,將ASCII碼小于0的轉(zhuǎn)換為大于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) } /* 轉(zhuǎn)換成文件,可以添加文件的type,lastModifiedDate屬性 */ const blob = new Blob([ab], { type: fileType }) blob.lastModifiedDate = new Date() return blob},/* 提交按鈕點(diǎn)擊 */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
外的場(chǎng)景進(jìn)行base64
轉(zhuǎn)換圖片文件。和HTMLCanvasElement.toBlob()
方法得到的結(jié)果一致。
以上兩種方法都可以完成圖片上傳,最終拿到CDN圖片鏈接后可傳給后端進(jìn)行處理。獲取屏幕信息。
總結(jié)
通過(guò)以上四個(gè)步驟就完成了Electron應(yīng)用中通過(guò)外接攝像頭拍照并上傳的功能。這里基本用不到Electron的能力,和在web端的實(shí)現(xiàn)方式并無(wú)區(qū)別,Electron在這里起到的作用就是獲取攝像頭媒體流不需要獲取用戶(hù)權(quán)限。
Electron
是基于Chromium
和Node.js
實(shí)現(xiàn)的,這就使前端開(kāi)發(fā)者可以使用JavaScript
、HTML
和CSS
輕松構(gòu)建跨平臺(tái)的桌面應(yīng)用。Electron
可以使用幾乎所有的Web前端生態(tài)領(lǐng)域及Node.js
生態(tài)領(lǐng)域的組件和技術(shù)方案。
后續(xù)會(huì)介紹Electron在智能驗(yàn)機(jī)應(yīng)用中的實(shí)踐方案,更多關(guān)于Electron調(diào)用攝像頭拍照上傳的資料請(qǐng)關(guān)注其它相關(guān)文章!
