From 20116e84efd94f7cf0543aae91c13e1b9a056ee3 Mon Sep 17 00:00:00 2001 From: Nicolas Cocaign Date: Tue, 29 Oct 2024 12:02:49 +0100 Subject: [PATCH] fix(DASH): Fix playback of some DASH with multiple period (#7516) With this change, closeSegmentIndex() of all streams of a removed period are defered in StreamingEngine.onUpdate_() Fixes #7516 --- lib/dash/dash_parser.js | 10 ++ lib/media/segment_index.js | 13 +++ lib/media/streaming_engine.js | 22 ++++ lib/player.js | 2 + lib/util/periods.js | 43 +++++++- some-changes.patch | 188 ++++++++++++++++++++++++++++++++++ 6 files changed, 276 insertions(+), 2 deletions(-) create mode 100644 some-changes.patch diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index ba0ba0772a..a92b57062a 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -1491,6 +1491,16 @@ shaka.dash.DashParser = class { } } + /** + * Handles deferred releases of old SegmentIndexes + * content type from a previous update. + */ + handleDeferredCloseSegmentIndexes() { + if (this.periodCombiner_) { + this.periodCombiner_.handleDeferredCloseSegmentIndexes_(); + } + } + /** * Clean StreamMap Object to remove reference of deleted Stream Object * @private diff --git a/lib/media/segment_index.js b/lib/media/segment_index.js index 2142aeab28..9cf8d5acd2 100644 --- a/lib/media/segment_index.js +++ b/lib/media/segment_index.js @@ -308,6 +308,19 @@ shaka.media.SegmentIndex = class { this.numEvicted_ += diff; } + /** + * Removes all SegmentReferences that end before the given time. + * + * @param {number} time The time in seconds. + * @export + */ + evictAll() { + if (this.references.length) { + this.numEvicted_ += this.references.length; + this.references = []; + } + } + /** * Drops references that start after windowEnd, or end before windowStart, diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index 38056b0314..a8e82fe5b8 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -148,6 +148,13 @@ shaka.media.StreamingEngine = class { /** @private {?shaka.media.StreamingEngine.MediaState_} */ this.lastTextMediaStateBeforeUnload_ = null; + + /** + * Defered closedSegmentIndex of the periodCombiner. + * + * @private {function} + */ + this.periodCombinerDeferredCloseSegmentIndexesFunction_ = null; } /** @override */ @@ -182,6 +189,7 @@ shaka.media.StreamingEngine = class { this.playerInterface_ = null; this.manifest_ = null; this.config_ = null; + this.periodCombinerDeferredCloseSegmentIndexesFunction_ = null; } /** @@ -269,6 +277,14 @@ shaka.media.StreamingEngine = class { } } + /** + * Provide function for releases of old SegmentIndexes of the PeriodCombiner + * @param {function} extraDeferredCloseSegmentIndexes + */ + setExtraDeferredCloseSegmentIndexesFunction(deferredCloseSegmentIndexesFunction) + { + this.periodCombinerDeferredCloseSegmentIndexesFunction_ = deferredCloseSegmentIndexesFunction; + } /** * Applies a playback range. This will only affect non-live content. @@ -1255,6 +1271,12 @@ shaka.media.StreamingEngine = class { } return; } + + if (this.periodCombinerDeferredCloseSegmentIndexesFunction_) + { + this.periodCombinerDeferredCloseSegmentIndexesFunction_(); + } + } // Update the MediaState. diff --git a/lib/player.js b/lib/player.js index 9544582b67..166b38f5a4 100644 --- a/lib/player.js +++ b/lib/player.js @@ -2570,6 +2570,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.streamingEngine_ = this.createStreamingEngine(); this.streamingEngine_.configure(this.config_.streaming); + if (this.parser_.handleDeferredCloseSegmentIndexes) + this.streamingEngine_.setExtraDeferredCloseSegmentIndexesFunction(this.parser_.handleDeferredCloseSegmentIndexes) // Set the load mode to "loaded with media source" as late as possible so // that public methods won't try to access internal components until diff --git a/lib/util/periods.js b/lib/util/periods.js index 2055e944c8..24503ac779 100644 --- a/lib/util/periods.js +++ b/lib/util/periods.js @@ -56,6 +56,14 @@ shaka.util.PeriodCombiner = class { * @private {!Set.} */ this.usedPeriodIds_ = new Set(); + + /** + * Retains a reference to the function used to close SegmentIndex objects + * for streams which were switched away from during an ongoing update_() + * just after the period removal. + * @private {!Map.} + */ + this.deferredCloseSegmentIndex_ = new Map(); } /** @override */ @@ -69,6 +77,7 @@ shaka.util.PeriodCombiner = class { stream.segmentIndex.release(); } } + this.handleDeferredCloseSegmentIndexes_(); this.audioStreams_ = []; this.videoStreams_ = []; @@ -110,6 +119,21 @@ shaka.util.PeriodCombiner = class { return this.imageStreams_; } + + /** + * Handles deferred releases of old SegmentIndexes + * content type from a previous update. + * @private + */ + handleDeferredCloseSegmentIndexes_() { + for (const [key, value] of this.deferredCloseSegmentIndex_.entries()) { + const streamId = /** @type {string} */ (key); + const closeSegmentIndex = /** @type {!function()} */ (value); + closeSegmentIndex(); + this.deferredCloseSegmentIndex_.delete(streamId); + } + } + /** * Deletes a stream from matchedStreams because it is no longer needed * @@ -154,9 +178,24 @@ shaka.util.PeriodCombiner = class { }); } } - if (stream.segmentIndex) { - stream.closeSegmentIndex(); + + if (stream.segmentIndex && !this.deferredCloseSegmentIndex_.has(stream.id)) { + const originalcloseSegmentIndex = stream.closeSegmentIndex; + stream.closeSegmentIndex = () => { + if(stream.segmentIndex) + { + /** + * as the closure of the segmentIndex is defered + * update the number of evicted references in case or + * than 2 of more period are removed. + */ + stream.segmentIndex.evictAll(); + } + originalcloseSegmentIndex(); + } + this.deferredCloseSegmentIndex_.set(stream.id, stream.closeSegmentIndex); } + this.usedPeriodIds_.delete(periodId); } diff --git a/some-changes.patch b/some-changes.patch new file mode 100644 index 0000000000..7d65322e74 --- /dev/null +++ b/some-changes.patch @@ -0,0 +1,188 @@ +diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js +index ba0ba0772..a92b57062 100644 +--- a/lib/dash/dash_parser.js ++++ b/lib/dash/dash_parser.js +@@ -1491,6 +1491,16 @@ shaka.dash.DashParser = class { + } + } + ++ /** ++ * Handles deferred releases of old SegmentIndexes ++ * content type from a previous update. ++ */ ++ handleDeferredCloseSegmentIndexes() { ++ if (this.periodCombiner_) { ++ this.periodCombiner_.handleDeferredCloseSegmentIndexes_(); ++ } ++ } ++ + /** + * Clean StreamMap Object to remove reference of deleted Stream Object + * @private +diff --git a/lib/media/segment_index.js b/lib/media/segment_index.js +index 2142aeab2..9cf8d5acd 100644 +--- a/lib/media/segment_index.js ++++ b/lib/media/segment_index.js +@@ -308,6 +308,19 @@ shaka.media.SegmentIndex = class { + this.numEvicted_ += diff; + } + ++ /** ++ * Removes all SegmentReferences that end before the given time. ++ * ++ * @param {number} time The time in seconds. ++ * @export ++ */ ++ evictAll() { ++ if (this.references.length) { ++ this.numEvicted_ += this.references.length; ++ this.references = []; ++ } ++ } ++ + + /** + * Drops references that start after windowEnd, or end before windowStart, +diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js +index 38056b031..a8e82fe5b 100644 +--- a/lib/media/streaming_engine.js ++++ b/lib/media/streaming_engine.js +@@ -148,6 +148,13 @@ shaka.media.StreamingEngine = class { + + /** @private {?shaka.media.StreamingEngine.MediaState_} */ + this.lastTextMediaStateBeforeUnload_ = null; ++ ++ /** ++ * Defered closedSegmentIndex of the periodCombiner. ++ * ++ * @private {function} ++ */ ++ this.periodCombinerDeferredCloseSegmentIndexesFunction_ = null; + } + + /** @override */ +@@ -182,6 +189,7 @@ shaka.media.StreamingEngine = class { + this.playerInterface_ = null; + this.manifest_ = null; + this.config_ = null; ++ this.periodCombinerDeferredCloseSegmentIndexesFunction_ = null; + } + + /** +@@ -269,6 +277,14 @@ shaka.media.StreamingEngine = class { + } + } + ++ /** ++ * Provide function for releases of old SegmentIndexes of the PeriodCombiner ++ * @param {function} extraDeferredCloseSegmentIndexes ++ */ ++ setExtraDeferredCloseSegmentIndexesFunction(deferredCloseSegmentIndexesFunction) ++ { ++ this.periodCombinerDeferredCloseSegmentIndexesFunction_ = deferredCloseSegmentIndexesFunction; ++ } + + /** + * Applies a playback range. This will only affect non-live content. +@@ -1255,6 +1271,12 @@ shaka.media.StreamingEngine = class { + } + return; + } ++ ++ if (this.periodCombinerDeferredCloseSegmentIndexesFunction_) ++ { ++ this.periodCombinerDeferredCloseSegmentIndexesFunction_(); ++ } ++ + } + + // Update the MediaState. +diff --git a/lib/player.js b/lib/player.js +index 27d8042f3..3b0173a0c 100644 +--- a/lib/player.js ++++ b/lib/player.js +@@ -2570,6 +2570,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { + + this.streamingEngine_ = this.createStreamingEngine(); + this.streamingEngine_.configure(this.config_.streaming); ++ if (this.parser_.handleDeferredCloseSegmentIndexes) ++ this.streamingEngine_.setExtraDeferredCloseSegmentIndexesFunction(this.parser_.handleDeferredCloseSegmentIndexes) + + // Set the load mode to "loaded with media source" as late as possible so + // that public methods won't try to access internal components until +diff --git a/lib/util/periods.js b/lib/util/periods.js +index 2055e944c..24503ac77 100644 +--- a/lib/util/periods.js ++++ b/lib/util/periods.js +@@ -56,6 +56,14 @@ shaka.util.PeriodCombiner = class { + * @private {!Set.} + */ + this.usedPeriodIds_ = new Set(); ++ ++ /** ++ * Retains a reference to the function used to close SegmentIndex objects ++ * for streams which were switched away from during an ongoing update_() ++ * just after the period removal. ++ * @private {!Map.} ++ */ ++ this.deferredCloseSegmentIndex_ = new Map(); + } + + /** @override */ +@@ -69,6 +77,7 @@ shaka.util.PeriodCombiner = class { + stream.segmentIndex.release(); + } + } ++ this.handleDeferredCloseSegmentIndexes_(); + + this.audioStreams_ = []; + this.videoStreams_ = []; +@@ -110,6 +119,21 @@ shaka.util.PeriodCombiner = class { + return this.imageStreams_; + } + ++ ++ /** ++ * Handles deferred releases of old SegmentIndexes ++ * content type from a previous update. ++ * @private ++ */ ++ handleDeferredCloseSegmentIndexes_() { ++ for (const [key, value] of this.deferredCloseSegmentIndex_.entries()) { ++ const streamId = /** @type {string} */ (key); ++ const closeSegmentIndex = /** @type {!function()} */ (value); ++ closeSegmentIndex(); ++ this.deferredCloseSegmentIndex_.delete(streamId); ++ } ++ } ++ + /** + * Deletes a stream from matchedStreams because it is no longer needed + * +@@ -154,9 +178,24 @@ shaka.util.PeriodCombiner = class { + }); + } + } +- if (stream.segmentIndex) { +- stream.closeSegmentIndex(); ++ ++ if (stream.segmentIndex && !this.deferredCloseSegmentIndex_.has(stream.id)) { ++ const originalcloseSegmentIndex = stream.closeSegmentIndex; ++ stream.closeSegmentIndex = () => { ++ if(stream.segmentIndex) ++ { ++ /** ++ * as the closure of the segmentIndex is defered ++ * update the number of evicted references in case or ++ * than 2 of more period are removed. ++ */ ++ stream.segmentIndex.evictAll(); ++ } ++ originalcloseSegmentIndex(); ++ } ++ this.deferredCloseSegmentIndex_.set(stream.id, stream.closeSegmentIndex); + } ++ + this.usedPeriodIds_.delete(periodId); + } +