# 使用RecordRTC对video视频进行录制

实现对监控画面进行抓拍以及录像功能时。由后端使用ffmpeg对直播流进行截取,但是由于直播流有一定的缓冲,前端看到的画面对于服务器来说已经是过时的画面,这就导致截取的画面不是自己想要的画面。为了解决这个问题,现考虑从前端页面进行画面的截取。

使用canvas容易实现对video标签的单帧截取。

RecordRTC可以记录一段时间内canvas的变化。

视频截取的思路:在单位时间内将video画面实时绘制到canvas中,然后利用RecordRTC记录canvas。

# 参考

view RecordRTC in Github (opens new window)

CSDN (opens new window)

RecordRTC总结 (opens new window)

RecordRTC API (opens new window)

#vue项目中使用RecordRTC

安装

npm install --save-dev 'recordrtc'

引入

import RecordRTC from 'recordrtc';
export default {
  	data() {
        return {
            recorder: null,
        };
    },
    methods:{
        initRecord(){
             const canvas = this.$refs.canvas;
             this.recorder = RecordRTC(canvas, {type:'gif'}
        }
    }
}

# 解决报错

这里录制的对象是一个canvas,按上述引入后,点击开始录制后,浏览器控制台报一个警告。经测试,如果录制对象是一个媒体流的话,不会有这样的警告。这里需要做就是把这个screenshot.js引入到项目中去。

Please link: https://www.webrtc-experiment.com/screenshot.js

vue.runtime.esm.js?6e6d:619 [Vue warn]: Error in v-on handler: "Please link: https://www.webrtc-experiment.com/screenshot.js"

found in

---> <Test> at src/views/components-demo/RecordRTCDemo/Test.vue
       <Anonymous>
         <AppMain> at src/layout/components/AppMain.vue
           <Layout> at src/layout/index.vue
             <App> at src/App.vue
               <Root>
               
warn @ vue.runtime.esm.js?6e6d:619
logError @ vue.runtime.esm.js?6e6d:1884
globalHandleError @ vue.runtime.esm.js?6e6d:1879
handleError @ vue.runtime.esm.js?6e6d:1839
invokeWithErrorHandling @ vue.runtime.esm.js?6e6d:1862
invoker @ vue.runtime.esm.js?6e6d:2179
original._wrapper @ vue.runtime.esm.js?6e6d:6911

在vue中使用RecordRT记录canvas时报错,经验证,应该是缺少screenshot.js这个文件导致的。

修改public文件夹下的index.html文件。添加如下代码

<script src="https://cdn.webrtc-experiment.com/screenshot.js"></script>

或者使用本地文件

<script src='./screenshot.js'></script>

# RecordRTC的使用

RecordRTC API文档 (opens new window)

RecordRTC(mediaStream, {
	type: 'outPutType',
})

mediaStream:输入是一个媒体流。(Single media-stream object, array of media-streams, html-canvas-element, etc.)

outPutType:输出类型,有video,gif,audio,canvas

example:

这个例子中会获取本地摄像头的权限。输出类型是video,点击停止按钮后,recorder.getBlob()获取到录制的数据,之后做上传,预览等处理。

// click start btn
document.getElementById('btn-start-recording').onclick = function () {
    navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then(function (camera) 		{
            // camera就是一个媒体流
            recorder = RecordRTC(camera, {
                type: 'video'
        	});
        	recorder.startRecording();
   	 	}).catch(function (error) {
        	alert('Unable to capture your camera. Please check console logs.');
        	console.error(error);
    	});
}

// click end btn
document.getElementById('btn-stop-recording').onclick = function () {
    this.disabled = true;
    recorder.stopRecording(stopRecordingCallback);
};

function stopRecordingCallback() {
    const video = document.getElementById('video')
    video.src = video.srcObject = null;
    video.muted = false;
    video.volume = 1;
    video.src = URL.createObjectURL(recorder.getBlob());

    recorder.camera.stop();
    recorder.destroy();
    recorder = null;
}

# 录制video标签

这里的录制video标签是我们想要实现的目的,实际上录制的对象是canvas。这里的关键有两点:

1.何时停止录制。

2.如何将video的画面实时渲染到canvas中

需求要求点击录制按钮后录制5s的画面,点击开始时记录当前系统的时间,this.startTimeCutVideo = new Date().getTime();。在每次循环绘制canvas画面前,用当前时间减去开始时间大于6s时停止记录。

looper中用到了一个API,requestAnimationFrame(looper),这里是循环的关键,这个API的作用是,在页面每次渲染前需要执行looper方法。保证了,video画面绘制到canvas的连续性。

    data() {
        return {
            recorder: null,
            startTimeCutVideo: null,
            endTImeCutVideo: null,
        };
    },
        
     methods:{
         startRecording() {
            this.startTimeCutVideo = new Date().getTime();

            const canvas = this.$refs.canvas;
            this.recorder = RecordRTC(canvas, {
                type: 'gif',
                frameRate: 200,
                quality: 10,
                disableLogs: true,
                onGifRecordingStarted: () => {
                    console.log('录制开始');
                },
                onGifPreview: gifURL => {
                    console.log(gifURL); // 录制中的实时数据,可以显示录制的画面
                },
            });
            this.recorder.startRecording(); // 开始录制
            this.looper();// 循环绘制canvas
        },
        looper() {
            this.endTImeCutVideo = new Date().getTime();
            if ((this.endTImeCutVideo - this.startTimeCutVideo) / 1000 > 6) {
                this.stopRecording();
                return;
            }
            this.drawMedia(); // 刷新canvas
            requestAnimationFrame(this.looper);
        },
       	drawMedia() {
            const canvas = this.$refs.canvas;
            const ctx = canvas.getContext('2d');
            const video = this.$refs.video;
            canvas.setAttribute('width', video.videoWidth);
            canvas.setAttribute('height', video.videoHeight);
            ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
        },
        stopRecording() {
            this.recorder.stopRecording(this.stopRecordingCallback);
        },
        stopRecordingCallback() {
            console.log('Gif 录制完成', this.recorder.getBlob());
            // 上传到服务器
            this.upload(this.recorder.getBlob());
            this.recorder.destroy();
            this.recorder = null;
        },
     }

demo:Gitee (opens new window)

截图