我已经看了很多相关的文章,但是我还没有找到一个明确的原因,为什么视频控制相关的功能在本地执行会导致这样的延迟。还有,因为它是很久以前写的,所以有不同之处。
关键是在<canvas>
上绘制2个视频图像。在此过程中,我们创建了控制视频播放、暂停、播放速率和帧间移动的函数。
然而,当我按下Load
按钮时,视频不会立即加载,如果我运行视频,做一个运行播放速率改变并再次运行,当视频完成时,它的行为很奇怪。
使用的两个视频都是4
秒长。代码需要优化吗?还是我写的代码逻辑出错了?
我很好奇如何最好地解决这个问题。
// FPS
const FPS = 1 / 60;
const leftVideo = document.querySelector('#left_video');
const rightVideo = document.querySelector('#right_video');
const leftCanvas = document.querySelector('#left_canvas');
const rightCanvas = document.querySelector('#right_canvas');
leftCanvas.width = 256;
leftCanvas.height = 256;
rightCanvas.width = 256;
rightCanvas.height = 256;
const leftCanvasContext = leftCanvas.getContext('2d');
const rightCanvasContext = rightCanvas.getContext('2d');
const mediaLoadButton = document.querySelector('#media_load');
const mediaPlayButton = document.querySelector('#media_play');
const mediaPauseButton = document.querySelector('#media_pause');
const mediaPreviousFrameButton = document.querySelector('#media_previous_frame');
const mediaNextFrameButton = document.querySelector('#media_next_frame');
const mediaPlaybackRateButton = document.querySelectorAll('.media_playback_rate');
const mediaSeekBar = document.querySelector('#media_seekbar');
const updateVideoTime = () => {
mediaSeekBar.value = leftVideo.currentTime;
mediaSeekBar.style.backgroundSize = (mediaSeekBar.value - mediaSeekBar.min) * 100 / (mediaSeekBar.max - mediaSeekBar.min) + '% 100%';
};
const updateSeekBar = (event) => {
const location = (event.offsetX / mediaSeekBar.offsetWidth) * leftVideo.duration;
leftVideo.currentTime = location;
rightVideo.currentTime = location;
};
const loadVideoFirstFrame = (direction) => {
switch(direction) {
case 'left':
if(!isNaN(leftVideo.duration)) {
leftVideo.currentTime = 0;
}
break;
case 'right':
if(!isNaN(rightVideo.duration)) {
rightVideo.currentTime = 0;
}
break;
}
};
const drawVideoFrame = (direction) => {
switch(direction) {
case 'left':
leftCanvasContext.drawImage(leftVideo, 0, 0, leftCanvas.width, leftCanvas.height);
requestAnimationFrame(() => { drawVideoFrame('left'); });
break;
case 'right':
rightCanvasContext.drawImage(rightVideo, 0, 0, rightCanvas.width, rightCanvas.height);
requestAnimationFrame(() => { drawVideoFrame('right'); });
break;
}
};
let playbackRate = 1.0;
let videoMousedown = false;
mediaLoadButton.addEventListener('click', () => {
loadVideoFirstFrame('left');
loadVideoFirstFrame('right');
mediaSeekBar.min = 0;
mediaSeekBar.max = leftVideo.duration;
leftCanvasContext.drawImage(leftVideo, 0, 0, leftCanvas.width, leftCanvas.height);
rightCanvasContext.drawImage(rightVideo, 0, 0, rightCanvas.width, rightCanvas.height);
});
leftVideo.addEventListener('play', () => {
drawVideoFrame('left');
});
leftVideo.addEventListener('timeupdate', updateVideoTime, false);
leftVideo.addEventListener('ended', () => {
loadVideoFirstFrame('left');
loadVideoFirstFrame('right');
mediaSeekBar.value = 0;
mediaSeekBar.min = 0;
mediaSeekBar.max = leftVideo.duration;
leftCanvasContext.drawImage(leftVideo, 0, 0, leftCanvas.width, leftCanvas.height);
rightCanvasContext.drawImage(rightVideo, 0, 0, rightCanvas.width, rightCanvas.height);
});
rightVideo.addEventListener('play', () => {
drawVideoFrame('right');
});
mediaPlayButton.addEventListener('click', () => {
leftVideo.playbackRate = 0.2;
rightVideo.playbackRate = 0.2;
leftVideo.play();
rightVideo.play();
console.info(`PLAYBACK RATE VALUE : ${parseFloat(playbackRate).toFixed(1)}`);
});
mediaPauseButton.addEventListener('click', () => {
leftVideo.pause();
rightVideo.pause();
});
mediaPreviousFrameButton.addEventListener('click', () => {
leftVideo.currentTime = Math.max(0, leftVideo.currentTime - FPS);
rightVideo.currentTime = Math.max(0, rightVideo.currentTime - FPS);
});
mediaNextFrameButton.addEventListener('click', () => {
leftVideo.currentTime = Math.min(leftVideo.duration, leftVideo.currentTime + FPS);
rightVideo.currentTime = Math.min(rightVideo.duration, rightVideo.currentTime + FPS);
});
mediaSeekBar.addEventListener('click', updateSeekBar);
mediaSeekBar.addEventListener('mousemove', (event) => videoMousedown && updateSeekBar(event));
mediaSeekBar.addEventListener('mousedown', () => videoMousedown = true);
mediaSeekBar.addEventListener('mouseup', () => videoMousedown = false);
mediaPlaybackRateButton.forEach((element) => {
element.addEventListener('click', (event) => {
playbackRate = event.target.innerText;
});
});
<div class="container">
<div class="media-wrapper">
<!-- left video -->
<video id="left_video" src="res/left.mp4"></video>
<!-- right video -->
<video id="right_video" src="res/right.mp4"></video>
<!-- left canvas for left video -->
<canvas id="left_canvas"></canvas>
<!-- right canvas for right video -->
<canvas id="right_canvas"></canvas>
</div>
<div class="media-controller-wrapper">
<input id="media_seekbar" type="range" step="any" value="0" min="0" max="100" onchange="updateVideoTime()"/>
<button id="media_load" type="button">Load</button>
<button id="media_play" type="button">Play</button>
<button id="media_pause" type="button">Pause</button>
<button id="media_previous_frame" type="button">Previous frame</button>
<button id="media_next_frame" type="button">Next frame</button>
<button class="media_playback_rate" type="button">1.0</button>
<button class="media_playback_rate" type="button">0.8</button>
<button class="media_playback_rate" type="button">0.6</button>
<button class="media_playback_rate" type="button">0.4</button>
<button class="media_playback_rate" type="button">0.2</button>
</div>
</div>
我发现了一些问题,想分享一下:
我注意到在你的JS文件中你反复使用
document.querySelector()
(forclasses))
而不是document.getElementById()
(id)
例如:
const leftVideo = document.querySelector('#left_video');
// instead of
const leftVideo = document.getElementById('left_video');
我在适当的地方对代码进行了所有修改,现在代码片段运行至少(将视频的SRC替换为公共视频链接进行测试):
编辑
我在视频标签下面添加了:
- 添加
#t=0.1
到源根据我在HTML5视频 中动态使用第一帧作为海报的帖子,视频标签开始加速视频的海报 - 添加
type="video/mp4"
- 添加
preload="auto"
这有助于加载海报在第一次点击的"Load">
所以我们现在有:
<video id="left_video" src="https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_1MB.mp4#t=0.1" type="video/mp4" preload="auto"></video>
只需按"加载">海报应立即出现:
// FPS
const FPS = 1 / 60;
const leftVideo = document.getElementById('left_video');
const rightVideo = document.getElementById('right_video');
const leftCanvas = document.getElementById('left_canvas');
const rightCanvas = document.getElementById('right_canvas');
leftCanvas.width = 256;
leftCanvas.height = 256;
rightCanvas.width = 256;
rightCanvas.height = 256;
const leftCanvasContext = leftCanvas.getContext('2d');
const rightCanvasContext = rightCanvas.getContext('2d');
const mediaLoadButton = document.getElementById('media_load');
const mediaPlayButton = document.getElementById('media_play');
const mediaPauseButton = document.getElementById('media_pause');
const mediaPreviousFrameButton = document.getElementById('media_previous_frame');
const mediaNextFrameButton = document.getElementById('media_next_frame');
const mediaPlaybackRateButton = document.querySelectorAll('.media_playback_rate');
const mediaSeekBar = document.getElementById('media_seekbar');
const updateVideoTime = () => {
mediaSeekBar.value = leftVideo.currentTime;
mediaSeekBar.style.backgroundSize = (mediaSeekBar.value - mediaSeekBar.min) * 100 / (mediaSeekBar.max - mediaSeekBar.min) + '% 100%';
};
const updateSeekBar = (event) => {
const location = (event.offsetX / mediaSeekBar.offsetWidth) * leftVideo.duration;
leftVideo.currentTime = location;
rightVideo.currentTime = location;
};
const loadVideoFirstFrame = (direction) => {
switch(direction) {
case 'left':
if(!isNaN(leftVideo.duration)) {
leftVideo.currentTime = 0;
}
break;
case 'right':
if(!isNaN(rightVideo.duration)) {
rightVideo.currentTime = 0;
}
break;
}
};
const drawVideoFrame = (direction) => {
switch(direction) {
case 'left':
leftCanvasContext.drawImage(leftVideo, 0, 0, leftCanvas.width, leftCanvas.height);
requestAnimationFrame(() => { drawVideoFrame('left'); });
break;
case 'right':
rightCanvasContext.drawImage(rightVideo, 0, 0, rightCanvas.width, rightCanvas.height);
requestAnimationFrame(() => { drawVideoFrame('right'); });
break;
}
};
let playbackRate = 1.0;
let videoMousedown = false;
mediaLoadButton.addEventListener('click', () => {
loadVideoFirstFrame('left');
loadVideoFirstFrame('right');
mediaSeekBar.min = 0;
mediaSeekBar.max = leftVideo.duration;
leftCanvasContext.drawImage(leftVideo, 0, 0, leftCanvas.width, leftCanvas.height);
rightCanvasContext.drawImage(rightVideo, 0, 0, rightCanvas.width, rightCanvas.height);
});
leftVideo.addEventListener('play', () => {
drawVideoFrame('left');
});
leftVideo.addEventListener('timeupdate', updateVideoTime, false);
leftVideo.addEventListener('ended', () => {
loadVideoFirstFrame('left');
loadVideoFirstFrame('right');
mediaSeekBar.value = 0;
mediaSeekBar.min = 0;
mediaSeekBar.max = leftVideo.duration;
leftCanvasContext.drawImage(leftVideo, 0, 0, leftCanvas.width, leftCanvas.height);
rightCanvasContext.drawImage(rightVideo, 0, 0, rightCanvas.width, rightCanvas.height);
});
rightVideo.addEventListener('play', () => {
drawVideoFrame('right');
});
mediaPlayButton.addEventListener('click', () => {
leftVideo.playbackRate = 0.2;
rightVideo.playbackRate = 0.2;
leftVideo.play();
rightVideo.play();
console.info(`PLAYBACK RATE VALUE : ${parseFloat(playbackRate).toFixed(1)}`);
});
mediaPauseButton.addEventListener('click', () => {
leftVideo.pause();
rightVideo.pause();
});
mediaPreviousFrameButton.addEventListener('click', () => {
leftVideo.currentTime = Math.max(0, leftVideo.currentTime - FPS);
rightVideo.currentTime = Math.max(0, rightVideo.currentTime - FPS);
});
mediaNextFrameButton.addEventListener('click', () => {
leftVideo.currentTime = Math.min(leftVideo.duration, leftVideo.currentTime + FPS);
rightVideo.currentTime = Math.min(rightVideo.duration, rightVideo.currentTime + FPS);
});
mediaSeekBar.addEventListener('click', updateSeekBar);
mediaSeekBar.addEventListener('mousemove', (event) => videoMousedown && updateSeekBar(event));
mediaSeekBar.addEventListener('mousedown', () => videoMousedown = true);
mediaSeekBar.addEventListener('mouseup', () => videoMousedown = false);
mediaPlaybackRateButton.forEach((element) => {
element.addEventListener('click', (event) => {
playbackRate = event.target.innerText;
});
});
@charset "UTF-8";
html, body {
background-color: #242424;
}
video {
display: none;
}
canvas {
width: 256px;
height: 256px;
}
input[type="range"] {
-webkit-appearance: none;
width : 100%;
height : 1px !important;
background : #FFFFFF;
border-radius : 0px;
background-image : linear-gradient(#FF0000, #FF0000);
background-size : 0% 100%;
background-repeat : no-repeat;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width : 8px;
height : 8px;
border-radius : 50%;
background-color : #FFFFFF;
cursor : pointer;
box-shadow : 0 0 2px 0 #555555;
transition : background .3s ease-in-out;
}
input[type="range"]::-moz-range-thumb {
-webkit-appearance: none;
width : 8px;
height : 8px;
border-radius : 50%;
background-color : #FFFFFF;
cursor : pointer;
box-shadow : 0 0 2px 0 #555555;
transition : background .3s ease-in-out;
}
input[type="range"]::-ms-thumb {
-webkit-appearance: none;
width : 8px;
height : 8px;
border-radius : 50%;
background-color : #FFFFFF;
cursor : pointer;
box-shadow : 0 0 2px 0 #555555;
transition : background .3s ease-in-out;
}
input[type="range"]::-webkit-slider-thumb:hover {
background : #FF0000;
}
input[type="range"]::-moz-range-thumb:hover {
background : #FF0000;
}
input[type="range"]::-ms-thumb:hover {
background : #FF0000;
}
input[type="range"]::-webkit-slider-runnable-track {
-webkit-appearance: none;
box-shadow : none;
border : none;
background : transparent;
}
input[type="range"]::-moz-range-track {
-webkit-appearance: none;
box-shadow : none;
border : none;
background : transparent;
}
input[type="range"]::-ms-track {
-webkit-appearance: none;
box-shadow : none;
border : none;
background : transparent;
}
#media_seekbar {
width: 512px;
height: auto;
}
<div class="container">
<div class="media-wrapper">
<!-- left video -->
<video id="left_video" src="https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_1MB.mp4#t=0.1" type="video/mp4"></video>
<!-- right video -->
<video id="right_video" src="https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_1MB.mp4#t=0.1" type="video/mp4"></video>
<!-- left canvas for left video -->
<canvas id="left_canvas"></canvas>
<!-- right canvas for right video -->
<canvas id="right_canvas"></canvas>
</div>
<div class="media-controller-wrapper">
<input id="media_seekbar" type="range" step="any" value="0" min="0" max="100" onchange="updateVideoTime()"/>
<button id="media_load" type="button">Load</button>
<button id="media_play" type="button">Play</button>
<button id="media_pause" type="button">Pause</button>
<button id="media_previous_frame" type="button">Previous frame</button>
<button id="media_next_frame" type="button">Next frame</button>
<button class="media_playback_rate" type="button">1.0</button>
<button class="media_playback_rate" type="button">0.8</button>
<button class="media_playback_rate" type="button">0.6</button>
<button class="media_playback_rate" type="button">0.4</button>
<button class="media_playback_rate" type="button">0.2</button>
</div>
</div>