WebRTC音视频开发:React+Flutter+Go实战
上QQ阅读APP看书,第一时间看更新

4.5 截取视频

HTML5的<canvas>标签用于绘制图像(通过脚本,通常是JavaScript)。不过canvas元素本身并没有绘制能力(它仅仅是图形的容器)。你必须使用脚本来完成实际的绘图任务。getContext()方法可以返回一个对象,该对象提供了用于在画布上绘图的方法和属性。

可以把视频数据渲染至canvas上达到截屏的目的。具体步骤如下所示。

步骤1 打开h5-samples工程下的src目录,添加Canvas.jsx文件。定义约束条件,这里启用视频,禁用音频。首先需要打开摄像头,获取到视频数据。代码如下所示。


//约束条件
const constraints = window.constraints = {
    //禁用音频
    audio: false,
    //启用视频
    video: true
};

步骤2 根据约束条件获取媒体,调用getUserMedia()方法,即可请求摄像头。代码如下所示。


//根据约束条件获取媒体
const stream = await navigator.mediaDevices.getUserMedia(constraints);

步骤3 当成功获取到视频流后,将其传递给video对象的srcObject即可,这样视频流就会源源不断地向video对象输出并渲染出来。大致处理如下所示。


//成功返回视频流
handleSuccess = (stream) => {
    //获取video对象
    ...
    //将video对象的视频源指定为stream
    video.srcObject = stream;
}

步骤4 在页面渲染部分添加video标签,video是HTML5标准的一个重要组成部分,可以用来播放视频。代码如下所示。


<video className="small-video" playsInline autoPlay></video>

这里同样添加了autoPlay(视频自动播放)及playsInline(防止用户拖动滚动条)两个属性。值得注意的是,className指定成了small-video样式,即采用了小视频样式。

步骤5 在页面渲染部分再添加一个canvas标签,指定为small-canvas样式。代码如下所示。


<canvas className="small-canvas"></canvas>

步骤6 打开项目根目录下的styles/css/目录,添加canvas.scss文件用于添加此示例的特有样式。样式代码如下所示,主要是将宽高设置为320×240,宽高比为4:3。


//小视频样式
.small-video {
    background: #222;
    margin: 0 0 20px 0;
    width: 320px;
    height: 240px;
}
//小画布样式 
.small-canvas {
    background-color: #ccc;
    width: 320px;
    height: 240px;
}

步骤7 当获取到摄像头的视频数据后,video对象就可以播放视频。video对象可以作为canvas绘图的一个数据源,调用canvas的drawImage()方法即可绘制一张二维(2d)的位图。大致处理如下所示。


takeSnap = async (e) => {
    //获取画布对象
    ...
    //设置画面宽高
    ...
    //根据视频对象、xy坐标、画布宽、画布高绘图
    canvas.getContext('2d').drawImage(video, x, y, width, height);
}

步骤8 在src目录下的App.jsx及Samples.jsx文件里加上链接及路由绑定,参考第3章即可。完整的代码如下所示。


import React from "react";
import {Button} from "antd";
import '../styles/css/canvas.scss';

//视频
let video;
/**
 * 画布示例
 */
class Canvas extends React.Component {
    constructor() {
        super();
    }

    componentDidMount(){
        //获取video对象
        video = this.refs['video'];

        //约束条件
        const constraints = {
            //禁用音频
            audio: false,
            //启用视频
            video: true
        };
      //根据约束获取视频流

      navigator.mediaDevices.getUserMedia(constraints).then(this.handleSuccess).
    catch(this.handleError);
    }

    //获取视频成功
    handleSuccess = (stream) => {
        window.stream = stream;
        //将视频源指定为视频流
        video.srcObject = stream;
    }
  
    //截屏处理
    takeSnap = async (e) => {
        //获取画布对象
        let canvas = this.refs['canvas'];
        //设置画面宽度
        canvas.width = video.videoWidth;
        //设置画面高度
        canvas.height = video.videoHeight;
        //根据视频对象、xy坐标、画布宽、画布高绘图
        canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
    }

    //错误处理
    handleError(error) {
        console.log('navigator.MediaDevices.getUserMedia error: ', error.message, 
error.name);
    }

    render() {
    
        return (
        <div className="container">
            <h1>
                <span>截取视频示例</span>
            </h1>
            <div>
                    <video className="small-video" ref='video' playsInline 
autoPlay></video>
                    {/* 画布Canvas */}
                    <canvas className="small-canvas" ref='canvas'></canvas>
                </div>
                <Button className="button" onClick={this.takeSnap}>截屏</Button>
            </div>
        );
    }
}
//导出组件
export default Canvas;

上面的示例代码主要是将从摄像头获取到的视频数据转换成一张一张位图的过程。运行程序后,打开画布示例,点击“截屏”按钮,运行效果如图4-3所示。

图4-3 截取视频至canvas示例

图4-3中上下两幅图片不一样是正常的,上图为一个摄像头的动态的图像,下面的图片为某一时刻的截图,所以是不同的图片。