From 96f0f3fea63fce6624a4da4ca5a6d153c0e2f26a Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Tue, 4 Feb 2020 17:44:02 -0500 Subject: [PATCH] feat: support suggestedPresentationDelay in DASH manifests This is a port of #698 against master. --- src/master-playlist-controller.js | 5 +- src/playlist.js | 39 +++++-- test/master-playlist-controller.test.js | 2 + test/playlist.test.js | 142 ++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 10 deletions(-) diff --git a/src/master-playlist-controller.js b/src/master-playlist-controller.js index aea19ef50..8de1c4196 100644 --- a/src/master-playlist-controller.js +++ b/src/master-playlist-controller.js @@ -1004,7 +1004,8 @@ export class MasterPlaylistController extends videojs.EventTarget { return; } - const mainSeekable = Hls.Playlist.seekable(media, expired); + const suggestedPresentationDelay = this.masterPlaylistLoader_.master.suggestedPresentationDelay; + const mainSeekable = Hls.Playlist.seekable(media, expired, suggestedPresentationDelay); if (mainSeekable.length === 0) { return; @@ -1018,7 +1019,7 @@ export class MasterPlaylistController extends videojs.EventTarget { return; } - audioSeekable = Hls.Playlist.seekable(media, expired); + audioSeekable = Hls.Playlist.seekable(media, expired, suggestedPresentationDelay); if (audioSeekable.length === 0) { return; diff --git a/src/playlist.js b/src/playlist.js index 43c963630..892b1aa6a 100644 --- a/src/playlist.js +++ b/src/playlist.js @@ -217,21 +217,36 @@ export const sumDurations = function(playlist, startIndex, endIndex) { * window which is the duration of the last segment plus 2 target durations from the end * of the playlist. * + * A liveEdgePadding can be provided which will be used instead of calculating the safe live edge. + * This corresponds to suggestedPresentationDelay in DASH manifests. + * * @param {Object} playlist * a media playlist object + * @param {number} [liveEdgePadding] + * A number in seconds indicating how far from the end we want to be. + * If provided, this value is used instead of calculating the safe live index from the target durations. + * Corresponds to suggestedPresentationDelay in DASH manifests. * @return {number} * The media index of the segment at the safe live point. 0 if there is no "safe" * point. * @function safeLiveIndex */ -export const safeLiveIndex = function(playlist) { +export const safeLiveIndex = function(playlist, liveEdgePadding) { if (!playlist.segments.length) { return 0; } - let i = playlist.segments.length - 1; - let distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration; - const safeDistance = distanceFromEnd + playlist.targetDuration * 2; + let i = playlist.segments.length; + const lastSegmentDuration = playlist.segments[i - 1].duration || playlist.targetDuration; + const safeDistance = typeof liveEdgePadding === 'number' ? + liveEdgePadding : + lastSegmentDuration + playlist.targetDuration * 2; + + if (safeDistance === 0) { + return i; + } + + let distanceFromEnd = 0; while (i--) { distanceFromEnd += playlist.segments[i].duration; @@ -254,10 +269,16 @@ export const safeLiveIndex = function(playlist) { * playlist end calculation should consider the safe live end * (truncate the playlist end by three segments). This is normally * used for calculating the end of the playlist's seekable range. + * This takes into account the value of liveEdgePadding. + * Setting liveEdgePadding to 0 is equivalent to setting this to false. + * @param {number} liveEdgePadding a number indicating how far from the end of the playlist we should be in seconds. + * If this is provided, it is used in the safe live end calculation. + * Setting useSafeLiveEnd=false or liveEdgePadding=0 are equivalent. + * Corresponds to suggestedPresentationDelay in DASH manifests. * @return {number} the end time of playlist * @function playlistEnd */ -export const playlistEnd = function(playlist, expired, useSafeLiveEnd) { +export const playlistEnd = function(playlist, expired, useSafeLiveEnd, liveEdgePadding) { if (!playlist || !playlist.segments) { return null; } @@ -271,7 +292,7 @@ export const playlistEnd = function(playlist, expired, useSafeLiveEnd) { expired = expired || 0; - const endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length; + const endSequence = useSafeLiveEnd ? safeLiveIndex(playlist, liveEdgePadding) : playlist.segments.length; return intervalDuration( playlist, @@ -292,13 +313,15 @@ export const playlistEnd = function(playlist, expired, useSafeLiveEnd) { * dropped off the front of the playlist in a live scenario * @param {number=} expired the amount of time that has * dropped off the front of the playlist in a live scenario + * @param {number} liveEdgePadding how far from the end of the playlist we should be in seconds. + * Corresponds to suggestedPresentationDelay in DASH manifests. * @return {TimeRanges} the periods of time that are valid targets * for seeking */ -export const seekable = function(playlist, expired) { +export const seekable = function(playlist, expired, liveEdgePadding) { const useSafeLiveEnd = true; const seekableStart = expired || 0; - const seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd); + const seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding); if (seekableEnd === null) { return createTimeRange(); diff --git a/test/master-playlist-controller.test.js b/test/master-playlist-controller.test.js index da8f67558..e0fcb95d8 100644 --- a/test/master-playlist-controller.test.js +++ b/test/master-playlist-controller.test.js @@ -2111,6 +2111,7 @@ QUnit.test( let mainTimeRanges = []; let audioTimeRanges = []; + this.masterPlaylistController.masterPlaylistLoader_.master = {}; this.masterPlaylistController.masterPlaylistLoader_.media = () => mainMedia; this.masterPlaylistController.syncController_.getExpiredTime = () => 0; @@ -2243,6 +2244,7 @@ QUnit.test( Playlist.seekable = () => { return videojs.createTimeRanges(mainTimeRanges); }; + this.masterPlaylistController.masterPlaylistLoader_.master = {}; this.masterPlaylistController.masterPlaylistLoader_.media = () => media; this.masterPlaylistController.syncController_.getExpiredTime = () => 0; diff --git a/test/playlist.test.js b/test/playlist.test.js index cd968d632..874895a9b 100644 --- a/test/playlist.test.js +++ b/test/playlist.test.js @@ -513,6 +513,148 @@ QUnit.test('safeLiveIndex is 0 when no safe live point', function(assert) { ); }); +QUnit.test('safeLiveIndex accounts for liveEdgePadding in simple case', function(assert) { + const playlist = { + targetDuration: 6, + mediaSequence: 10, + syncInfo: { + time: 0, + mediaSequence: 10 + }, + segments: [ + { + duration: 6 + }, + { + duration: 6 + }, + { + duration: 6 + }, + { + duration: 6 + }, + { + duration: 6 + }, + { + duration: 6 + } + ] + }; + + assert.equal( + Playlist.safeLiveIndex(playlist, 36), 0, + 'returns 0 when liveEdgePadding is 30 and duration is 6' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 30), 1, + 'returns 1 when liveEdgePadding is 30 and duration is 6' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 24), 2, + 'returns 2 when liveEdgePadding is 24 and duration is 6' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 18), 3, + 'returns 3 when liveEdgePadding is 18 and duration is 6' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 12), 4, + 'returns 4 when liveEdgePadding is 12 and duration is 6' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 6), 5, + 'returns 5 when liveEdgePadding is 6 and duration is 6' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 0), 6, + 'returns 6 when liveEdgePadding is 0 and duration is 6' + ); +}); + +QUnit.test('safeLiveIndex accounts for liveEdgePadding in non-simple case', function(assert) { + const playlist = { + targetDuration: 6, + mediaSequence: 10, + syncInfo: { + time: 0, + mediaSequence: 10 + }, + segments: [ + { + duration: 3 + }, + { + duration: 6 + }, + { + duration: 6 + }, + { + duration: 3 + }, + { + duration: 3 + }, + { + duration: 0.5 + } + ] + }; + + assert.equal( + Playlist.safeLiveIndex(playlist, 24), 0, + 'returns 0 when liveEdgePadding is 24' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 18), 1, + 'returns 1 when liveEdgePadding is 18' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 12), 2, + 'returns 2 when liveEdgePadding is 12' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 6), 3, + 'returns 3 when liveEdgePadding is 6' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 4), 3, + 'returns 3 when liveEdgePadding is 4' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 1), 4, + 'returns 4 when liveEdgePadding is 1' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 0.5), 5, + 'returns 5 when liveEdgePadding is 0.5' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 0.25), 5, + 'returns 5 when liveEdgePadding is 0.25' + ); + + assert.equal( + Playlist.safeLiveIndex(playlist, 0), 6, + 'returns 6 when liveEdgePadding is 0' + ); +}); + QUnit.test( 'seekable end and playlist end account for non-zero starting VOD media sequence', function(assert) {