文章导读:继上节的内容,本节要完成的内容是完成视频采集参数以及滤镜参数的动态配置功能。推荐阅读方式:实操。
为了更好的讲解代码,我们还是把软件的截图贴出来,如下图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外,还设置了默认值,但“采集设备选择”这个选项比较特殊,需要通过webrtc代码读取到本机可用的摄像头设备列表,那什么时候读取摄像头设备列表呢?根据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对象就多了一个方法,该方法在哪里被调用?调用代码如下,即在cameraManager的openCamera方法中。
//开启摄像头 async openCamera(mediaStreamconstrains) { let media = await navigator.mediaDevices.getUserMedia(mediaStreamconstrains); this.enumerateVideoDevices();//调用列举可用视频设备列表 this.mediaStream = media; return media; },
到这里,视频采集参数的“初始化”工作做完了, 接下来开始读取这些参数,上述我们说到,参数的管理使用一个对象——configManager来管理,那视频参数的管理就非常简单了,只需要读取出对应表单元素的value值,并构建出一个webrtc能识别的constrains对象即可。代码如下。
//配置参数管理对象 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来实现的,并非webrtc API。CSS3中预设了多种基础的滤镜特效,开发者可以任意的叠加这些基础滤镜来实现“炫酷”的效果,滤镜的叠加的方式也很简答:filter:滤镜2(参数) 滤镜2(参数) ... 。 所以这个功能可以这么实现:首先、读取滤镜参数的配置;其次, 把滤镜参数拼成成CSS属性filter的格式并添加作用视频元素中即可。代码如下。
表单元素的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方法,用于作用是读取表单元素的值,并且拼接处css滤镜字符串。
//读取滤镜配置参数 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() } }, }
最后做下总结:本节新的知识点倒是没有,而是通过实现了更新采集配置和更新滤镜配置两个功能,为读者增加一些实战的经验,为下一节的的录制功能做准备。下一节,我们来实现本软件的最后一个功能:视频的录制和保存。
原文:https://www.cnblogs.com/rajan/p/12481838.html