2.5、实战案例(二)

文章导读:继上节的内容,本节要完成视频采集参数以及滤镜参数的动态配置。推荐阅读方式:实操。

  为了更好的讲解代码,这里还是把软件的界面展示出来,如下图2.5.1。

图 2.5.1 (软件界面截图)

  首先实现第一个功能:动态设置视频采集参数。

  不管是视频采集的参数还是滤镜的参数,我们可以看成是同一种业务,因此我们新建一个对象来管理:configManager,目前该对象有两个方法:读取视频采集参数getConstrains和读取滤镜参数getFilterConfig。先来实现第一个方法——getConstrains ,逻辑是:通过JS代码读取表单元素的值,并且返回一个参数对象。因此先为对应的表单元素加上id,方便操作,代码如下。

<div class="constraints-item">
    <label>是否启用音频</label>
    <input  id="useAudio" type="checkbox">
</div>
<div class="constraints-item">
    <label>视频宽</label>
    <input value="300" id="videoWidth" type="number">
</div>
<div class="constraints-item">
    <label>视频高</label>
    <input value="200"  id="videoHeight" type="number">
</div>
<div class="constraints-item">
    <label>采集设备选择</label>
     <select aria-placeholder="请选择采集的设备" name="" id="devicesList">
    </select>
</div>
<div class="constraints-item">
    <label>视频帧率</label><input value="30"  id="frameRate" type="number">
</div>

 

上述代码中,除了为表单元素设置id外,还设置了默认值,但“采集设备选择”这个表单控件(id="devicesList")比较特殊,需要通过webrtc API 读取到本机可用的摄像头列表才可赋值,那什么时候读取摄像头设备列表呢?根据2.1节 学习到的内容,首先得通过getUserMedia读取默认设备的媒体流,触发浏览器的对本域名程序访问摄像机、麦克风的权限询问,且通过了用户手动允许后,我们才能“顺畅”的访问其他webrtc API ,如enumerateDevices(列举本机可用的媒体设备列表)。按照这个思路,只有“打开摄像头”的逻辑会调用getUserMedia,所以,我们将在打开摄像头成功之后去列举并显示可用媒体设备列表,当然,这部分的逻辑依旧属于摄像机的管理操作,于是我们在cameraManager中新增一个方法——enumerateVideoDevices,其功能顾名思义就是列举出视频输入设备,其他的设备如音频输入、音频输出暂时不考虑。

  接下来分析下cameraManager的enumerateVideoDevices的逻辑:首先通过webrtc的相关API读取到可用的设备列表,注意:可用设备列表包含了 “音频输入(audioinpt)、音频输出(audiooutput)、视频输入(videoinput)”三种设备类型,而我们要筛选出视频输入(videoinput)设备出来即可;其次,筛选出来的视频输入设备后,我们要动态创建dom元素放到”<select aria-placeholder="请选择采集的设备" name="" id="devicesList">“标签中,供用户选择。代码如下。

// 加载摄像头供用户选择
    async enumerateVideoDevices() {
        let devices = await navigator.mediaDevices.enumerateDevices()
        let devicesListDom = domManager.getDom("devicesList");
        devicesListDom.innerHTML = ""; // 清空子元素
        if (devices) {
            for (let d of devices) {
                if (d && (d.kind == 'videoinput')) {
                    let element = document.createElement("option");
                    if (element) {
                        element.value = d.deviceId;
                        element.innerHTML = d.label;
                        devicesListDom.appendChild(element);
                    }
                }
            }
        }

    },

 

至此,cameraManager对象就多了enumerateVideoDevices方法,该方法在哪里被调用?调用代码如下,即在cameraManager的openCamera方法中。代码如下。

//开启摄像头 
    async openCamera(mediaStreamconstrains) {
        let media = await 
        navigator.mediaDevices.getUserMedia(mediaStreamconstrains);
        this.enumerateVideoDevices();//调用列举可用视频设备列表
        this.mediaStream = media;
        return media;
    },

 

到这里,视频采集参数的“初始化”工作完成了, 回到正题——动态设置视频采集参数。接下来就需要通过getConstrains方法来读取这些参数,代码如下。

//配置参数管理对象
const configManager = {

    //读取采集配置参数
    getConstrains() {
        /** 读取视频采集参数 start  */
        let constrains = {
            audio: false,
            video: {}
        }
        // 读取是否开启音频
        constrains.audio = domManager.getDom("useAudio").checked;
        constrains.video.width = domManager.getDom("videoWidth").value;
        constrains.video.height = domManager.getDom("videoHeight").value;
        constrains.video.frameRate = domManager.getDom("frameRate").value;
        constrains.video.deviceId = domManager.getDom("devicesList").value;
        /** 读取视频采集参数 end  */
        return constrains;

    },

}

