最近项目开发中,需要实现在video 视频上截图,录影后将文件上传到阿里云服务器上。截图功能相对来说比较容易实现,使用canvas 的 drawImage 方法将video 控件的区域绘制下来即可。录影相对来说比较麻烦,目前用webRTC 简单实现。
功能简单介绍:使用videojs来播放海康NVR 的Dash视频流,需要针对实时画面进行截取,或者录制10s的视频片段然后上传至阿里云上。
截图:截取图片后可以进行涂鸦编辑,并能清除,撤销。
//截取当前帧的图片
cutPicture(){
let self=this;
self.showCancelContent=false; // 在点击video 上的截图按钮隐藏取消编辑区域。
if(self.sourceList.length>=5){
self.notify('最多上传5个资源!','warning',3000);
return false;
}
if(self.fullScreen){ //视频如果处于全屏状态,退出全屏
self.exitFullscreen();
self.fullScreen=false;
}
self.showCutDialog=true;
self.$nextTick(()=>{
self.canvasEl=document.getElementById('icanvas');
var ctx = self.canvasEl.getContext('2d');
ctx.drawImage(self.videoEl,0,0,767*self.percentHeight,431*self.percentHeight);
var oGrayImg=self.canvasEl.toDataURL('image/jpeg');
self.imageCanvas.src=oGrayImg;
let imgObj=new Image();
imgObj.src=oGrayImg; //新建一个image 对象把初始的canvas.src 添加到数组中。
self.imageCanvasList.push(imgObj);
})
},
涂鸦编辑:通过监听canvas上的鼠标按下,移动,抬起事件来进行绘制。
//给canvas注册mousedown mousemove 事件
mouseDownAction(e){
let self=this;
self.isMouseDown=true;
self.X=e.offsetX;
self.Y=e.offsetY;
self.showPenBtn=false;
},
mouseMoveAction(e){
let self=this;
if(self.isMouseDown){
self.X1=e.offsetX;
self.Y1=e.offsetY;
self.showPenBtn=false; //鼠标移动的时候隐藏画笔区域
self.drawLine(self.X,self.Y,self.X1,self.Y1);
self.flag++;
}
},
mouseUpAction(e){
let self=this;
self.isMouseDown=false;
self.showPenBtn=true;
self.showCancelContent=true; //每次鼠标弹起后显示撤销区域,画笔区域
if(self.flag!=0&&self.canvasEl!=''){
let imgObj=new Image();
imgObj.src=self.canvasEl.toDataURL("image/jpeg");
self.imageCanvasList.push(imgObj); //每编辑一次就存入数组中
}
self.flag=0;
},
drawLine(x,y,x1,y1){
let self=this;
var ctx=self.canvasEl.getContext('2d');
if(self.flag){
ctx.beginPath();
}
ctx.moveTo(x,y);
ctx.lineWidth=4;
ctx.strokeStyle=self.penChecked;
ctx.lineTo(x1,y1);
ctx.stroke();
if(self.flag!=0){
self.X=self.X1;
self.Y=self.Y1;
}
},
涂鸦撤销清除功能:在清空canvas的同时,把数组中存放的image 再绘制到canvas上。
// 清空canvas 上所有的涂鸦信息
removeEditCanvas(){
let self=this;
self.showCancelContent=false;
self.canvasEl=document.getElementById('icanvas');
var ctx = self.canvasEl.getContext('2d');
ctx.clearRect(0,0,767*self.percentHeight,431*self.percentHeight);
ctx.drawImage(self.imageCanvasList[0],0,0,767*self.percentHeight,431*self.percentHeight);
self.imageCanvasList=[];
},
// 撤销一次涂鸦
cancleEditCanvas(){
let self=this;
self.showCancelContent=false;
self.imageCanvasList.pop();
self.canvasEl=document.getElementById('icanvas');
var ctx = self.canvasEl.getContext('2d');
ctx.clearRect(0,0,767*self.percentHeight,431*self.percentHeight);
if(self.imageCanvasList.length==0){
ctx.drawImage(self.imageCanvas,0,0,767*self.percentHeight,431*self.percentHeight);
}
else{
ctx.drawImage(self.imageCanvasList[self.imageCanvasList.length-1],0,0,767*self.percentHeight,431*self.percentHeight);
}
},
最终点击确认添加到下方的sourceList 中。
录影:点击按钮后录制10s左右的视频文件。
import RecordRTC from '../../../static/RecordRTC.js'
getVideo(){
let self=this;
self.showGetVideo=true; //显示录制按钮
self.videoSpeed=0;
self.$nextTick(()=>{
self.startTimeCutVideo=new Date().getTime();
self.computeFrame();
self.looper();
setTimeout(()=>{
var btn_canvas = document.getElementById("btn-graph-canvas");
self.drawBtn(btn_canvas, 100, "#f31d65", "#f31d65"); //在开始录制的同时显示录制进度的按钮。
},1000)
})
},
looper(){
let self=this;
if(!self.isRecordingStarted){
self.timeVideo=setTimeout(self.looper, 0);
}
else{
self.endTImeCutVideo=new Date().getTime();
if((self.endTImeCutVideo-self.startTimeCutVideo)/1000>11){
clearTimeout(self.timeVideo);
self.showGetVideo=false;
self.isRecordingStarted=false;
self.isREC=false;
setTimeout(()=>{
self.addVideoToList();
},100)
}
else{
self.isREC=true;
html2canvas(self.videoEl).then(function(canvas){
var ctx = self.canvasEl.getContext('2d');
let width=self.varyWindowWidth*0.418;
let height=self.varyWindowWidth*0.288;
ctx.clearRect(0, 0, width, height);
ctx.drawImage(self.videoEl,0,0,width,height);
if(self.isStoppedRecording) {
return;
}
requestAnimationFrame(self.looper);
})
}
}
},
computeFrame(){
let self=this;
self.canvasEl=document.getElementById('vcanvas');
var ctx = self.canvasEl.getContext('2d');
self.recorder = RecordRTC(self.canvasEl, {
type: 'canvas'
});
self.isStoppedRecording =false;
self.isRecordingStarted = true;
self.recorder.startRecording(); //开始录制
},
绘制录制按钮:参考网上大神绘制环形进度条的代码做的。使用计时器每秒绘制10%的长度。
drawMain(drawing_elem, percent, forecolor, bgcolor) {
/*
@drawing_elem: 绘制对象
@percent:绘制圆环百分比, 范围[0, 100]
@forecolor: 绘制圆环的前景色,颜色代码
@bgcolor: 绘制圆环的背景色,颜色代码
*/
let self=this;
var context = drawing_elem.getContext("2d");
var center_x = drawing_elem.width / 2;
var center_y = drawing_elem.height / 2;
var rad = Math.PI*2/100;
// 绘制背景圆圈
function backgroundCircle(){
context.beginPath();
context.lineWidth = 14; //设置线宽
var radius = center_x - context.lineWidth;
context.arc(center_x, center_y, radius, 0, Math.PI*2, false);
context.fillStyle=bgcolor;
context.globalAlpha = 0.5;
context.fill();
}
//绘制运动圆环
function foregroundCircle(n){
context.save();
context.strokeStyle = forecolor;
context.globalAlpha = 1;
context.lineWidth = 6;
context.lineCap = "round";
var radius = center_x - context.lineWidth;
context.beginPath();
context.arc(center_x, center_y, radius , -Math.PI/2, -Math.PI/2 +n*rad, false); //用于绘制圆弧context.arc(x坐标,y坐标,半径,起始角度,终止角度,顺时针/逆时针)
context.stroke();
context.closePath();
context.restore();
}
//绘制文字
function text(n){
context.save();
context.fillStyle='white';
context.globalAlpha = 1;
var font_size=self.btnFontSize;
context.font='bold '+font_size+'px Helvetica';
var textStr='';
if(n==100){
textStr='录制成功';
}
else{
textStr='正在录制';
}
var text_width = context.measureText(textStr).width;
context.fillText(textStr,center_x-text_width/2,center_y+font_size/2);
context.restore();
}
//执行动画
function drawFrame(speed){
context.clearRect(0, 0, drawing_elem.width, drawing_elem.height);
backgroundCircle();
text(speed);
foregroundCircle(speed);
if(speed>=percent){
clearInterval(self.videoSpeedId);
}
}
self.videoSpeedId=setInterval(() => {
if(self.videoSpeed >= percent){
return;
}
else{
self.videoSpeed += 2;
drawFrame(self.videoSpeed);
}
}, 100);
},
在录影过程中,可以设置面板pointer-events 样式来防止用户在页面上进行操作,从而确保10s的视频录制完成。
pointer-events: none;
功能最终实现结果如下所示。