From fc4893d53818fec2af6f687076a25137160ab284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Velad=20Galv=C3=A1n?= Date: Tue, 24 Sep 2024 20:30:32 +0200 Subject: [PATCH] feat: Remove streaming.parsePrftBox config (#7358) The site code is changed to use a parser that is always used and thus we avoid having a configuration that is not necessary. --- demo/config.js | 2 - docs/tutorials/upgrade.md | 1 + externs/shaka/player.js | 9 --- lib/media/media_source_engine.js | 64 ++++++++++++++- lib/media/streaming_engine.js | 63 +-------------- lib/player.js | 9 +++ lib/util/player_configuration.js | 1 - test/cast/cast_utils_unit.js | 1 + test/media/drm_engine_integration.js | 1 + test/media/media_source_engine_integration.js | 1 + test/media/media_source_engine_unit.js | 3 + test/media/streaming_engine_integration.js | 1 + test/media/streaming_engine_unit.js | 77 ------------------- 13 files changed, 80 insertions(+), 153 deletions(-) diff --git a/demo/config.js b/demo/config.js index 54a4b5793c..17f91fa5f9 100644 --- a/demo/config.js +++ b/demo/config.js @@ -490,8 +490,6 @@ shakaDemo.Config = class { 'streaming.observeQualityChanges') .addNumberInput_('Max Variant Disabled Time', 'streaming.maxDisabledTime') - .addBoolInput_('Parse PRFT box', - 'streaming.parsePrftBox') .addNumberInput_('Segment Prefetch Limit', 'streaming.segmentPrefetchLimit', /* canBeDecimal= */ false, diff --git a/docs/tutorials/upgrade.md b/docs/tutorials/upgrade.md index 3682b854df..7ba8a76ccd 100644 --- a/docs/tutorials/upgrade.md +++ b/docs/tutorials/upgrade.md @@ -115,6 +115,7 @@ application: `targetLatencyTolerance`, `maxPlaybackRate`, `minPlaybackRate`, `panicMode` `panicThreshold`. (deprecated in v4.10.0) - `useSafariBehaviorForLive` has been removed. + - `parsePrftBox` has been removed. - Plugin changes: - `TextDisplayer` plugins must implement the `configure()` method. diff --git a/externs/shaka/player.js b/externs/shaka/player.js index 244c13b32c..167e00891e 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -1556,7 +1556,6 @@ shaka.extern.LiveSyncConfiguration; * dispatchAllEmsgBoxes: boolean, * observeQualityChanges: boolean, * maxDisabledTime: number, - * parsePrftBox: boolean, * segmentPrefetchLimit: number, * prefetchAudioLanguages: !Array, * disableAudioPrefetch: boolean, @@ -1744,14 +1743,6 @@ shaka.extern.LiveSyncConfiguration; * If all variants are disabled this way, NETWORK HTTP_ERROR will be thrown. *
* Defaults to 30. - * @property {boolean} parsePrftBox - * If true, will raise a shaka.extern.ProducerReferenceTime - * player event (event name 'prft'). - * The event will be raised only once per playback session as program - * start date will not change, and would save parsing the segment multiple - * times needlessly. - *
- * Defaults to false. * @property {number} segmentPrefetchLimit * The maximum number of segments for each active stream to be prefetched * ahead of playhead in parallel. diff --git a/lib/media/media_source_engine.js b/lib/media/media_source_engine.js index 607d1b9bef..8347dbe40e 100644 --- a/lib/media/media_source_engine.js +++ b/lib/media/media_source_engine.js @@ -22,6 +22,7 @@ goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.Destroyer'); goog.require('shaka.util.Error'); goog.require('shaka.util.EventManager'); +goog.require('shaka.util.FakeEvent'); goog.require('shaka.util.Functional'); goog.require('shaka.util.IDestroyable'); goog.require('shaka.util.Id3Utils'); @@ -166,6 +167,9 @@ shaka.media.MediaSourceEngine = class { /** @private {!shaka.util.PublicPromise.} */ this.audioCompensation_ = new shaka.util.PublicPromise(); + + /** @private {boolean} */ + this.parsedPrftEventRaised_ = false; } /** @@ -498,6 +502,12 @@ shaka.media.MediaSourceEngine = class { !this.ignoreManifestTimestampsInSegmentsMode_; this.tsParser_ = null; + this.firstVideoTimestamp_ = null; + this.firstVideoReferenceStartTime_ = null; + this.firstAudioTimestamp_ = null; + this.firstAudioReferenceStartTime_ = null; + this.audioCompensation_ = new shaka.util.PublicPromise(); + this.parsedPrftEventRaised_ = false; for (const contentType of streamsByType.keys()) { const stream = streamsByType.get(contentType); @@ -838,8 +848,11 @@ shaka.media.MediaSourceEngine = class { const Mp4Parser = shaka.util.Mp4Parser; let startTime = 0; let parsedMedia = false; - new Mp4Parser() - .box('moof', Mp4Parser.children) + const parser = new Mp4Parser(); + if (!this.parsedPrftEventRaised_) { + parser.fullBox('prft', (box) => this.parsePrft_(timescale, box)); + } + parser.box('moof', Mp4Parser.children) .box('traf', Mp4Parser.children) .fullBox('tfdt', (box) => { goog.asserts.assert( @@ -876,6 +889,48 @@ shaka.media.MediaSourceEngine = class { return timestamp; } + /** + * Parse PRFT box. + * @param {number} timescale + * @param {!shaka.extern.ParsedBox} box + * @private + */ + parsePrft_(timescale, box) { + goog.asserts.assert( + box.version == 0 || box.version == 1, + 'PRFT version can only be 0 or 1'); + const parsed = shaka.util.Mp4BoxParsers.parsePRFTInaccurate( + box.reader, box.version); + + const wallClockTime = this.convertNtp_(parsed.ntpTimestamp); + const programStartDate = new Date(wallClockTime - + (parsed.mediaTime / timescale) * 1000); + const prftInfo = { + wallClockTime, + programStartDate, + }; + + const eventName = shaka.util.FakeEvent.EventName.Prft; + const data = (new Map()).set('detail', prftInfo); + const event = new shaka.util.FakeEvent( + eventName, data); + this.playerInterface_.onEvent(event); + this.parsedPrftEventRaised_ = true; + } + + + /** + * Convert Ntp ntpTimeStamp to UTC Time + * + * @param {number} ntpTimeStamp + * @return {number} utcTime + * @private + */ + convertNtp_(ntpTimeStamp) { + const start = new Date(Date.UTC(1900, 0, 1, 0, 0, 0)); + return new Date(start.getTime() + ntpTimeStamp).getTime(); + } + /** * Enqueue an operation to append data to the SourceBuffer. * Start and end times are needed for TextEngine, but not for MediaSource. @@ -2234,7 +2289,8 @@ shaka.media.MediaSourceEngine.SourceBufferMode_ = { /** * @typedef {{ * getKeySystem: function():?string, - * onMetadata: function(!Array, number, ?number) + * onMetadata: function(!Array, number, ?number), + * onEvent: function(!Event) * }} * * @summary Player interface @@ -2243,5 +2299,7 @@ shaka.media.MediaSourceEngine.SourceBufferMode_ = { * @property {function( * !Array, number, ?number)} onMetadata * Callback to use when metadata arrives. + * @property {function(!Event)} onEvent + * Called when an event occurs that should be sent to the app. */ shaka.media.MediaSourceEngine.PlayerInterface; diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index 1a9e6b0be0..c6e2e18aaf 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -93,9 +93,6 @@ shaka.media.StreamingEngine = class { /** @private {number} */ this.textStreamSequenceId_ = 0; - /** @private {boolean} */ - this.parsedPrftEventRaised_ = false; - /** * Maps a content type, e.g., 'audio', 'video', or 'text', to a MediaState. * @@ -2254,8 +2251,6 @@ shaka.media.StreamingEngine = class { const hasEmsg = ((stream.emsgSchemeIdUris != null && stream.emsgSchemeIdUris.length > 0) || this.config_.dispatchAllEmsgBoxes); - const shouldParsePrftBox = - (this.config_.parsePrftBox && !this.parsedPrftEventRaised_); const isMP4 = stream.mimeType == 'video/mp4' || stream.mimeType == 'audio/mp4'; @@ -2268,7 +2263,7 @@ shaka.media.StreamingEngine = class { this.manifest_.type == shaka.media.ManifestParser.DASH && this.config_.shouldFixTimestampOffset; - if (hasEmsg || shouldParsePrftBox || shouldFixTimestampOffset) { + if (hasEmsg || shouldFixTimestampOffset) { parser = new shaka.util.Mp4Parser(); } @@ -2280,14 +2275,6 @@ shaka.media.StreamingEngine = class { reference, stream.emsgSchemeIdUris, box)); } - if (shouldParsePrftBox) { - parser - .fullBox( - 'prft', - (box) => this.parsePrft_( - reference, box)); - } - if (shouldFixTimestampOffset) { parser .box('moof', shaka.util.Mp4Parser.children) @@ -2326,7 +2313,7 @@ shaka.media.StreamingEngine = class { }); } - if (hasEmsg || shouldParsePrftBox || shouldFixTimestampOffset) { + if (hasEmsg || shouldFixTimestampOffset) { parser.parse(segment, /* partialOkay= */ false, isChunkedData); } @@ -2476,52 +2463,6 @@ shaka.media.StreamingEngine = class { } } - /** - * Parse PRFT box. - * @param {!shaka.media.SegmentReference} reference - * @param {!shaka.extern.ParsedBox} box - * @private - */ - parsePrft_(reference, box) { - if (this.parsedPrftEventRaised_ || - !reference.initSegmentReference.timescale) { - return; - } - goog.asserts.assert( - box.version == 0 || box.version == 1, - 'PRFT version can only be 0 or 1'); - const parsed = shaka.util.Mp4BoxParsers.parsePRFTInaccurate( - box.reader, box.version); - - const timescale = reference.initSegmentReference.timescale; - const wallClockTime = this.convertNtp(parsed.ntpTimestamp); - const programStartDate = new Date(wallClockTime - - (parsed.mediaTime / timescale) * 1000); - const prftInfo = { - wallClockTime, - programStartDate, - }; - - const eventName = shaka.util.FakeEvent.EventName.Prft; - const data = (new Map()).set('detail', prftInfo); - const event = new shaka.util.FakeEvent( - eventName, data); - this.playerInterface_.onEvent(event); - this.parsedPrftEventRaised_ = true; - } - - - /** - * Convert Ntp ntpTimeStamp to UTC Time - * - * @param {number} ntpTimeStamp - * @return {number} utcTime - */ - convertNtp(ntpTimeStamp) { - const start = new Date(Date.UTC(1900, 0, 1, 0, 0, 0)); - return new Date(start.getTime() + ntpTimeStamp).getTime(); - } - /** * Evicts media to meet the max buffer behind limit. * diff --git a/lib/player.js b/lib/player.js index 87e99ddfe8..17dbd02ff4 100644 --- a/lib/player.js +++ b/lib/player.js @@ -2398,6 +2398,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { onMetadata: (metadata, offset, endTime) => { this.processTimedMetadataMediaSrc_(metadata, offset, endTime); }, + onEvent: (event) => this.dispatchEvent(event), }, this.lcevcDec_); mediaSourceEngine.configure(this.config_.mediaSource); @@ -3945,6 +3946,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget { delete config['manifest']['hls']['useSafariBehaviorForLive']; } + // Deprecate 'streaming.parsePrftBox' configuration. + if (config['streaming'] && 'parsePrftBox' in config['streaming']) { + shaka.Deprecate.deprecateFeature(5, + 'streaming.parsePrftBox configuration', + 'Now fired without needing a configuration.'); + delete config['streaming']['parsePrftBox']; + } + // If lowLatencyMode is enabled, and inaccurateManifestTolerance and // rebufferingGoal and segmentPrefetchLimit and baseDelay and // autoCorrectDrift and maxDisabledTime are not specified, set diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index eb30b2ef65..f0f9690d5b 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -233,7 +233,6 @@ shaka.util.PlayerConfiguration = class { dispatchAllEmsgBoxes: false, observeQualityChanges: false, maxDisabledTime: 30, - parsePrftBox: false, // When low latency streaming is enabled, segmentPrefetchLimit will // default to 2 if not specified. segmentPrefetchLimit: 0, diff --git a/test/cast/cast_utils_unit.js b/test/cast/cast_utils_unit.js index b9f394a5b3..b3e0bc74b3 100644 --- a/test/cast/cast_utils_unit.js +++ b/test/cast/cast_utils_unit.js @@ -227,6 +227,7 @@ describe('CastUtils', () => { { getKeySystem: () => null, onMetadata: () => {}, + onEvent: () => {}, }); const config = shaka.util.PlayerConfiguration.createDefault().mediaSource; diff --git a/test/media/drm_engine_integration.js b/test/media/drm_engine_integration.js index 1f42f2be92..6162ae42af 100644 --- a/test/media/drm_engine_integration.js +++ b/test/media/drm_engine_integration.js @@ -118,6 +118,7 @@ describe('DrmEngine', () => { { getKeySystem: () => null, onMetadata: () => {}, + onEvent: () => {}, }); const mediaSourceConfig = shaka.util.PlayerConfiguration.createDefault().mediaSource; diff --git a/test/media/media_source_engine_integration.js b/test/media/media_source_engine_integration.js index c261ad0a18..1d3798d346 100644 --- a/test/media/media_source_engine_integration.js +++ b/test/media/media_source_engine_integration.js @@ -173,6 +173,7 @@ describe('MediaSourceEngine', () => { { getKeySystem: () => null, onMetadata: shaka.test.Util.spyFunc(onMetadata), + onEvent: () => {}, }); const config = shaka.util.PlayerConfiguration.createDefault().mediaSource; mediaSourceEngine.configure(config); diff --git a/test/media/media_source_engine_unit.js b/test/media/media_source_engine_unit.js index 7fdd817d57..8bda658d77 100644 --- a/test/media/media_source_engine_unit.js +++ b/test/media/media_source_engine_unit.js @@ -249,6 +249,7 @@ describe('MediaSourceEngine', () => { { getKeySystem: () => null, onMetadata: () => {}, + onEvent: () => {}, }); mediaSourceEngine.getCaptionParser = () => { return mockClosedCaptionParser; @@ -323,6 +324,7 @@ describe('MediaSourceEngine', () => { { getKeySystem: () => null, onMetadata: () => {}, + onEvent: () => {}, }); expect(createMediaSourceSpy).toHaveBeenCalled(); @@ -345,6 +347,7 @@ describe('MediaSourceEngine', () => { { getKeySystem: () => null, onMetadata: () => {}, + onEvent: () => {}, }); if (window.ManagedMediaSource) { diff --git a/test/media/streaming_engine_integration.js b/test/media/streaming_engine_integration.js index 29e8e38c65..9048cd6ab5 100644 --- a/test/media/streaming_engine_integration.js +++ b/test/media/streaming_engine_integration.js @@ -73,6 +73,7 @@ describe('StreamingEngine', () => { { getKeySystem: () => null, onMetadata: () => {}, + onEvent: () => {}, }); const mediaSourceConfig = shaka.util.PlayerConfiguration.createDefault().mediaSource; diff --git a/test/media/streaming_engine_unit.js b/test/media/streaming_engine_unit.js index d63be7d384..688c57c44e 100644 --- a/test/media/streaming_engine_unit.js +++ b/test/media/streaming_engine_unit.js @@ -3308,83 +3308,6 @@ describe('StreamingEngine', () => { }); }); - describe('embedded prft boxes', () => { - const prftSegment = Uint8ArrayUtils.fromHex( - '00000020707266740100000000000001E683B62E8E63CC580000001B319D5767'); - const mdhdSegment = Uint8ArrayUtils.fromHex( - '000000446D6F6F760000003C7472616B000000346D6469610000002C6D646864'+ - '0100000000000000DF22526500000000DF22526500989680FFFFFFFFFFFFFFFF'+ - '15C70000'); - - const prftEventObj = { - wallClockTime: 1658402734556, - startDate: new Date(1658391054904.7898), - }; - - beforeEach(() => { - setupVod(); - mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData); - const config = shaka.util.PlayerConfiguration.createDefault().streaming; - config.parsePrftBox = true; - createStreamingEngine(config); - }); - - it('raises an event for registered prft v1', async () => { - segmentData[ContentType.VIDEO].segments[0] = prftSegment; - segmentData[ContentType.VIDEO].initSegments[0] = mdhdSegment; - - streamingEngine.switchVariant(variant); - streamingEngine.switchTextStream(textStream); - await streamingEngine.start(); - playing = true; - await runTest(); - expect(onEvent).toHaveBeenCalled(); - - const event = onEvent.calls.argsFor(0)[0]; - expect(event.detail.wallClockTime).toBe(prftEventObj.wallClockTime); - expect(event.detail.programStartDate.getUTCMilliseconds()).toBe( - prftEventObj.startDate.getUTCMilliseconds()); - expect(event.detail.programStartDate.toUTCString()).toBe( - prftEventObj.startDate.toUTCString()); - }); - - it('raises an event for registered prft v0', async () => { - const prftSegment = Uint8ArrayUtils.fromHex( - '0000001C707266740000000000000001E683B62E8E63CC5819999999'); - const expectedStartDate = new Date(1658402691606.3271); - segmentData[ContentType.VIDEO].segments[0] = prftSegment; - segmentData[ContentType.VIDEO].initSegments[0] = mdhdSegment; - - streamingEngine.switchVariant(variant); - streamingEngine.switchTextStream(textStream); - await streamingEngine.start(); - playing = true; - await runTest(); - expect(onEvent).toHaveBeenCalled(); - - const event = onEvent.calls.argsFor(0)[0]; - expect(event.detail.wallClockTime).toBe(prftEventObj.wallClockTime); - expect(event.detail.programStartDate.getUTCMilliseconds()).toBe( - expectedStartDate.getUTCMilliseconds()); - expect(event.detail.programStartDate.toUTCString()).toBe( - expectedStartDate.toUTCString()); - }); - - it('raises an event once only', async () => { - segmentData[ContentType.VIDEO].segments[0] = - shaka.util.Uint8ArrayUtils.concat(prftSegment, prftSegment); - segmentData[ContentType.VIDEO].segments[1] = prftSegment; - segmentData[ContentType.VIDEO].initSegments[0] = mdhdSegment; - - streamingEngine.switchVariant(variant); - streamingEngine.switchTextStream(textStream); - await streamingEngine.start(); - playing = true; - await runTest(); - expect(onEvent).toHaveBeenCalledTimes(1); - }); - }); - describe('network downgrading', () => { /** @type {shaka.extern.Variant} */ let initialVariant;