关于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&amp;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 ( constraints, successCallback, errorCallback );

参数
  1. constraints : 
    successCallback中传入的 LocalMediaStream对象所支持的媒体类型。
  2. successCallback
    当应用中传递LocalMediaStream对象时触发的函数。
  3. 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()这个方法,这个方法会把当前的设备以多个对象组成数组的形式全部返回,而我们对整个数组进行循环分类,从而得到其相关信息,其中

  1. MediaDeviceInfo.label 
    设备名称
  2. MediaDeviceInfo.deviceId 
    设备ID
  3. MediaDeviceInfo.kind 
    设备类型,只会有三种类型“videoinput”,"audioinput" or "audiooutput"
  4. 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&amp;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部分的内容以及移动端的具体事件这里就不一一展开了,有兴趣的同学也可以进行研究。

posted @ 2017-07-18 18:49  生姜可乐  阅读(4471)  评论(0编辑  收藏  举报