關於webRTC中video的使用實踐
此次demo使用chrome49調試測試
前端在操作視頻輸入,音頻輸入,輸出上一直是比較弱的,或者說很難進行相關的操作,經過我最近的一些研究發現,在PC上實際上是可以實現這一系列的功能的,其實現原理主要是得益於google的webRTC技術。
什麽是webRTC
WebRTC,名稱源自網頁即時通訊(英語:Web Real-Time Communication)的縮寫,是一個支持網頁瀏覽器進行實時語音對話或視頻對話的API。它於2011年6月1日開源並在Google、Mozilla、Opera支持下被納入萬維網聯盟的W3C推薦標準[1][2][3]。(來自維基百科)
也就是說webRTC是讓網頁瀏覽器進行實時語音對話或者視頻對話的一系列的解決方案。官方demo:https://github.com/webrtc/samples
這個demo裏面實際上功能非常多,關於調用攝像頭我們主要關心的是這個demo,可是當大家把代碼拉下來之後發現非常多東西,我在這裏給大家簡單的總結一下,並自己寫上幾個簡單的demo,當然想要關心具體實現功能,或者代碼的也可以看源碼。
如何在網頁中調用攝像頭
首先我們需要實現的就是在網頁中調用到攝像頭進行錄制,假如沒有攝像頭的同學也可以去下載一個繁星伴奏,進行模擬,下載地址:http://fanxing.kugou.com/ac/accompany
我們這裏需要用到navigator.getUserMedia這個API,當然在chrome下需要使用兼容前綴即navigator.webkitGetUserMedia
代碼如下:
<body> <video width="400" height="" id="video" autoplay=""> <source src="myvideo.mp4" type="video/mp4"></source> <source src="myvideo.ogv" type="video/ogg"></source> <source src="myvideo.webm" type="video/webm"></source> <object width="" height="" type="application/x-shockwave-flash" data="myvideo.swf"> <param name="movie" value="myvideo.swf" /> <param name="flashvars" value="autostart=true&file=myvideo.swf" /> </object> 當前瀏覽器不支持 video直接播放,點擊這裏下載視頻: <a href="myvideo.webm">下載視頻</a> </video> </body> <script type="text/javascript"> var constraints={video:true}; //設置參數LocalMediaStream var video_element=document.getElementById("video"); // if(navigator.getUserMedia){ //默認API navigator.getUserMedia(constraints) .then(function(stream) { console.info(stream); window.stream = stream; video_element.srcObject = stream; video_element.src=URL.createObjectURL(stream); return navigator.mediaDevices.enumerateDevices(); }) .catch(errorCallback); }else if(navigator.webkitGetUserMedia){ //chrome兼容 navigator.webkitGetUserMedia(constraints,function(stream){ //成功獲取後回調 console.info(stream); window.stream = stream; video_element.srcObject = stream; video_element.src=URL.createObjectURL(stream); return navigator.mediaDevices.enumerateDevices(); },function(data){ //失敗回調 console.info(data) }); } </script>
我們可以看下代碼,HTML部分很簡單 就是一個video標簽,主要功能的實現在js中可以找到navigator.getUserMedia這個API,通過這個就可以調用當前攝像頭,並且返回流信息。
關於navigator.getUserMedia
提示用戶需要權限去使用像攝像頭或麥克風之類的媒體設備,如果用戶提供了這個權限。
語法
navigator.getUserMedia ( constraints, successCallback, errorCallback );
參數
- constraints :
successCallback中傳入的 LocalMediaStream對象所支持的媒體類型。 - successCallback:
當應用中傳遞LocalMediaStream對象時觸發的函數。 - errorCallback:
當調用媒體設備失敗時觸發的函數.
其中第一個和第二是都是必須的.
-
第一個需要傳入想要調用哪種媒體類型,具體就像代碼中定義:
var constraints={video:true};
當然也可以寫多個類型,例如
var constraints={video:true, audio: true};
- 第二個參數則是調用成功後的回調函數,回調函數本身也有一個參數data返回的則是相關的音頻視頻信息。
通過這個對整個API的使用,把獲取到的流信息stream傳給video標簽的src中即可將視頻信息在網頁中顯示出來。
多個設備的處理
假設現在用戶有多個攝像設備,那我們需要讓用戶進行選擇,又應該如何做呢?
首先我們需要給用戶提供他自己的設備名稱讓他進行選擇,在這裏我們需要使用navigator.mediaDevices.enumerateDevices()這個API 因此我們可以這樣寫:
<html> <head> <meta charset="UTF-8"> <title></title> <script src="js/jquery.js" type="text/javascript" charset="utf-8"></script> </head> <body> <button id="showInfo">顯示設備信息</button> <div> <h2>視頻輸入設備</h2> <div id="videoinputInfoBox"> </div> </div> </body> <script type="text/javascript"> $(function(){ init(); }); function init(){ $("#showInfo").on("click",function(){ //獲取設備信息 navigator.mediaDevices.enumerateDevices().then(gotDevices); }); } //獲取設備信息後的處理 function gotDevices(data){ var videoinputhtml=""; for (var i = 0; i < data.length; i++) { console.info(data[i]); if(data[i].kind=="videoinput"){ videoinputhtml+="<span data-id=‘"+data[i].deviceId+"‘ data-info=‘"+data[i].label+"‘ class=‘infoBtn‘>"+data[i].label+"</span><br>"; } } $("#videoinputInfoBox").html(videoinputhtml); } </script> </html>
我們在頁面添加了一個按鈕showInfo,當點擊這個按鈕的時候調用了navigator.mediaDevices.enumerateDevices()這個方法,這個方法會把當前的設備以多個對象組成數組的形式全部返回,而我們對整個數組進行循環分類,從而得到其相關信息,其中
- MediaDeviceInfo.label
設備名稱 - MediaDeviceInfo.deviceId
設備ID - MediaDeviceInfo.kind
設備類型,只會有三種類型“videoinput”,"audioinput" or "audiooutput" -
MediaDeviceInfo.groupId
設備系列ID(自己翻譯的,理解就是同一個設備有不同的功能,比如麥克風和聽筒,則公用同一個groupId 官方的說法是Returns a DOMString that is a group identifier. Two devices have the same group identifier if they belong to the same physical device; for example a monitor with both a built-in camera and microphone.
如有錯誤請指正)
根據上面的這4個字段的解析我們就可以得到用戶關於音視頻設備的各種信息了。
那接著我們需要做最後一步就是讓用戶點擊某個視頻設備則調用相應的視頻設備進行相關的視頻流信息輸出
大概思路就是給每個設備名稱綁定點擊事件,然後獲取到當前設備的deviceId,通過前面提到的constraints參數將設備ID傳到navigator.getUserMedia方法中從而獲取相關的設備信息。
代碼如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script src="js/jquery.js" type="text/javascript" charset="utf-8"></script> </head> <body> <video width="400" height="" id="video" autoplay=""> <source src="myvideo.mp4" type="video/mp4"></source> <source src="myvideo.ogv" type="video/ogg"></source> <source src="myvideo.webm" type="video/webm"></source> <object width="" height="" type="application/x-shockwave-flash" data="myvideo.swf"> <param name="movie" value="myvideo.swf" /> <param name="flashvars" value="autostart=true&file=myvideo.swf" /> </object> 當前瀏覽器不支持 video直接播放,點擊這裏下載視頻: <a href="myvideo.webm">下載視頻</a> </video> <button id="showInfo">顯示設備信息</button> <div> <h2>視頻輸入設備</h2> <div id="videoinputInfoBox"> </div> </div> </body> <script type="text/javascript"> $(function() { init(); start(); }); function init() { $("#showInfo").on("click", function() { //獲取設備信息 navigator.mediaDevices.enumerateDevices().then(gotDevices); }); } //獲取設備信息後的處理 function gotDevices(data) { var videoinputhtml = ""; for (var i = 0; i < data.length; i++) { console.info(data[i]); if (data[i].kind == "videoinput") { videoinputhtml += "<span data-id=‘" + data[i].deviceId + "‘ data-info=‘" + data[i].label + "‘ class=‘infoBtn‘>" + data[i].label + "</span><br>"; } } $("#videoinputInfoBox").html(videoinputhtml); } //點擊選擇設備 function start() { $("#videoinputInfoBox").on("click", ".infoBtn", function() { //判斷當前是否有其他的流信息,如果有則停止 if (window.stream) { window.stream.getTracks().forEach(function(track) { track.stop(); }); } var $this = $(this); var id = $this.attr("data-id"); //將設備id傳入constraints var constraints = { video: { sourceId: id } }; var video_element = document.getElementById(‘video‘); errorCallback = function(error) { console.log("Video capture error: ", error.code); }; if (navigator.getUserMedia) { navigator.getUserMedia(constraints) .then(function(stream) { window.stream = stream; video_element.srcObject = stream; video_element.src = URL.createObjectURL(stream); return navigator.mediaDevices.enumerateDevices(); }) .then(gotDevices) .catch(errorCallback); } else if (navigator.webkitGetUserMedia) { //webkit特別處理,將標準中的constraints格式轉換成webkit所支持的 constraints.video = constraintsToChrome(constraints.video); navigator.webkitGetUserMedia(constraints, function(stream) { window.stream = stream; video_element.srcObject = stream; video_element.src = URL.createObjectURL(stream); return navigator.mediaDevices.enumerateDevices(); }, function(data) { console.info(data) }); } }) } // getUserMedia constraints shim. 官方源碼提供的方法,把傳入的sourceId轉換成navigator.webkitGetUserMedia鎖需要的格式 var constraintsToChrome = function(c) { if (typeof c !== ‘object‘ || c.mandatory || c.optional) { return c; } var cc = {}; Object.keys(c).forEach(function(key) { if (key === ‘require‘ || key === ‘advanced‘ || key === ‘mediaSource‘) { return; } var r = (typeof c[key] === ‘object‘) ? c[key] : { ideal: c[key] }; if (r.exact !== undefined && typeof r.exact === ‘number‘) { r.min = r.max = r.exact; } var oldname = function(prefix, name) { if (prefix) { return prefix + name.charAt(0).toUpperCase() + name.slice(1); } return (name === ‘deviceId‘) ? ‘sourceId‘ : name; }; if (r.ideal !== undefined) { cc.optional = cc.optional || []; var oc = {}; if (typeof r.ideal === ‘number‘) { oc[oldname(‘min‘, key)] = r.ideal; cc.optional.push(oc); oc = {}; oc[oldname(‘max‘, key)] = r.ideal; cc.optional.push(oc); } else { oc[oldname(‘‘, key)] = r.ideal; cc.optional.push(oc); } } if (r.exact !== undefined && typeof r.exact !== ‘number‘) { cc.mandatory = cc.mandatory || {}; cc.mandatory[oldname(‘‘, key)] = r.exact; } else { [‘min‘, ‘max‘].forEach(function(mix) { if (r[mix] !== undefined) { cc.mandatory = cc.mandatory || {}; cc.mandatory[oldname(mix, key)] = r[mix]; } }); } }); if (c.advanced) { cc.optional = (cc.optional || []).concat(c.advanced); } return cc; }; </script> </html>
我們將前面兩段代碼進行了結合,添加了一些方法,從而實現點擊切換視頻源
分析下上面的代碼,其實關鍵點都有備註:
一
if (window.stream) { window.stream.getTracks().forEach(function(track) { track.stop(); }); }
因為我們每次都會將流信息stream放到window下面,因此每次選擇新的視頻源的時候都需要將舊的清除
二
var constraints = { video: { sourceId: id } };
我們在設置constraints參數的時候不再是使用constraints={video:true}了,而是將當前選擇的這個設備id傳入
三
constraints.video = constraintsToChrome(constraints.video);
這一段主要是針對webkit內核瀏覽器的,也就是chrome。
constraintsToChrome這個方法是我在webRTC的demo源碼中找到的,應該是對傳入的constraints進行格式化處理的
處理之前
var constraints = { video: { sourceId: id } };處理之後:
var constraints = { video: { optional:[ {sourceId: id} ] } };這裏我們直接調用就好。
截取圖片
進行了上面的這些調整之後,我們就可以實現多個設備之間的選擇從而調用到相應的攝像頭。
那獲取的攝像數據後我們還可以在進行一些擴展,例如截圖
我們在上面的代碼中添加一個方法即可
function setPhoto(){ var video_element=document.getElementById(‘video‘); //拍照按鈕 var screenshotBtn=document.getElementById("screenshot"); //點擊拍照 screenshotBtn.onclick=function(){ var canvas=document.createElement(‘canvas‘); //動態創建畫布對象 var ctx=canvas.getContext(‘2d‘); var cw=$("#video").width(); var ch=$("#video").height(); canvas.width=cw; canvas.height=ch; ctx.fillStyle=‘#ffffff‘; ctx.fillRect(0,0,cw,ch); ctx.drawImage(video_element,0,0,cw,ch); //將video對象內指定的區域捕捉繪制到畫布上指定的區域,可進行不等大不等位的繪制。 console.info(canvas); //渲染canvas $("body").append(canvas); //canvas轉換成base64位的數據的圖片 var imageData=canvas.toDataURL(); //渲染圖片 $("body").append(‘<img src="‘+imageData+‘" id="canvasImg">‘); } }
上面的方法基於canvas即可實現獲取到屏幕中video部分的像素點生成一個canvas畫布以及一張相關的圖片。
關於webRTC的相關內容還有很多,這裏只是簡單的說明了PC端video部分的使用,另外還有audio部分的內容以及移動端的具體事件這裏就不一一展開了,有興趣的同學也可以進行研究。
關於webRTC中video的使用實踐