控制 HTML5 视频元素时导致延迟的原因是啥?
Posted
技术标签:
【中文标题】控制 HTML5 视频元素时导致延迟的原因是啥?【英文标题】:What causes delay when controlling HTML5 video elements?控制 HTML5 视频元素时导致延迟的原因是什么? 【发布时间】:2021-10-30 07:09:09 【问题描述】:我查看了许多相关文章,但我无法找到一个明确的原因,为什么要在本地执行的视频控制相关功能会导致这种延迟。另外,因为是很久以前写的,所以也有区别。
关键是在<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>
【问题讨论】:
第 1 步:记得减少帖子的代码。如果您的问题是关于视频,那么您应该能够在删除与视频无关的一切时重现您的问题(例如,没有CSS,没有媒体控制,没有Canvas元素等)。将你的代码变成minimal reproducible example 并强迫自己删除一些东西,直到你只剩下最少的代码几乎总是让你自己找到问题,因为在某些时候你删除了一些东西,问题突然消失了:你只是找到了什么造成的。但如果没有,你现在有完美的代码来发帖了。 你为什么使用 querySelector 而不是我?例如,QuerySelectoe 非常适合在列表中查找 .selected。 请注意,没有理由坚持使用getElementById
而不是querySelector
。这不是性能瓶颈,并且会产生相同的结果(null
或 htmlElement)。唯一的区别是您的字符串是否包含#
。 querySelector 和 querySelectorAll 函数不是“用于类”,它们是“用于查询选择器”,包括 id、标记名、类、属性以及有效查询选择器可能包含的所有其他内容,并使用它们代替 getElementById、getElementsByTagName、等等都很好。
@Jürgen Fink 我在发帖后看到了你的回答,这就是 +1 的来源……我,干杯。
历史。 getElementById
987654332 querySelector
2087654332 @几乎十年,Ququery甚至没有添加QuerySelector,直到jQuery显示了能够通过CSS查询选择器获得更多有用的有用,多年来。 span>
【参考方案1】:
我发现并希望分享的一些问题:
我注意到在你的 JS 文件中你重复使用了
document.querySelector()
(用于类)
而不是document.getElementById()
(用于id)
例如:
const leftVideo = document.querySelector('#left_video');
// instead of
const leftVideo = document.getElementById('left_video');
我在您的代码中进行了所有适当的修改,现在至少 code sn-p 运行(将视频的 src 替换为公共视频链接进行测试):
编辑,考虑有用的评论: 我添加到以下视频标签:
-
根据我在Dynamically using the first frame as poster in HTML5 video 的帖子,将
#t=0.1
添加到视频标签的来源,以加速视频的海报开头
添加了type="video/mp4"
添加了preload="auto"
这有助于在首次点击“加载”时加载海报
因此我们现在有:
<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>
【讨论】:
Load
函数是一个海报函数,用于显示视频Play
之前的第一帧。目前至少需要点击 2 次才能加载第一帧,有没有其他方法可以解决这个问题?
@MinwooKim 我在答案中编辑了代码,根据我的帖子Dynamically using the first frame as poster in HTML5 video 将#t=0.1
添加到<video>
标签的来源。我对其进行了测试,现在海报出现得更快。尝试让我们知道它是否改善了您的海报加载。
@MinwooKim 还将preload="auto"
添加到<video>
标签。现在,海报在“加载”的第一次点击时加载?
我能够找到并理解您提到的preload
和#t
选项。感谢分享好知识。以上是关于控制 HTML5 视频元素时导致延迟的原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章