【问题标题】: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>

标签: javascript html video html5-video


【解决方案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 替换为公共视频链接进行测试):

编辑,考虑有用的评论:
我添加到以下视频标签:

  1. 根据我在Dynamically using the first frame as poster in HTML5 video 的帖子,将#t=0.1 添加到视频标签的来源,以加速视频的海报开头
  2. 添加了type="video/mp4"
  3. 添加了preload="auto"
    这有助于在首次点击“加载”
  4. 时加载海报

因此我们现在有:

<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 添加到&lt;video&gt; 标签的来源。我对其进行了测试,现在海报出现得更快。尝试让我们知道它是否改善了您的海报加载。
  • @MinwooKim 还将preload="auto" 添加到&lt;video&gt; 标签。现在,海报在“加载”的第一次点击时加载?
  • 我能够找到并理解您提到的preload 和#t 选项。感谢分享好知识。
猜你喜欢
  • 1970-01-01
  • 2020-10-14
  • 2015-02-28
  • 1970-01-01
  • 1970-01-01
  • 2013-01-12
  • 1970-01-01
  • 2012-07-05
  • 1970-01-01
相关资源
最近更新 更多