目录

截图:截取图片后可以进行涂鸦编辑,并能清除,撤销。

录影:点击按钮后录制10s左右的视频文件。

文件上传服务器示例


最近项目开发中,需要实现在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'); // 这里通过toDataURL 获取图片的URL连接链接
        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;

功能最终实现结果如下所示。

vue一般用什么镜像 vue可以视频镜像吗_上传

vue一般用什么镜像 vue可以视频镜像吗_ide_02

vue一般用什么镜像 vue可以视频镜像吗_vue一般用什么镜像_03

vue一般用什么镜像 vue可以视频镜像吗_2d_04

视频截图涂鸦的demo 如下地址

https://github.com/dickbinge/vue-node-admin/blob/jingbin_dev/vue-admin/src/views/video-demo/cutPicture.vue

视频录影,如果视频质量较高录影卡顿就会非常明显,这个暂时没有办法解决。

文件上传服务器示例

这里首先申请阿里云OSS存储服务,具体参考网上教程。

使用canvas 的 toDataURL 方法可以将canvas对象转换为一个 data-URL地址。示例如下

1,首先画一个示例的canvas画板,然后获取对应的data-URL ,并给到一个image对象。

let iCanvas = document.getElementById('canvas');
var getCanvas = function() {
    if (!iCanvas) {
        iCanvas = document.createElement('canvas');
        iCanvas.width = 500;
        iCanvas.height = 300;
        iCanvas.style.position = 'absolute';
        iCanvas.style.left = 0;
        iCanvas.style.top = 0;
        var ctx = iCanvas.getContext('2d');
        ctx.fillStyle = 'blue';
        ctx.fillRect(10, 10, 300, 300);
        document.body.append(iCanvas);
    }
};
getCanvas();

var getImageSrc = function() {
    var src = iCanvas.toDataURL(); // toDataURL 默认参数是image/jpeg, base64位的编码
    var imgObj = new Image();
    imgObj.src = src; // 可以得到一个image对象
    iCanvas.toBlob(blob => {
        imgObj.file = blob; // blob 文件可以通过formData格式传参直接上传服务器
        var url = URL.createObjectURL(blob); // 用于URL的File对象,Blob对象。
    });
}

通过canvas的toBlob 方法创造Blob对象,type指定图片格式,默认格式为image/png

canvas.toBlob(callback, type, encoderOptions);

vue一般用什么镜像 vue可以视频镜像吗_2d_05

vue一般用什么镜像 vue可以视频镜像吗_上传_06

 

vue一般用什么镜像 vue可以视频镜像吗_2d_07

 2,上传到oss存储

 上传文件前需要把我们上传的文件转换成 Blob,File 格式。

  其中File 继承自Blob类,是特殊的Blob,可以用在任意的Blob类型Context 中,即Blob适用的场景File也可以。

// 阿里云OSS存储
var uploadImg = (fileObj) => {
    const OSS = require('ali-oss'); // 引入阿里云的包
    const client = new OSS({
         region,
         accessKeyId,
         accessKeySecret,
         bucket,
    });
    client.multipartUpload(fileObj.fileName, fileObj.file).then((result) => {
        const fileUrl = result.name; // 最终可得到一个上传成功后的文件云端路径,我们可以把这个地址存储在数据库中,当需要查看上传的图片时再进行解析即可。
    });
};

3,使用formData通过请求后端接口上传

var uploadItem = (fileObj) => {
    var formData = new FormData(); // 获取一个formData 对象
    formData.append('file', fileObj.file);
    axios.post('http://....', formData).then(res => { console.log(res);}); //请求接口进行上传
}

可以看到formData 格式中的file有了时间戳等信息。

vue一般用什么镜像 vue可以视频镜像吗_vue一般用什么镜像_08

4,File 格式上传服务器

通常我们可以使用input type="file" 进行上传,我们可以通过inputfile action 直接上传,也可以通过FormData 格式调用接口上传。

<input type="file"
    multiple="multiple"
    id="avatar" name="avatar"
    accept="image/png, image/jpeg">
<script>
    var inputDom = document.getElementId('avatar');
    var fileList = Array.from(inputDom.files);
    // 由于fileList 是一个FileList类型 因此我们可以将其转为Array类型进行遍历。
    fileList.forEach(file => {
        uploadItem(file); // 这里即可以把这个file转为formData 格式然后手动请求后端接口
    });
</script>

vue一般用什么镜像 vue可以视频镜像吗_vue一般用什么镜像_09