From 25b0d0542e106fb6c22be55afbe2e94a4bdccd1b Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Thu, 2 Jul 2020 13:29:28 +0200 Subject: [PATCH 1/9] PlaybackController: re-set seek time in seeking handler since for some browsers/devices (in case of live streams), setting current time on video element fails when there is no buffered data at requested time --- src/streaming/controllers/PlaybackController.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/streaming/controllers/PlaybackController.js b/src/streaming/controllers/PlaybackController.js index b7d70a39e5..a5f50f97b7 100644 --- a/src/streaming/controllers/PlaybackController.js +++ b/src/streaming/controllers/PlaybackController.js @@ -59,6 +59,7 @@ function PlaybackController() { playOnceInitialized, lastLivePlaybackTime, availabilityStartTime, + seekTarget, isLowLatencySeekingInProgress, playbackStalled, minPlaybackRateChange, @@ -188,6 +189,7 @@ function PlaybackController() { videoModel.setCurrentTime(time, stickToBuffered); } } else { + seekTarget = time; eventBus.trigger(Events.PLAYBACK_SEEK_ASKED); logger.info('Requesting seek to time: ' + time); videoModel.setCurrentTime(time, stickToBuffered); @@ -322,6 +324,7 @@ function PlaybackController() { streamSeekTime = NaN; liveDelay = 0; availabilityStartTime = 0; + seekTarget = NaN; if (videoModel) { eventBus.off(Events.STREAM_INITIALIZED, onStreamInitialized, this); eventBus.off(Events.DATA_UPDATE_COMPLETED, onDataUpdateCompleted, this); @@ -486,7 +489,15 @@ function PlaybackController() { } function onPlaybackSeeking() { - const seekTime = getTime(); + let seekTime = getTime(); + + // On some browsers/devices, in case of live streams, setting current time on video element fails when there is no buffered data at requested time + // Then re-set seek target time and video element will be seeked afterwhile once data is buffered (see BufferContoller) + if (!isNaN(seekTarget) && seekTarget !== seekTime) { + seekTime = seekTarget; + } + seekTarget = NaN; + logger.info('Seeking to: ' + seekTime); startUpdatingWallclockTime(); eventBus.trigger(Events.PLAYBACK_SEEKING, { From 1ff41ecbafbb5bf1266c89109f80459432c00d6f Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Thu, 2 Jul 2020 13:31:37 +0200 Subject: [PATCH 2/9] BufferController: fix seeking for live streams for browser/devices on which seek can't be done at unavalaible data range --- src/streaming/controllers/BufferController.js | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/streaming/controllers/BufferController.js b/src/streaming/controllers/BufferController.js index ebd3bc4a26..3ce35cfb54 100644 --- a/src/streaming/controllers/BufferController.js +++ b/src/streaming/controllers/BufferController.js @@ -241,7 +241,7 @@ function BufferController(config) { function showBufferRanges(ranges) { if (ranges && ranges.length > 0) { for (let i = 0, len = ranges.length; i < len; i++) { - logger.debug('Buffered Range', ranges.start(i), ' - ', ranges.end(i), ' currentTime = ', playbackController.getTime()); + logger.debug('Buffered range: ' + ranges.start(i) + ' - ' + ranges.end(i) + ', currentTime = ', playbackController.getTime()); } } } @@ -281,19 +281,7 @@ function BufferController(config) { if (appendedBytesInfo.segmentType === HTTPRequest.MEDIA_SEGMENT_TYPE) { showBufferRanges(ranges); onPlaybackProgression(); - - // If seeking, seek video model to range start in case appended segment starts beyond seek target - if (!isNaN(seekTarget) && - (playbackController.getTime() === 0 || playbackController.getTime() < ranges.start(0))) { - playbackController.seek(ranges.start(0), true, true); - seekTarget = NaN; - } - } else { - if (replacingBuffer) { - const currentTime = playbackController.getTime(); - logger.debug('AppendToBuffer seek target should be ' + currentTime); - triggerEvent(Events.SEEK_TARGET, {time: currentTime}); - } + adjustSeekTarget(); } if (appendedBytesInfo) { @@ -306,6 +294,26 @@ function BufferController(config) { } } + function adjustSeekTarget () { + if (isNaN(seekTarget)) return; + + const segmentDuration = representationController.getCurrentRepresentation().segmentDuration; + const range = getRangeAt(seekTarget, segmentDuration); + if (!range) return; + + const currentTime = playbackController.getTime(); + if (Math.abs(currentTime - range.start) > segmentDuration) { + // If current video model time is decorrelated from seek target / appended buffer then seek video element to seek target + // (in case of live streams on some browsers/devices for which we can't set video element time at unavalaible range) + playbackController.seek(seekTarget, false, true); + seekTarget = NaN; + } else if (currentTime < range.start) { + // If appended buffer starts after seek target (segments timeline/template tolerance) then seek to range start + playbackController.seek(range.start, false, true); + seekTarget = NaN; + } + } + function onQualityChanged(e) { if (e.streamInfo.id != streamInfo.id || e.mediaType !== type || requiredQuality === e.newQuality) return; @@ -653,9 +661,6 @@ function BufferController(config) { if (currentTime < range.end) { isBufferingCompleted = false; maxAppendedIndex = 0; - if (!replacingBuffer) { - triggerEvent(Events.SEEK_TARGET, {time: currentTime}); - } } buffer.remove(range.start, range.end, range.force); @@ -682,7 +687,6 @@ function BufferController(config) { clearNextRange(); } else { if (!replacingBuffer) { - logger.debug('onRemoved : call updateBufferLevel'); updateBufferLevel(); } else { replacingBuffer = false; From ed34ada33a6e7ac002d464d6df77cfff63b5c79a Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Thu, 2 Jul 2020 13:32:26 +0200 Subject: [PATCH 3/9] update code comments --- src/streaming/controllers/ScheduleController.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/streaming/controllers/ScheduleController.js b/src/streaming/controllers/ScheduleController.js index 5fd3eded20..5108e82000 100644 --- a/src/streaming/controllers/ScheduleController.js +++ b/src/streaming/controllers/ScheduleController.js @@ -458,11 +458,12 @@ function ScheduleController(config) { latency: latency }); - //if, during the seek command, the scheduleController is waiting : stop waiting, request chunk as soon as possible if (!isFragmentProcessingInProgress) { + // Restart scheduler if in pending state startScheduleTimer(0); } else { - logger.debug('onPlaybackSeeking, call fragmentModel.abortRequests in order to seek quicker'); + // Abort current requests + logger.debug('Abort requests'); fragmentModel.abortRequests(); } } From 5b40a3e6b7ca0f34d919c48db5fbe9220c5ba8da Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Fri, 3 Jul 2020 09:13:56 +0200 Subject: [PATCH 4/9] Avoid seek if seeking at current time or current seeking target --- .../controllers/PlaybackController.js | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/streaming/controllers/PlaybackController.js b/src/streaming/controllers/PlaybackController.js index a5f50f97b7..c24f7e04ff 100644 --- a/src/streaming/controllers/PlaybackController.js +++ b/src/streaming/controllers/PlaybackController.js @@ -179,21 +179,22 @@ function PlaybackController() { } function seek(time, stickToBuffered, internalSeek) { - if (streamInfo && videoModel) { - if (internalSeek === true) { - if (time !== videoModel.getTime()) { - // Internal seek = seek video model only (disable 'seeking' listener), - // buffer(s) are already appended at given time (see onBytesAppended()) - videoModel.removeEventListener('seeking', onPlaybackSeeking); - logger.info('Requesting internal seek to time: ' + time); - videoModel.setCurrentTime(time, stickToBuffered); - } - } else { - seekTarget = time; - eventBus.trigger(Events.PLAYBACK_SEEK_ASKED); - logger.info('Requesting seek to time: ' + time); - videoModel.setCurrentTime(time, stickToBuffered); - } + if (!streamInfo || !videoModel) return; + + let currentTime = !isNaN(seekTarget) ? seekTarget : videoModel.getTime(); + if (time === currentTime) return; + + if (internalSeek === true) { + // Internal seek = seek video model only (disable 'seeking' listener) + // buffer(s) are already appended at requested time + videoModel.removeEventListener('seeking', onPlaybackSeeking); + logger.info('Requesting internal seek to time: ' + time); + videoModel.setCurrentTime(time, stickToBuffered); + } else { + seekTarget = time; + eventBus.trigger(Events.PLAYBACK_SEEK_ASKED); + logger.info('Requesting seek to time: ' + time); + videoModel.setCurrentTime(time, stickToBuffered); } } From cb51774102599e9e65897cd6b4ce5a009c1948b8 Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Fri, 3 Jul 2020 16:09:51 +0200 Subject: [PATCH 5/9] revert code removed --- src/streaming/controllers/BufferController.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/streaming/controllers/BufferController.js b/src/streaming/controllers/BufferController.js index 3ce35cfb54..c8bd107ddc 100644 --- a/src/streaming/controllers/BufferController.js +++ b/src/streaming/controllers/BufferController.js @@ -282,6 +282,12 @@ function BufferController(config) { showBufferRanges(ranges); onPlaybackProgression(); adjustSeekTarget(); + } else if (replacingBuffer) { + // When replacing buffer due to switch track, and once new initialization segment has been appended + // (and previous buffered data removed) then seek stream to current time + const currentTime = playbackController.getTime(); + logger.debug('AppendToBuffer seek target should be ' + currentTime); + triggerEvent(Events.SEEK_TARGET, {time: currentTime}); } if (appendedBytesInfo) { From 9230a2ceec64a637a190666ba0abcd26620f8f34 Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Tue, 7 Jul 2020 08:55:26 +0200 Subject: [PATCH 6/9] BufferController: fix seeking for live streams for browser/devices on which seek can't be done at unavalaible data range --- src/streaming/controllers/BufferController.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/streaming/controllers/BufferController.js b/src/streaming/controllers/BufferController.js index c8bd107ddc..1a91689a46 100644 --- a/src/streaming/controllers/BufferController.js +++ b/src/streaming/controllers/BufferController.js @@ -301,10 +301,18 @@ function BufferController(config) { } function adjustSeekTarget () { + // Check buffered data only for audio and video + if (type !== Constants.AUDIO && type !== Constants.VIDEO) return; if (isNaN(seekTarget)) return; const segmentDuration = representationController.getCurrentRepresentation().segmentDuration; - const range = getRangeAt(seekTarget, segmentDuration); + + // Check if current buffered range already contains seek target + let range = getRangeAt(seekTarget, 0); + if (range) return; + + // Get range corresponding to to seek target + range = getRangeAt(seekTarget, segmentDuration); if (!range) return; const currentTime = playbackController.getTime(); @@ -455,7 +463,7 @@ function BufferController(config) { let len, i; - const toler = (tolerance || 0.15); + const toler = !isNaN(tolerance) ? tolerance : 0.15; if (ranges !== null && ranges !== undefined) { for (i = 0, len = ranges.length; i < len; i++) { From 397818196187d926e085c54d40846e59ad20d5ea Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Tue, 7 Jul 2020 08:56:24 +0200 Subject: [PATCH 7/9] remove logs --- src/streaming/StreamProcessor.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/streaming/StreamProcessor.js b/src/streaming/StreamProcessor.js index ea36bd25ed..5b8d25b681 100644 --- a/src/streaming/StreamProcessor.js +++ b/src/streaming/StreamProcessor.js @@ -229,7 +229,6 @@ function StreamProcessor(config) { function onDataUpdateCompleted(e) { if (e.sender.getType() !== getType() || e.sender.getStreamId() !== streamInfo.id) return; - logger.info('DataUpdateCompleted'); if (!e.error) { // Update representation if no error From 66a52c53f8d304a06c53517129165fc8e28c122f Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Wed, 8 Jul 2020 09:14:04 +0200 Subject: [PATCH 8/9] BufferController: update adjustSeekTarget() --- src/streaming/controllers/BufferController.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/streaming/controllers/BufferController.js b/src/streaming/controllers/BufferController.js index 1a91689a46..b3790e26b7 100644 --- a/src/streaming/controllers/BufferController.js +++ b/src/streaming/controllers/BufferController.js @@ -306,20 +306,20 @@ function BufferController(config) { if (isNaN(seekTarget)) return; const segmentDuration = representationController.getCurrentRepresentation().segmentDuration; + const currentTime = playbackController.getTime(); - // Check if current buffered range already contains seek target + // Check if current buffered range already contains seek target (and current video element time) let range = getRangeAt(seekTarget, 0); - if (range) return; + if (currentTime === seekTarget && range) return; - // Get range corresponding to to seek target + // Get buffered range corresponding to the seek target range = getRangeAt(seekTarget, segmentDuration); if (!range) return; - const currentTime = playbackController.getTime(); - if (Math.abs(currentTime - range.start) > segmentDuration) { - // If current video model time is decorrelated from seek target / appended buffer then seek video element to seek target + if (Math.abs(currentTime - seekTarget) > segmentDuration) { + // If current video model time is decorrelated from seek target (and appended buffer) then seek video element to seek target // (in case of live streams on some browsers/devices for which we can't set video element time at unavalaible range) - playbackController.seek(seekTarget, false, true); + playbackController.seek(Math.max(seekTarget, range.start), false, true); seekTarget = NaN; } else if (currentTime < range.start) { // If appended buffer starts after seek target (segments timeline/template tolerance) then seek to range start From d56dc8ae0ecbc72ed38573c46925e0baa3063df0 Mon Sep 17 00:00:00 2001 From: Bertrand Berthelot Date: Wed, 15 Jul 2020 08:25:13 +0200 Subject: [PATCH 9/9] Check if appended segment is not anterior to seek target --- src/streaming/controllers/BufferController.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/streaming/controllers/BufferController.js b/src/streaming/controllers/BufferController.js index b3790e26b7..874be66831 100644 --- a/src/streaming/controllers/BufferController.js +++ b/src/streaming/controllers/BufferController.js @@ -305,22 +305,26 @@ function BufferController(config) { if (type !== Constants.AUDIO && type !== Constants.VIDEO) return; if (isNaN(seekTarget)) return; - const segmentDuration = representationController.getCurrentRepresentation().segmentDuration; - const currentTime = playbackController.getTime(); - // Check if current buffered range already contains seek target (and current video element time) + const currentTime = playbackController.getTime(); let range = getRangeAt(seekTarget, 0); if (currentTime === seekTarget && range) return; // Get buffered range corresponding to the seek target + const segmentDuration = representationController.getCurrentRepresentation().segmentDuration; range = getRangeAt(seekTarget, segmentDuration); if (!range) return; if (Math.abs(currentTime - seekTarget) > segmentDuration) { - // If current video model time is decorrelated from seek target (and appended buffer) then seek video element to seek target + // If current video model time is decorrelated from seek target (and appended buffer) then seek video element // (in case of live streams on some browsers/devices for which we can't set video element time at unavalaible range) - playbackController.seek(Math.max(seekTarget, range.start), false, true); - seekTarget = NaN; + + // Check if appended segment is not anterior from seek target (segments timeline/template tolerance) + if (seekTarget <= range.end) { + // Seek video element to seek target or range start if appended buffer starts after seek target (segments timeline/template tolerance) + playbackController.seek(Math.max(seekTarget, range.start), false, true); + seekTarget = NaN; + } } else if (currentTime < range.start) { // If appended buffer starts after seek target (segments timeline/template tolerance) then seek to range start playbackController.seek(range.start, false, true);