diff --git a/build/types/core b/build/types/core index 23d04a5c8f..5989b765bf 100644 --- a/build/types/core +++ b/build/types/core @@ -114,6 +114,7 @@ +../../lib/util/public_promise.js +../../lib/util/state_history.js +../../lib/util/stats.js ++../../lib/util/segment_index.js +../../lib/util/stream_utils.js +../../lib/util/string_utils.js +../../lib/util/switch_history.js diff --git a/externs/shaka/manifest_parser.js b/externs/shaka/manifest_parser.js index 6eaa98d1e7..ff66b61dbf 100644 --- a/externs/shaka/manifest_parser.js +++ b/externs/shaka/manifest_parser.js @@ -120,6 +120,13 @@ shaka.extern.ManifestParser = class { * @exportDoc */ setMediaElement(mediaElement) {} + + /** + * provide CloseSegmentIndexRegister + * @param {shaka.util.CloseSegmentIndexRegister} register + * @exportDoc + */ + setCloseSegmentIndexRegister(register) {} }; diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index ba0ba0772a..412758b384 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -230,6 +230,18 @@ shaka.dash.DashParser = class { } } + /** + * @override + * @exportInterface + */ + setCloseSegmentIndexRegister(register) { + const parser = this; + const closeSegmentIndexes = () => { + parser.handleDeferredCloseSegmentIndexes(); + }; + register.add(closeSegmentIndexes); + } + /** * @override * @exportInterface @@ -1491,6 +1503,17 @@ shaka.dash.DashParser = class { } } + /** + * Handles deferred releases of old SegmentIndexes + * content type from a previous update. + * @export + */ + handleDeferredCloseSegmentIndexes() { + if (this.periodCombiner_) { + this.periodCombiner_.handleDeferredCloseSegmentIndexes(); + } + } + /** * Clean StreamMap Object to remove reference of deleted Stream Object * @private diff --git a/lib/dash/segment_template.js b/lib/dash/segment_template.js index 627937dffb..cee0518950 100644 --- a/lib/dash/segment_template.js +++ b/lib/dash/segment_template.js @@ -833,6 +833,17 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex { } } + /** + * @override + */ + evictAll() { + if (this.templateInfo_ && this.templateInfo_.timeline.length) { + this.numEvicted_ += this.templateInfo_.timeline.length; + this.templateInfo_.timeline = []; + } + this.references = []; + } + /** * Merge new template info * @param {shaka.dash.SegmentTemplate.SegmentTemplateInfo} info diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 3834cc560f..5681b410a5 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -311,6 +311,11 @@ shaka.hls.HlsParser = class { } } + /** @override */ + setCloseSegmentIndexRegister(register) { + // No-op + } + /** * @override * @exportInterface diff --git a/lib/media/segment_index.js b/lib/media/segment_index.js index 2142aeab28..c42b5bc426 100644 --- a/lib/media/segment_index.js +++ b/lib/media/segment_index.js @@ -308,6 +308,17 @@ shaka.media.SegmentIndex = class { this.numEvicted_ += diff; } + /** + * Removes all SegmentReferences that end before the given time. + * @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..90088a8771 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -34,7 +34,7 @@ goog.require('shaka.util.MimeUtils'); goog.require('shaka.util.Mp4BoxParsers'); goog.require('shaka.util.Mp4Parser'); goog.require('shaka.util.Networking'); - +goog.require('shaka.util.CloseSegmentIndexRegister'); /** * @summary Creates a Streaming Engine. @@ -148,6 +148,12 @@ shaka.media.StreamingEngine = class { /** @private {?shaka.media.StreamingEngine.MediaState_} */ this.lastTextMediaStateBeforeUnload_ = null; + + /** + * @private {shaka.util.CloseSegmentIndexRegister} + */ + this.closeSegmentIndexRegister_ = + new shaka.util.CloseSegmentIndexRegister(); } /** @override */ @@ -182,6 +188,7 @@ shaka.media.StreamingEngine = class { this.playerInterface_ = null; this.manifest_ = null; this.config_ = null; + this.closeSegmentIndexRegister_ = null; } /** @@ -269,7 +276,6 @@ shaka.media.StreamingEngine = class { } } - /** * Applies a playback range. This will only affect non-live content. * @@ -530,8 +536,15 @@ shaka.media.StreamingEngine = class { this.deferredCloseSegmentIndex_.delete(streamId); } } + this.closeSegmentIndexRegister_.closeSegmentIndexes(); } + /** + * @return {shaka.util.CloseSegmentIndexRegister} + */ + getCloseSegmentIndexRegister() { + return this.closeSegmentIndexRegister_; + } /** * Switches to the given Stream. |stream| may be from any Variant. diff --git a/lib/mss/mss_parser.js b/lib/mss/mss_parser.js index f8be5fc390..78aaa339c5 100644 --- a/lib/mss/mss_parser.js +++ b/lib/mss/mss_parser.js @@ -103,6 +103,12 @@ shaka.mss.MssParser = class { } } + /** + * @override + * @exportInterface + */ + setCloseSegmentIndexRegister(register) {} + /** * @override * @exportInterface diff --git a/lib/offline/offline_manifest_parser.js b/lib/offline/offline_manifest_parser.js index d318e9bc86..94958ad64e 100644 --- a/lib/offline/offline_manifest_parser.js +++ b/lib/offline/offline_manifest_parser.js @@ -31,6 +31,11 @@ shaka.offline.OfflineManifestParser = class { // No-op } + /** @override */ + setCloseSegmentIndexRegister(register) { + // No-op + } + /** @override */ async start(uriString, playerInterface) { /** @type {shaka.offline.OfflineUri} */ diff --git a/lib/player.js b/lib/player.js index 9544582b67..baed3e1c43 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); + this.parser_.setCloseSegmentIndexRegister( + this.streamingEngine_.getCloseSegmentIndexRegister()); // 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..71a974bfab 100644 --- a/lib/util/periods.js +++ b/lib/util/periods.js @@ -56,6 +56,15 @@ 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,7 +78,7 @@ shaka.util.PeriodCombiner = class { stream.segmentIndex.release(); } } - + this.handleDeferredCloseSegmentIndexes(); this.audioStreams_ = []; this.videoStreams_ = []; this.textStreams_ = []; @@ -110,6 +119,21 @@ shaka.util.PeriodCombiner = class { return this.imageStreams_; } + + /** + * Handles deferred releases of old SegmentIndexes + * content type from a previous update. + * @export + */ + handleDeferredCloseSegmentIndexes() { + for (const [key, value] of this.deferredCloseSegmentIndex_.entries()) { + const streamId = /** @type {number} */ (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,17 @@ shaka.util.PeriodCombiner = class { }); } } - if (stream.segmentIndex) { - stream.closeSegmentIndex(); + if (stream.segmentIndex && + !this.deferredCloseSegmentIndex_.has(stream.id)) { + const deferedCloseSegmentIndex = () => { + if (stream.segmentIndex) { + stream.segmentIndex.evictAll(); + stream.closeSegmentIndex(); + } + }; + this.deferredCloseSegmentIndex_.set(stream.id, deferedCloseSegmentIndex); } + this.usedPeriodIds_.delete(periodId); } diff --git a/lib/util/segment_index.js b/lib/util/segment_index.js new file mode 100644 index 0000000000..6a79a22b4d --- /dev/null +++ b/lib/util/segment_index.js @@ -0,0 +1,36 @@ +goog.provide('shaka.util.CloseSegmentIndexRegister'); + +/** + * @summary a register which provides an interface + * to close shaka.media.SegmentIndex + * @export + */ +shaka.util.CloseSegmentIndexRegister = class { + /** */ + constructor() { + /** @private {!Array.} */ + this.register_ = []; + } + + /** + * add function which closes shaka.media.SegmentIndex + * from the stream + * @param {Function} closeSegmentIndexFunction + */ + add(closeSegmentIndexFunction) { + if (closeSegmentIndexFunction) { + this.register_.push(closeSegmentIndexFunction); + } + } + + /** + * close shaka.media.SegmentIndex from register + */ + closeSegmentIndexes() { + for (const closeSegmentIndexFunction of this.register_) { + if (closeSegmentIndexFunction) { + closeSegmentIndexFunction(); + } + } + } +}; diff --git a/test/offline/storage_integration.js b/test/offline/storage_integration.js index a1ddad3381..4ec1b7e987 100644 --- a/test/offline/storage_integration.js +++ b/test/offline/storage_integration.js @@ -1734,6 +1734,11 @@ filterDescribe('Storage', storageSupport, () => { /** @override */ configure(params) {} + /** @override */ + setCloseSegmentIndexRegister(register) { + // No-op + } + /** @override */ start(uri, player) { return Promise.resolve(this.map_[uri]); diff --git a/test/test/util/simple_fakes.js b/test/test/util/simple_fakes.js index 46a12b1e33..cf7c845cb5 100644 --- a/test/test/util/simple_fakes.js +++ b/test/test/util/simple_fakes.js @@ -120,6 +120,10 @@ shaka.test.FakeStreamingEngine = class { jasmine.createSpy('switchTextStream').and.callFake((textStream) => { activeText = textStream; }); + + /** @type {!jasmine.Spy} */ + this.getCloseSegmentIndexRegister = + jasmine.createSpy('getCloseSegmentIndexRegister'); } }; @@ -149,6 +153,10 @@ shaka.test.FakeManifestParser = class { /** @type {!jasmine.Spy} */ this.onExpirationUpdated = jasmine.createSpy('onExpirationUpdated'); + + /** @type {!jasmine.Spy} */ + this.setCloseSegmentIndexRegister = + jasmine.createSpy('setCloseSegmentIndexRegister'); } }; diff --git a/test/test/util/test_scheme.js b/test/test/util/test_scheme.js index 572f8c2359..f0894649b4 100644 --- a/test/test/util/test_scheme.js +++ b/test/test/util/test_scheme.js @@ -754,6 +754,11 @@ shaka.test.TestScheme.ManifestParser = class { return new shaka.test.TestScheme.ManifestParser(); } + /** @override */ + setCloseSegmentIndexRegister(register) { + // No-op + } + /** @override */ start(uri, playerInterface) { const re = /^test:([^/]+)$/;