zoukankan      html  css  js  c++  java
  • 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()
            }
    
        },
    }

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

  • 相关阅读:
    Javaweb初试——选课系统
    Java四则运算第二次课堂完整版
    Java动手动脑03
    阅读笔记
    Java四则运算课堂测试三
    读书笔记
    Java日报10.14
    Java日报10.13
    Java动手动脑04
    2020.9.22测试
  • 原文地址:https://www.cnblogs.com/rajan/p/12481838.html
Copyright © 2011-2022 走看看