mirror of
https://github.com/video-dev/hls.js.git
synced 2026-06-16 13:34:44 +00:00
Do not start loading on level update from interstitials-controller when autoStartLoad is false
Fix start on jagged audio/video segment boundaries before interstitial break
This commit is contained in:
@@ -810,15 +810,18 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe
|
||||
const videoTrack = tracks.video;
|
||||
const videoSb = videoTrack?.buffer;
|
||||
if (videoSb && sn !== 'initSegment' && offset !== undefined) {
|
||||
const audioSb = audioTrack?.buffer;
|
||||
const partOrFrag = part || (frag as MediaFragment);
|
||||
if (
|
||||
type === 'audio' &&
|
||||
parent !== 'main' &&
|
||||
!(videoTrack.ending || videoTrack.ended)
|
||||
!(videoTrack.ending || videoTrack.ended) &&
|
||||
audioSb &&
|
||||
BufferHelper.getBuffered(audioSb).length
|
||||
) {
|
||||
const pStart = partOrFrag.start;
|
||||
const pTime = pStart + partOrFrag.duration * 0.05;
|
||||
const vbuffered = videoSb.buffered;
|
||||
const vbuffered = BufferHelper.getBuffered(videoSb);
|
||||
const vappending = this.currentOp('video');
|
||||
if (!vbuffered.length && !vappending) {
|
||||
// wait for video before appending audio
|
||||
|
||||
@@ -1031,7 +1031,8 @@ export default class InterstitialsController
|
||||
private checkStart() {
|
||||
const schedule = this.schedule;
|
||||
const interstitialEvents = schedule?.events;
|
||||
if (!interstitialEvents || this.playbackDisabled || !this.media) {
|
||||
const hls = this.hls;
|
||||
if (!interstitialEvents || this.playbackDisabled || !this.media || !hls) {
|
||||
return;
|
||||
}
|
||||
// Check buffered to pre-roll
|
||||
@@ -1042,7 +1043,11 @@ export default class InterstitialsController
|
||||
const timelinePos = this.timelinePos;
|
||||
const effectivePlayingItem = this.effectivePlayingItem;
|
||||
if (timelinePos === -1) {
|
||||
const startPosition = this.hls.startPosition;
|
||||
if (!hls.loadingEnabled) {
|
||||
this.log(`waiting for startLoad( <startPosition> )`);
|
||||
return;
|
||||
}
|
||||
const startPosition = hls.startPosition;
|
||||
this.timelinePos = startPosition;
|
||||
if (interstitialEvents.length === 0) {
|
||||
this.setSchedulePosition(0);
|
||||
@@ -1056,7 +1061,7 @@ export default class InterstitialsController
|
||||
startPosition > 0 ? startPosition : 0);
|
||||
const index = schedule.findItemIndexAtTime(start);
|
||||
this.setSchedulePosition(index);
|
||||
} else if (this.hls.liveSyncPosition === 0) {
|
||||
} else if (hls.liveSyncPosition === 0) {
|
||||
this.setSchedulePosition(0);
|
||||
} else {
|
||||
this.log('[checkStart] waiting for live start');
|
||||
|
||||
@@ -1161,8 +1161,17 @@ describe('BufferController with attached media', function () {
|
||||
|
||||
it('blocks audio append when video buffer is empty', function () {
|
||||
const audioQueue = getAudioQueue();
|
||||
// Audio arrives with no video buffered - should be blocked
|
||||
// Audio arrives with no video buffered - first append is not blocked
|
||||
triggerAudioAppend(0, 2);
|
||||
// Queue should have block-audio op followed by the audio append
|
||||
expect(audioQueue.length).to.equal(1);
|
||||
expect(audioQueue[0].label).to.equal('append-audio');
|
||||
|
||||
audioQueue.length = 0;
|
||||
setSourceBufferBufferedRange(bufferController, 'audio', 0, 2);
|
||||
|
||||
// Morem audio arrives with no video buffered - blocked
|
||||
triggerAudioAppend(2, 4);
|
||||
|
||||
// Queue should have block-audio op followed by the audio append
|
||||
expect(audioQueue.length).to.equal(2);
|
||||
@@ -1185,6 +1194,7 @@ describe('BufferController with attached media', function () {
|
||||
it('blocks audio when audio is ahead of video', function () {
|
||||
// Video has buffered 0-5, but audio segment starts at 6 (ahead of video)
|
||||
setSourceBufferBufferedRange(bufferController, 'video', 0, 5);
|
||||
setSourceBufferBufferedRange(bufferController, 'audio', 0, 6);
|
||||
(bufferController as any).lastVideoAppendEnd = 5;
|
||||
|
||||
const audioQueue = getAudioQueue();
|
||||
@@ -1197,8 +1207,9 @@ describe('BufferController with attached media', function () {
|
||||
|
||||
it('unblocks audio when video append advances past blocked audio position', function () {
|
||||
const audioQueue = getAudioQueue();
|
||||
// Audio arrives first, gets blocked (no video buffered)
|
||||
triggerAudioAppend(0, 2);
|
||||
// Audio arrives first, second append gets blocked (no video buffered)
|
||||
setSourceBufferBufferedRange(bufferController, 'audio', 0, 1);
|
||||
triggerAudioAppend(1, 1);
|
||||
expect(audioQueue[0].label).to.equal('block-audio');
|
||||
|
||||
// Video append arrives — advances lastVideoAppendEnd past audio pTime
|
||||
@@ -1234,11 +1245,11 @@ describe('BufferController with attached media', function () {
|
||||
)?.buffer;
|
||||
const audioQueue = getAudioQueue();
|
||||
|
||||
// First audio triggers block (no video buffered)
|
||||
triggerAudioAppend(0, 2);
|
||||
expect(audioQueue[0].label).to.equal('block-audio');
|
||||
// First audio is appended
|
||||
setSourceBufferBufferedRange(bufferController, 'audio', 0, 2);
|
||||
|
||||
// Second audio also triggers its own block (guard removed)
|
||||
// Second audio also triggers block (guard removed)
|
||||
triggerAudioAppend(2, 2);
|
||||
triggerAudioAppend(2, 2);
|
||||
expect(audioQueue.length).to.equal(4);
|
||||
expect(audioQueue[0].label).to.equal('block-audio');
|
||||
@@ -1273,6 +1284,7 @@ describe('BufferController with attached media', function () {
|
||||
expect(audioQueue.length).to.equal(1);
|
||||
expect(audioQueue[0].label).to.equal('append-audio');
|
||||
expect(audioBuffer!.appendBuffer).to.have.been.calledOnce;
|
||||
setSourceBufferBufferedRange(bufferController, 'audio', 0, 2);
|
||||
|
||||
// While audio[0] is still in-flight (no updateend yet), second audio
|
||||
// arrives ahead of video — block-audio lands behind the in-flight append
|
||||
|
||||
@@ -167,6 +167,25 @@ describe('InterstitialsController', function () {
|
||||
}
|
||||
|
||||
function setLoadedLevelDetails(playlist: string) {
|
||||
const attrs = new AttrList({});
|
||||
const level = new Level({
|
||||
name: '',
|
||||
url: '',
|
||||
attrs,
|
||||
bitrate: 0,
|
||||
});
|
||||
|
||||
(hls.streamController as any).levels = [level];
|
||||
(hls.levelController as any)._levels[0] = level;
|
||||
|
||||
// onManifestLoaded with autoStartLoaded kicks off loading
|
||||
if (!hls.loadingEnabled) {
|
||||
hls.startLoad(-1);
|
||||
}
|
||||
|
||||
(hls.levelController as any).currentLevelIndex = 0;
|
||||
(hls.levelController as any).currentLevel = level;
|
||||
|
||||
const details = M3U8Parser.parseLevelPlaylist(
|
||||
playlist,
|
||||
'http://example.com/playlist.m3u8',
|
||||
@@ -178,18 +197,9 @@ describe('InterstitialsController', function () {
|
||||
const timeSinceLoadedStub = sinon.stub(details, 'age');
|
||||
timeSinceLoadedStub.get(() => 0);
|
||||
expect(details.playlistParsingError).to.equal(null);
|
||||
const attrs = new AttrList({});
|
||||
const level = new Level({
|
||||
name: '',
|
||||
url: '',
|
||||
attrs,
|
||||
bitrate: 0,
|
||||
});
|
||||
|
||||
level.details = details;
|
||||
(hls.streamController as any).levels = [level];
|
||||
(hls.levelController as any)._levels[0] = level;
|
||||
(hls.levelController as any).currentLevelIndex = 0;
|
||||
(hls.levelController as any).currentLevel = level;
|
||||
|
||||
hls.trigger(Events.LEVEL_LOADED, {
|
||||
details,
|
||||
levelInfo: level,
|
||||
@@ -297,6 +307,7 @@ fileSequence4.ts
|
||||
const eventsTriggered = getTriggerCalls();
|
||||
expect(eventsTriggered).to.deep.equal(
|
||||
[
|
||||
Events.LEVEL_SWITCHING,
|
||||
Events.LEVEL_LOADED,
|
||||
Events.LEVEL_UPDATED,
|
||||
Events.INTERSTITIALS_UPDATED,
|
||||
@@ -1145,6 +1156,7 @@ fileSequence5.mp4`;
|
||||
const callsWithPrerollBeforeAttach = getTriggerCalls();
|
||||
expect(callsWithPrerollBeforeAttach).to.deep.equal(
|
||||
[
|
||||
Events.LEVEL_SWITCHING,
|
||||
Events.LEVEL_LOADED,
|
||||
Events.LEVEL_UPDATED,
|
||||
Events.INTERSTITIALS_UPDATED,
|
||||
@@ -1215,6 +1227,7 @@ fileSequence3.mp4
|
||||
[
|
||||
Events.MEDIA_ATTACHING,
|
||||
Events.MEDIA_ATTACHED,
|
||||
Events.LEVEL_SWITCHING,
|
||||
Events.LEVEL_LOADED,
|
||||
Events.LEVEL_UPDATED,
|
||||
Events.INTERSTITIALS_UPDATED,
|
||||
@@ -1283,6 +1296,7 @@ fileSequence3.mp4
|
||||
[
|
||||
Events.MEDIA_ATTACHING,
|
||||
Events.MEDIA_ATTACHED,
|
||||
Events.LEVEL_SWITCHING,
|
||||
Events.LEVEL_LOADED,
|
||||
Events.LEVEL_UPDATED,
|
||||
Events.INTERSTITIALS_UPDATED,
|
||||
@@ -1357,6 +1371,7 @@ fileSequence6.mp4`;
|
||||
const eventsBeforeAttach = getTriggerCalls();
|
||||
expect(eventsBeforeAttach).to.deep.equal(
|
||||
[
|
||||
Events.LEVEL_SWITCHING,
|
||||
Events.LEVEL_LOADED,
|
||||
Events.LEVEL_UPDATED,
|
||||
Events.INTERSTITIALS_UPDATED,
|
||||
@@ -1489,6 +1504,7 @@ fileSequence6.mp4`;
|
||||
const eventsAfterPlaylist = getTriggerCalls();
|
||||
expect(eventsAfterPlaylist).to.deep.equal(
|
||||
[
|
||||
Events.LEVEL_SWITCHING,
|
||||
Events.LEVEL_LOADED,
|
||||
Events.LEVEL_UPDATED,
|
||||
Events.INTERSTITIALS_UPDATED,
|
||||
@@ -1820,6 +1836,7 @@ fileSequence6.mp4
|
||||
const eventsBeforeAttach = getTriggerCalls();
|
||||
expect(eventsBeforeAttach).to.deep.equal(
|
||||
[
|
||||
Events.LEVEL_SWITCHING,
|
||||
Events.LEVEL_LOADED,
|
||||
Events.LEVEL_UPDATED,
|
||||
Events.INTERSTITIALS_UPDATED,
|
||||
@@ -2139,6 +2156,7 @@ fileSequence3.mp4`;
|
||||
const expectedEvents = [
|
||||
Events.MEDIA_ATTACHING,
|
||||
Events.MEDIA_ATTACHED,
|
||||
Events.LEVEL_SWITCHING,
|
||||
Events.LEVEL_LOADED,
|
||||
Events.LEVEL_UPDATED,
|
||||
Events.INTERSTITIALS_BUFFERED_TO_BOUNDARY,
|
||||
@@ -2182,6 +2200,7 @@ media_w507366714_268.ts`;
|
||||
const expectedEvents = [
|
||||
Events.MEDIA_ATTACHING,
|
||||
Events.MEDIA_ATTACHED,
|
||||
Events.LEVEL_SWITCHING,
|
||||
Events.LEVEL_LOADED,
|
||||
Events.LEVEL_UPDATED,
|
||||
Events.INTERSTITIALS_UPDATED,
|
||||
|
||||
Reference in New Issue
Block a user