前言
又入手新的项目啦,需求是将视频监控在前端页面上展示,并对可对监控进行截屏和涂鸦操作。一听就是我没有实现过的功能啊,瞬间激起了我的兴趣。开心~~
1、视频流的引入
了解flv.js
Flv.js是HTML Flash视频(FLV)播放器,纯原生JavaScript开发,没有用到Flash。由bilibili网站开源。
它的工作原理是将FLV文件流转码复用成ISO BMFF(MP4碎片)片段,然后通过Media Source Extensions将MPA喂进浏览器。
安装flv.js
npm install --save flv.js
使用
<template>
<div ref="video">
<video id="video"/>
</div>
<div @click="tackPic">
快拍
</div>
</template>
//js部分
import flvjs from 'flv.js' //单页面引用
export default{
data(){
return {
player:null
}
},
mounted(){
this.initPlayer();
},
methods:{
initPlayer(){
const video = document.getElementById("video");
this.player = flvjs.createPlayer(
{
type: "flv",
isLive: true,
hasAudio: false,
url: 'http://*******.flv'//视频流文件
},
{
enableWorker: true,
enableStashBuffer: false,
stashInitialSize: 120,
enableDurationMonitor: true
}
);
player.attachMediaElement(video);
player.load();
player.play();
}
},
beforeDestroy(){
this.player.pause();
this.player.unload();
this.player.destroy();
this.player.detachMediaElement();
this.player = null
}
}
2、保证视频流的稳定持续
在视频流的播放过程中,经常会出现视频流中断的问题,直观表现就是卡顿。仔细回忆下,是不是在观看直播过程中也会出现卡顿的情况,其实原理很简单,因为一些原因视频流播放发生中断的话,可进行重连。(后续更新具体操作。。。)
3、截屏保存
利用canvas进行截图
原理:其实就是在video地方新建相同位置大小的canvas标签,然后用canvas的“drawImage”方法,将视频部分保存下来。
click(){
let canvas = document.createElement("canvas");//创建canvas标签
let canvasCtx = canvas.getContext("2d");
let video = document.getElementById('video');
//设置canvas画布的宽和高,这一步很重要,决定截图是否完整
canvas.width = video.offsetWidth;
canvas.height = video.offsetHeight;
//用drawImage方法将图片保存下来
canvasCtx.drawImage(video, 0, 0, canvas.width, canvas.height);
let img = canvas.toDataURL("image/jpeg");//将图片保存为base64格式
}
4、实现涂鸦功能
通过以上的努力,我们已经将图片拿到了。到这里,其实就相当于在一张图片上进行涂鸦并保存。
<template>
<div>
<canvas
:id="random"
width="400"
height="300" //这里的宽高比例根据截屏的图片进行设置,上面video的宽高,可按比例进行缩放
@mousedown="canvasDown($event)"
@mouseup="canvasUp($event)"
@mousemove="canvasMove($event)"
@touchstart="canvasDown($event)"
@touchend="canvasUp($event)"
@touchmove="canvasMove($event)"
>
</canvas>
<div>
<img :src="drawImg"/>
</div>
</div>
</template>
const uuid = require('node-uuid')
export defaut{
data(){
return{
// canvas对象
context: {},
// 是否处于绘制状态
canvasMoveUse: false,
// 绘制矩形和椭圆时用来保存起始点信息
beginRec: {
x: "",
y: "",
imageData: ""
},
// 储存坐标信息
drawInfo: [],
img:new Image(),
random:uuid(), //避免canvas缓存,动态设置id值
url:'',//上面获取的图片的base64值,假装已经props过来
drawImg:''//绘制的图片
}
},
mounted(){
this.initDraw();
},
methods:{
initDraw(){
const canvas = document.getElementById(this.random);
this.context = canvas.getContext("2d");
//初始化图片
this.img.setAttribute("crossOrigin", "Anonymous");
this.img.src = this.url;
this.img.onerror = () => {
var timeStamp = +new Date();
this.img.src = this.url + "?" + timeStamp;
};
this.img.onload = () => {
this.clean();
};
// 初始化画笔
this.context.lineWidth = 4;
this.context.strokeStyle = 'red';
},
// 鼠标按下
canvasDown(e) {
if (this.canDraw) {
this.canvasMoveUse = true;
// client是基于整个页面的坐标,offset是cavas距离pictureDetail顶部以及左边的距离
let canvasX = e.offsetX;
let canvasY = e.offsetY;
// 记录起始点和起始状态
this.beginRec.x = canvasX;
this.beginRec.y = canvasY;
this.beginRec.imageData = this.context.getImageData(
0,
0,
this.width,
this.height
);
// 存储本次绘制坐标信息
this.drawInfo.push({
x: canvasX / this.width,
y: canvasY / this.height,
type: this.lineType
});
}
},
Area(p0, p1, p2) {
let area = 0.0;
area =
p0.x * p1.y +
p1.x * p2.y +
p2.x * p0.y -
p1.x * p0.y -
p2.x * p1.y -
p0.x * p2.y;
return area / 2;
},
// 计算多边形质心
getPolygonAreaCenter(points) {
let sum_x = 0;
let sum_y = 0;
let sum_area = 0;
let p1 = points[1];
for (var i = 2; i < points.length; i++) {
let p2 = points[i];
let area = this.Area(points[0], p1, p2);
sum_area += area;
sum_x += (points[0].x + p1.x + p2.x) * area;
sum_y += (points[0].y + p1.y + p2.y) * area;
p1 = p2;
}
return {
x: sum_x / sum_area / 3,
y: sum_y / sum_area / 3
};
},
// 根据坐标信息绘制图形
drawWithInfo() {
this.info.forEach(item => {
this.context.beginPath();
if (!item.type) {
// 设置颜色
this.context.strokeStyle = item.regionColor;
this.context.fillStyle = item.regionColor;
// 绘制多边形的边
if (typeof item.region === "string") {
item.region = JSON.parse(item.region);
}
item.region.forEach(point => {
this.context.lineTo(point.x * this.width, point.y * this.height);
});
this.context.closePath();
// 在多边形质心标注文字
let point = this.getPolygonAreaCenter(item.region);
this.context.fillText(
item.areaName,
point.x * this.width,
point.y * this.height
);
} else if (item.type === "rec") {
this.context.rect(
item.x * this.width,
item.y * this.height,
item.w * this.width,
item.h * this.height
);
} else if (item.type === "circle") {
this.drawEllipse(
this.context,
(item.x + item.a) * this.width,
(item.y + item.b) * this.height,
item.a > 0 ? item.a * this.width : -item.a * this.width,
item.b > 0 ? item.b * this.height : -item.b * this.height
);
}
this.context.stroke();
});
},
// 鼠标移动时绘制
canvasMove(e) {
if (this.canvasMoveUse && this.canDraw) {
// client是基于整个页面的坐标,offset是cavas距离pictureDetail顶部以及左边的距离
let canvasX = e.offsetX;
let canvasY = e.offsetY;
if (this.lineType === "rec") {
// 绘制矩形时恢复起始点状态再重新绘制
this.context.putImageData(this.beginRec.imageData, 0, 0);
this.context.beginPath();
this.context.rect(
this.beginRec.x,
this.beginRec.y,
canvasX - this.beginRec.x,
canvasY - this.beginRec.y
);
let info = this.drawInfo[this.drawInfo.length - 1];
info.w = canvasX / this.width - info.x;
info.h = canvasY / this.height - info.y;
} else if (this.lineType === "circle") {
// 绘制椭圆时恢复起始点状态再重新绘制
this.context.putImageData(this.beginRec.imageData, 0, 0);
this.context.beginPath();
let a = (canvasX - this.beginRec.x) / 2;
let b = (canvasY - this.beginRec.y) / 2;
this.drawEllipse(
this.context,
this.beginRec.x + a,
this.beginRec.y + b,
a > 0 ? a : -a,
b > 0 ? b : -b
);
let info = this.drawInfo[this.drawInfo.length - 1];
info.a = a / this.width;
info.b = b / this.height;
}
this.context.stroke();
}
},
// 绘制椭圆
drawEllipse(context, x, y, a, b) {
context.save();
var r = a > b ? a : b;
var ratioX = a / r;
var ratioY = b / r;
context.scale(ratioX, ratioY);
context.beginPath();
context.arc(x / ratioX, y / ratioY, r, 0, 2 * Math.PI, false);
context.closePath();
context.restore();
},
// 鼠标抬起
canvasUp(e) {
const canvas = document.getElementById(this.random);
this.drawImg = canvas.toDataURL(); //鼠标抬起保存图片
if (this.canDraw) {
this.canvasMoveUse = false;
}
},
// 获取坐标信息
getInfo() {
return this.drawInfo;
},
// 清空画布
clean() {
this.context.drawImage(this.img, 0, 0, this.width, this.height);
this.drawInfo = [];
if (this.info && this.info.length !== 0) this.drawWithInfo();
}
},
beforeDestroy() {
this.url = "";
}
}