控制 HTML5 个视频元素时延迟的原因是什么?
What causes delay when controlling HTML5 video elements?
我看了很多相关文章,但一直无法找到明确的原因,说明为什么本地执行的与视频控制相关的功能会导致这种延迟。还有,因为是很久以前写的,所以还是有区别的。
关键是在<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()
(对于类)
而不是
document.getElementById()
(对于 id)
例如:
const leftVideo = document.querySelector('#left_video');
// instead of
const leftVideo = document.getElementById('left_video');
我在您的代码中进行了所有适当的修改,现在 代码片段至少可以运行 (将视频的 src 替换为 public 视频 link测试):
编辑,考虑有用的评论:
我在视频标签中添加了以下内容:
根据我在 Dynamically using the first frame as poster in HTML5 video
- 已添加
type="video/mp4"
- 已添加
preload="auto"
这有助于在 首次单击 的“加载” 时加载 poster
因此我们现在有:
<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>
只要按“加载”,post用户应该立即出现:
// 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>
我看了很多相关文章,但一直无法找到明确的原因,说明为什么本地执行的与视频控制相关的功能会导致这种延迟。还有,因为是很久以前写的,所以还是有区别的。
关键是在<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()
(对于类)
而不是
document.getElementById()
(对于 id)
例如:
const leftVideo = document.querySelector('#left_video');
// instead of
const leftVideo = document.getElementById('left_video');
我在您的代码中进行了所有适当的修改,现在 代码片段至少可以运行 (将视频的 src 替换为 public 视频 link测试):
编辑,考虑有用的评论:
我在视频标签中添加了以下内容:
-
根据我在 Dynamically using the first frame as poster in HTML5 video
- 已添加
type="video/mp4"
- 已添加
preload="auto"
这有助于在 首次单击 的“加载” 时加载 poster
因此我们现在有:
<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>
只要按“加载”,post用户应该立即出现:
// 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>