上述代码中,需要注意的是“useAudio”这个表单元素是一个“选择按钮”,判断其是否被选中需要判断checked的值。 

  至此,视频采集参数管理方法(getConstrains)实现完成,接下来考虑何时调用,因为配置采集参数的功能是点击“配置参数”按钮触发的,所以我们需要在事件管理器——eventManager中新增一个监听,监听“更新配置“按钮的点击事件。 代码如下。

       // 更新配置事件监听
        domManager.getDom("updateConstrains").onclick = () => {

            // 读取视频采集配置信息
           let constrains = configManager.getConstrains()
            //先关闭摄像头
            cameraManager.closeCamera();
            // 再根据新的配置参数打开摄像头
            cameraManager.openCamera(constrains).then(media => {
                domManager.getDom("myvideo").srcObject = media;
                statusManager.openedCamera();
            }).catch(err => {
                console.log("读取媒体失败", error)
            })
        }        

  上述代码没有什么难点,唯一需要注意的地方就是更新采集配置时需要重新打开摄像头,所以上述代码的逻辑顺序:读取配置参数 ---> 关闭摄像头 --->  使用新参数重新打开摄像头。至此“更新配置”的功能搞定。

  第二个功能:更新滤镜。

  先分析下更新滤镜功能,滤镜功能并不是webrtc底层提供的,而是应用层实现的,说得通俗点就是:通过css来实现的。CSS3中预设了多种基础的滤镜特效,开发者可以任意的叠加这些基础滤镜来实现“炫酷”的效果,滤镜的叠加的方式也很简答:filter:滤镜2(参数)  滤镜2(参数) ... 。 所以这个功能可以这么实现:首先、读取滤镜参数;其次, 把滤镜参数拼成CSS的filter参数格式添加到视频元素中,所以同样在configManager对象中新增一个getFilterConfig方法来管理滤镜参数。代码如下。

表单元素的HTML代码:

<div class="constraints-item">
    <label>高斯模糊 blur(px)</label><input  id="blur" type="number">
</div>

<div class="constraints-item">
    <label>亮度 brightness(%)</label><input  id="brightness" type="number">
</div>

<div class="constraints-item">
    <label>对比度 contrast(%)</label><input  id="contrast" type="number">
</div>

<div class="constraints-item">
    <label>透明度 opacity(%)</label><input  id="opacity" type="number">
</div>
<div class="constraints-item">
    <label>深褐色 sepia(%)</label><input  id="sepia" type="number">
</div>

 

configManager的getFilterConfig方法,代码如下。

//读取滤镜配置参数
    getFilterConfig() {
        let blur = domManager.getDom("blur").value;
        let brightness = domManager.getDom("brightness").value;
        let contrast = domManager.getDom("contrast").value;
        let opacity = domManager.getDom("opacity").value;
        let sepia = domManager.getDom("sepia").value;
        let styleString = "";
        if (blur) {
            styleString += `blur(${blur}px) `
        }
        if (brightness) {
            styleString += `brightness(${brightness}%) `
        }
        if (contrast) {
            styleString += `contrast(${contrast}%) `
        }
        if (opacity) {
            styleString += `opacity(${opacity}%) `
        }
        if (sepia) {
            styleString += `sepia(${sepia}%) `
        }
        return styleString;
    }

创建滤镜样式的方法写完了,接下来就要考虑调用的问题,按照需求,我们只需在点击“更新滤镜”按钮时调用,所以在eventManager 的 eventInit新增一个事件监听,代码如下。

// 更新滤镜参数
domManager.getDom("updateFilter").onclick = () => {
    domManager.getDom("myvideo").style.filter = configManager.getFilterConfig()
}

  到这里,两个功能已经写完了,下面展示下完整的代码,如下。

HTML:

<fieldset>
    <legend>视频采集参数</legend>
    <div class="constraints-item">
        <label>是否启用音频</label>
        <input  id="useAudio" type="checkbox">
    </div>

    <div class="constraints-item">
        <label>视频宽</label>
        <input value="300" id="videoWidth" type="number">
    </div>

    <div class="constraints-item">
        <label>视频高</label>
        <input value="200"  id="videoHeight" type="number">
    </div>

    <div class="constraints-item">
        <label>采集设备选择</label>
         <select aria-placeholder="请选择采集的设备" name="" id="devicesList">
        </select>
    </div>

    <div class="constraints-item">
        <label>视频帧率</label><input value="30"  id="frameRate" type="number">
    </div>
    <div class="operator">
        <button disabled id="updateConstrains">更新配置</button>
        <button disabled id="startRecord">开始录制</button>
        <button disabled id="stopRecord">结束录制</button>
    </div>
</fieldset>
<fieldset>
    <legend>视频滤镜参数</legend>
    
    <div class="constraints-item">
        <label>高斯模糊 blur(px)</label><input  id="blur" type="number">
    </div>

    <div class="constraints-item">
        <label>亮度 brightness(%)</label><input  id="brightness" type="number">
    </div>

    <div class="constraints-item">
        <label>对比度 contrast(%)</label><input  id="contrast" type="number">
    </div>

    <div class="constraints-item">
        <label>透明度 opacity(%)</label><input  id="opacity" type="number">
    </div>
    <div class="constraints-item">
        <label>深褐色 sepia(%)</label><input  id="sepia" type="number">
    </div>
    <div class="operator">
        <button disabled id="updateFilter">更新滤镜</button>
    </div>
