Web演示
1.同步播放
在播放事件触发后,依据定时器实时获取帧及对应数据,并在画布恢复背景帧及标注信息
1.1添加播放控件
$("#PlayPause").click(function () {
if ($("#myVideo")[0].paused){
$("#myVideo")[0].play();
} else{
$("#myVideo")[0].pause();
}
});
1.2视频帧截取
通过不断视频帧截取,并粘贴至画布,以模拟视频播放的效果。
踩的坑:
1)画布是完全覆盖在视频上,起初为了看见视频画面,设置画布区域透明度opacity: 0.5 直接导致的是画布上的标注信息(线条、文字)变得模糊。
2)截取视频帧时采取定时器去描述时间间隔,去截取视频帧,drawImage至画布。然而setTimeout和setInterval精度不高且会出现丢帧。
实现步骤:
①定时器:requestAnimationFrame
function render() {
var now = Date.now();
if($("#myVideo")[0].currentTime>=$(".duration").text()|| globalFrame>=globalMaxFrame) {
$(".current-time").text($(".duration").text());
}else{
$(".current-time").text($("#myVideo")[0].currentTime.toFixed(2));
}
if ($("#myVideo")[0].played) {
console.log(globalFrame, ":", Math.round($("#myVideo")[0].currentTime / globalFrameTime));
if(globalFrame!==Math.round($("#myVideo")[0].currentTime / globalFrameTime)) {
var currentRatio = $("#myVideo")[0].currentTime / $("#myVideo")[0].duration;
$(".progress-bar").css("width", currentRatio*100 + '%'); //只有当帧改变再更新进度条
console.log("实际时间",now,"AnimationFrame时间戳",$("#myVideo")[0].currentTime);
getCurrentFrame(); //该自定义函数主要加载当前时间戳对应帧(并调用标注信息加载及绘制操作函数)
timer = window.requestAnimationFrame(render);
}else {
console.log("帧没变化");
timer = window.requestAnimationFrame(render);
}
}else {
window.cancelAnimationFrame(timer);
}
}
var timer;
$("#myVideo").bind({
'play': function () {
if($("#myVideo")[0].currentTime>globalFrameTime) {
}else {
$(".progress-bar").css("width",0+'%'); //进度条归0
}
// timer = setInterval(getCurrentFrame, 1000 * globalFrameTime);
timer = requestAnimationFrame(render);
console.log("video play");
},
'pause': function () {
// clearInterval(timer);
window.cancelAnimationFrame(timer);
console.log("video pause");
},
'ended': function () {
$(".progress-bar").css("width", 100 + '%'); //进度条满血
$(" .play-btn span").removeClass("glyphicon-pause").addClass("glyphicon-play");
// clearInterval(timer);
window.cancelAnimationFrame(timer);
console.log("video ended");
}
});
②绘制视频帧
思路:为了让帧作为画布背景,以致标签信息在背景帧的上面,每次在清除完画布后,立即绘制对应的背景帧。
function clearCanvas() {
//清除画布
canvas.ctx.clearRect(0, 0, canvas.width, canvas.height);
if($("#videoName").val()) {
var v = $("#myVideo")[0];
canvas.ctx.drawImage(v,0,0,canvas.width,canvas.height);
}else {
var image = $("#myimg")[0];
canvas.ctx.drawImage(image,0,0,canvas.width,canvas.height);
}
}
1.2.标注恢复
①②③④⑤⑥⑦⑧√×✔✘☞☜
页面显示,后端所发送含标注信息的json格式数据
实现步骤:
①获取当前帧:自定义getCurrentFrame();
function getCurrentFrame() {
var currentTime = $("#myVideo")[0].currentTime; // 检测当前的播放时间
globalFrame = Math.round(currentTime / globalFrameTime); //globalFrameTime为帧间隔
console.log("当前帧", globalFrame,"当前时间戳",currentTime,"/",$("#myVideo")[0].duration);
currentFrameData();
}
②当前帧数据获取
function currentFrameData() {
//总共26帧 0~25
// var currentTime = $("#myVideo")[0].currentTime; // 检测当前的播放时间
// globalFrame = Math.round(currentTime / globalFrameTime);
if (globalFrame>=globalMaxFrame) {
globalFrame = globalMaxFrame;
console.log("帧结束", globalFrame);
}
if (globalVideoJsonData.hasOwnProperty(globalFrame.toString())) {
//data的索引后是个json字符串,需要转为json对象 js才能操作
var jsonObj = JSON.parse(globalVideoJsonData[globalFrame.toString()]); //当下帧对应数据
console.log(globalFrame, " data:", jsonObj);
restoreData(jsonObj); //恢复数据至对应变量
} else {
console.log(globalFrame,"该帧数据不存在");
clearCanvas();
canvas.labels = [];
canvas.polygons = [];
}
}
③当前帧显示:帧背景+标注信息
function restoreData(data) {
clearCanvas(); //清空画布,绘制当下帧背景
canvas.labels = [];
canvas.polygons = [];
// alert("data:" + data.labels); //可以 .属性 方式访问,说明闲杂是json对象 //JS操作的是JSON对象
$.each(data.polygons,function (index,value) {
var polygon = {
};
for(var i=0;i<Object.keys(value).length/2;i++) {
polygon["x"+i] = value["x"+i]
polygon["y"+i] = value["y"+i]
}
canvas.labels.push(data.labels[index]);
canvas.polygons.push(polygon);
});
console.log("labels",canvas.labels);
drawPolygons(); //画四边形框
drawAllLabel(); //画标签
}
☞帧间隔 globalFrameTime
web端视频标签可以获取视频的总时长与当前时间(注意这个当前时间是不精确的,因为会存在加载卡顿),所以本地数据库是以帧数去获取保存对象数据的。故为二者对应,我们需要知道当前时间的对应帧(即除以帧间隔)
措施:我们在上传视频、OCR识别完成返回的视频标注数据中嵌入当前视频总帧数,然后通过总时间除以总帧数即可
globalVideoJsonData = data; //data为ajax异步通信返回的视频标注数据,内含视频的总帧数
globalMaxFrame = globalVideoJsonData["totalFramesNum"] - 1; // 0 ~
globalFrameTime = $("#myVideo")[0].duration / (globalMaxFrame+1); //从0开始。注帧间隔中显示的是前一帧
2.帧跳转
对于视频,我们通常需要去进行信息回查,故只从前到后的依次播放不能满足需求
2.1.前后帧切换
踩的坑:
1)帧跳转(即人为该变时间戳$("#myVideo")[0].currentTime = newValue,赋予新值后,视频不会立即跳转,加载目标时间戳的帧需要时间。这也是起初导致帧切换后,标注信息切换过去了,而帧背景绘制的仍是原始帧的图片。由于目标帧还未加载完,便要画背景帧,绘制完背景帧后视频才切换到目标位置(此时视频画面和画布真背景是不一样的
solution:在绘制背景帧前加个延时,以便其目标帧加载完
①添加前后帧切换控件
$("#Prev").click(function () {
getPrevFrame();
});
$("#Next").click(function () {
getNextFrame();
});
function getNextFrame() {
var currentTime = $("#myVideo")[0].currentTime; // 检测当前的播放时间
if(currentTime+globalFrameTime<=$("#myVideo")[0].duration) {
globalFrame = Math.round(currentTime / globalFrameTime);
globalFrame += 1;
$("#myVideo")[0].currentTime = (globalFrame+0.25)*globalFrameTime;
$(".current-time").text($("#myVideo")[0].currentTime.toFixed(2));
var currentRatio = $("#myVideo")[0].currentTime / $("#myVideo")[0].duration;
$(".progress-bar").css("width", currentRatio*100 + '%');
console.log("后一帧", globalFrame,"时间戳",$("#myVideo")[0].currentTime);
// currentFrameData(); //放在滑动条改变响应函数中,以致目标帧加载完
}else {
$("#myVideo")[0].currentTime = $("#myVideo")[0].duration;
$(".current-time").text($(".duration").text());
}
}
function getPrevFrame() {
var currentTime = $("#myVideo")[0].currentTime; // 检测当前的播放时间
if(currentTime>0) {
globalFrame = Math.round(currentTime / globalFrameTime);
globalFrame -= 1;
if(globalFrame<=0) {
globalFrame = 0;
$(".progress-bar").css("width",0+'%');
$("#myVideo")[0].currentTime = 0;
}else{
$("#myVideo")[0].currentTime = (globalFrame+0.25)*globalFrameTime;
}
$(".current-time").text($("#myVideo")[0].currentTime.toFixed(2));
var currentRatio = $("#myVideo")[0].currentTime / $("#myVideo")[0].duration;
$(".progress-bar").css("width", currentRatio*100 + '%');
console.log("前一帧哈", globalFrame,"时间戳",$("#myVideo")[0].currentTime);
// currentFrameData(); //放在滑动条改变响应函数中,以致目标帧加载完
}
}
②目标帧加载缓冲
给视频添加监听滑动条改变的事件(即时间戳人为赋值跳转)
$("#myVideo").bind({
...
'seeking':function() {
//帧跳转
console.log('开始移动进度条');
setTimeout(function () {
//给个定时器好让图片加载完
console.log("视频帧缓冲");
currentFrameData(); //当前帧数据
}, 200); //200ms
}
});