I've looked at a number of related articles, but I haven't been able to find a clear reason why video control-related functions that are made to be performed locally are causing such lag. Also, there are differences because it was written so long ago.
The key is to draw 2 video images on the <canvas>
. In the course of this, we created functions to control the video play, pause, playback rate, and moving between frames.
However, when I press the Load
button the video doesn't load all at once, and if I run the video, do a run playback rate change and run it again, it behaves strangely when the video is finished.
Both videos used are 4
seconds long. Is the code in need of optimization? Or is the logic of the code I wrote wrong?
I'm curious how best to solve it.
// 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>