</fieldset>

 

配置管理对象——configManager:

const configManager = {

    //读取采集配置参数
    getConstrains() {
        /** 读取视频采集参数 start  */
        let constrains = {
            audio: false,
            video: {}
        }
        // 读取是否开启音频
        constrains.audio = domManager.getDom("useAudio").checked;
        constrains.video.width = domManager.getDom("videoWidth").value;
        constrains.video.height = domManager.getDom("videoHeight").value;
        constrains.video.frameRate = domManager.getDom("frameRate").value;
        constrains.video.deviceId = domManager.getDom("devicesList").value;
        /** 读取视频采集参数 end  */
        return constrains;

    },

    //读取滤镜配置参数
    getFilterConfig() {
        let blur = domManager.getDom("blur").value;
        let brightness = domManager.getDom("brightness").value;
        let contrast = domManager.getDom("contrast").value;
        let opacity = domManager.getDom("opacity").value;
        let sepia = domManager.getDom("sepia").value;
        let styleString = "";
        if (blur) {
            styleString += `blur(${blur}px) `
        }
        if (brightness) {
            styleString += `brightness(${brightness}%) `
        }
        if (contrast) {
            styleString += `contrast(${contrast}%) `
        }
        if (opacity) {
            styleString += `opacity(${opacity}%) `
        }
        if (sepia) {
            styleString += `sepia(${sepia}%) `
        }
        return styleString;
    }
}

摄像头管理对象cameraManager,新增“enumerateVideoDevices”方法,代码如下:

// 摄像头管理对象
const cameraManager = {

    mediaStream: null,

    // 摄像头列表
    cameraList: [],

    //开启摄像头 
    async openCamera(mediaStreamconstrains) {
        let media = await navigator.mediaDevices.getUserMedia(mediaStreamconstrains);
        this.enumerateVideoDevices();
        this.mediaStream = media;
        return media;
    },

    // 关闭摄像头
    closeCamera() {
        if (this.mediaStream) {
            let trackes = this.mediaStream.getTracks();
            if (trackes) {
                trackes.forEach(track => {
                    if (track) {
                        track.stop();
                    }
                });
            }

            this.mediaStream = null;
        }
    },

    // 加载摄像头供用户选择
    async enumerateVideoDevices() {
        let devices = await navigator.mediaDevices.enumerateDevices()
        let devicesListDom = domManager.getDom("devicesList");
        devicesListDom.innerHTML = ""; // 清空子元素
        if (devices) {
            for (let d of devices) {
                if (d && (d.kind == 'videoinput')) {
                    let element = document.createElement("option");
                    if (element) {
                        element.value = d.deviceId;
                        element.innerHTML = d.label;
                        devicesListDom.appendChild(element);
                    }
                }
            }
        }

    },

}

 

事件管理对象——eventManager,新增了两个监听:监听updateConstrains按钮、监听updateFilter按钮。代码如下:

// 事件方法管理对象
const eventManager = {
    // 初始化按钮事件的监听
    eventInit() {
        // 打开摄像头
        domManager.getDom("openCamera").onclick = () => {
            let constrains = configManager.getConstrains()
            cameraManager.openCamera(constrains).then(media => {
                domManager.getDom("myvideo").srcObject = media;
                statusManager.openedCamera();
            }).catch(err => {
                console.log("读取媒体失败", error)
            })
        }

        // 关闭摄像头
        domManager.getDom("closeCamera").onclick = () => {
            cameraManager.closeCamera()
            statusManager.systemReady();
        }

        // 更新配置
        domManager.getDom("updateConstrains").onclick = () => {

            let constrains = configManager.getConstrains()
            //先关闭摄像头
            cameraManager.closeCamera();
            // 再根据新的配置参数打开摄像头
            cameraManager.openCamera(constrains).then(media => {
                domManager.getDom("myvideo").srcObject = media;
                statusManager.openedCamera();
            }).catch(err => {
                console.log("读取媒体失败", error)
            })
        }

        // 更新滤镜参数
        domManager.getDom("updateFilter").onclick = () => {
            domManager.getDom("myvideo").style.filter = configManager.getFilterConfig()
        }

    },
}

   最后做下总结:本节没有新讲任何知识点,而是实现了更新采集配置和更新滤镜配置两个功能,为读者增加了实战的经验,为下一节的的录制功能做准备。下一节,我们来实现本软件的最后一个功能:视频的录制和保存。

 

posted on 2020-03-12 18:41  Rajan  阅读(314)  评论(0编辑  收藏  举报
扫码和作者预约吧