diff --git a/demo/common/message_ids.js b/demo/common/message_ids.js index 99de27b8a2..1bc3e904e2 100644 --- a/demo/common/message_ids.js +++ b/demo/common/message_ids.js @@ -201,6 +201,7 @@ shakaDemo.MessageIds = { FAST_HALF_LIFE: 'DEMO_FAST_HALF_LIFE', FORCE_HTTPS: 'DEMO_FORCE_HTTPS', FORCE_TRANSMUX: 'DEMO_FORCE_TRANSMUX', + INSERT_FAKE_ENCRYPTION_IN_INIT: 'DEMO_INSERT_FAKE_ENCRYPTION_IN_INIT', FUZZ_FACTOR: 'DEMO_FUZZ_FACTOR', GAP_DETECTION_THRESHOLD: 'DEMO_GAP_DETECTION_THRESHOLD', GAP_JUMP_TIMER_TIME: 'DEMO_GAP_JUMP_TIMER_TIME', diff --git a/demo/config.js b/demo/config.js index 568dd5e0a2..d0aa4a2e35 100644 --- a/demo/config.js +++ b/demo/config.js @@ -512,7 +512,9 @@ shakaDemo.Config = class { .addTextInput_(MessageIds.SOURCE_BUFFER_EXTRA_FEATURES, 'mediaSource.sourceBufferExtraFeatures') .addBoolInput_(MessageIds.FORCE_TRANSMUX, - 'mediaSource.forceTransmux'); + 'mediaSource.forceTransmux') + .addBoolInput_(MessageIds.INSERT_FAKE_ENCRYPTION_IN_INIT, + 'mediaSource.insertFakeEncryptionInInit'); } /** @private */ diff --git a/demo/locales/en.json b/demo/locales/en.json index 13c9bdb287..caa5329808 100644 --- a/demo/locales/en.json +++ b/demo/locales/en.json @@ -88,6 +88,7 @@ "DEMO_FAST_HALF_LIFE": "Fast half life", "DEMO_FORCE_HTTPS": "Force HTTPS", "DEMO_FORCE_TRANSMUX": "Force Transmux", + "DEMO_INSERT_FAKE_ENCRYPTION_IN_INIT": "Insert fake encryption in init segments when needed by the platform.", "DEMO_FRONT_INTRO_DISMISS": "Dismiss", "DEMO_FRONT_INTRO_ONE": "This is a demo of Google's Shaka Player, a JavaScript library for adaptive video streaming.", "DEMO_FRONT_INTRO_TWO": "Choose a video to playback; more assets are available via the \"all content\" tab.", diff --git a/demo/locales/source.json b/demo/locales/source.json index be6c8e64c0..ae61c3d367 100644 --- a/demo/locales/source.json +++ b/demo/locales/source.json @@ -355,6 +355,10 @@ "description": "The name of a configuration value.", "message": "Force Transmux" }, + "DEMO_INSERT_FAKE_ENCRYPTION_IN_INIT": { + "description": "The name of a configuration value.", + "message": "Insert fake encryption in init segments when needed by the platform." + }, "DEMO_FRONT_INTRO_DISMISS": { "description": "A button allowing users to dismiss the intro message.", "message": "Dismiss" diff --git a/externs/shaka/player.js b/externs/shaka/player.js index 2d6d79b6ba..5bed82da58 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -1226,7 +1226,8 @@ shaka.extern.StreamingConfiguration; /** * @typedef {{ * sourceBufferExtraFeatures: string, - * forceTransmux: boolean + * forceTransmux: boolean, +* insertFakeEncryptionInInit: boolean * }} * * @description @@ -1240,6 +1241,17 @@ shaka.extern.StreamingConfiguration; * If this is true, we will transmux AAC and TS content even if * not strictly necessary for the assets to be played. * This value defaults to false. + * @property {boolean} insertFakeEncryptionInInit + * If true, will apply a work-around for non-encrypted init segments on + * encrypted content for some platforms. + *

+ * See https://github.com/shaka-project/shaka-player/issues/2759. + *

+ * If you know you don't need this, you canset this value to + * false to gain a few milliseconds on loading time and seek + * time. + *

+ * This value defaults to true. * @exportDoc */ shaka.extern.MediaSourceConfiguration; diff --git a/lib/media/media_source_engine.js b/lib/media/media_source_engine.js index 6f31e8ae12..8f2d8d95b9 100644 --- a/lib/media/media_source_engine.js +++ b/lib/media/media_source_engine.js @@ -1483,15 +1483,17 @@ shaka.media.MediaSourceEngine = class { const encryptionExpected = this.expectedEncryption_[contentType]; // If: - // 1. this is an init segment, - // 2. and encryption is expected, - // 3. and the platform requires encryption in all init segments, - // 4. and the content is MP4 (mimeType == "video/mp4" or "audio/mp4"), + // 1. the configuration tells to insert fake encryption, + // 2. and this is an init segment, + // 3. and encryption is expected, + // 4. and the platform requires encryption in all init segments, + // 5. and the content is MP4 (mimeType == "video/mp4" or "audio/mp4"), // then insert fake encryption metadata for init segments that lack it. // The MP4 requirement is because we can currently only do this // transformation on MP4 containers. // See: https://github.com/shaka-project/shaka-player/issues/2759 - if (isInitSegment && + if (this.config_.insertFakeEncryptionInInit && + isInitSegment && encryptionExpected && shaka.util.Platform.requiresEncryptionInfoInAllInitSegments() && shaka.util.MimeUtils.getContainerType( diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index d1a1696ec1..aa2b997e60 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -310,6 +310,7 @@ shaka.util.PlayerConfiguration = class { const mediaSource = { sourceBufferExtraFeatures: '', forceTransmux: false, + insertFakeEncryptionInInit: true, }; const ads = { diff --git a/test/media/media_source_engine_unit.js b/test/media/media_source_engine_unit.js index 7019b156e9..566e4e8326 100644 --- a/test/media/media_source_engine_unit.js +++ b/test/media/media_source_engine_unit.js @@ -56,9 +56,9 @@ describe('MediaSourceEngine', () => { const buffer2 = /** @type {!ArrayBuffer} */ (/** @type {?} */ (2)); const buffer3 = /** @type {!ArrayBuffer} */ (/** @type {?} */ (3)); - const fakeVideoStream = {mimeType: 'video/foo', drmInfos: []}; - const fakeAudioStream = {mimeType: 'audio/foo', drmInfos: []}; - const fakeTextStream = {mimeType: 'text/foo', drmInfos: []}; + const fakeVideoStream = {mimeType: 'video/mp4', drmInfos: [{}]}; + const fakeAudioStream = {mimeType: 'audio/mp4', drmInfos: []}; + const fakeTextStream = {mimeType: 'text/mp4', drmInfos: []}; const fakeTransportStream = {mimeType: 'tsMimetype', drmInfos: []}; /** @type {shaka.extern.Stream} */ @@ -82,6 +82,10 @@ describe('MediaSourceEngine', () => { /** @type {!jasmine.Spy} */ let createMediaSourceSpy; + /** @type {!jasmine.Spy} */ + let requiresEncryptionInfoInAllInitSegmentsSpy; + /** @type {!jasmine.Spy} */ + let fakeEncryptionSpy; /** @type {!shaka.media.MediaSourceEngine} */ let mediaSourceEngine; @@ -139,6 +143,12 @@ describe('MediaSourceEngine', () => { shaka.media.MediaSourceEngine.prototype.createMediaSource = Util.spyFunc(createMediaSourceSpy); + requiresEncryptionInfoInAllInitSegmentsSpy = spyOn(shaka.util.Platform, + 'requiresEncryptionInfoInAllInitSegments').and.returnValue(false); + + fakeEncryptionSpy = spyOn(shaka.media.ContentWorkarounds, 'fakeEncryption') + .and.callFake((data) => data + 100); + // MediaSourceEngine uses video to: // - set src attribute // - read error codes when operations fail @@ -269,8 +279,8 @@ describe('MediaSourceEngine', () => { initObject.set(ContentType.AUDIO, fakeAudioStream); initObject.set(ContentType.VIDEO, fakeVideoStream); await mediaSourceEngine.init(initObject, false); - expect(mockMediaSource.addSourceBuffer).toHaveBeenCalledWith('audio/foo'); - expect(mockMediaSource.addSourceBuffer).toHaveBeenCalledWith('video/foo'); + expect(mockMediaSource.addSourceBuffer).toHaveBeenCalledWith('audio/mp4'); + expect(mockMediaSource.addSourceBuffer).toHaveBeenCalledWith('video/mp4'); expect(shaka.text.TextEngine).not.toHaveBeenCalled(); }); @@ -396,6 +406,42 @@ describe('MediaSourceEngine', () => { await mediaSourceEngine.init(initObject, false); }); + it('should apply fake encryption by default', async () => { + requiresEncryptionInfoInAllInitSegmentsSpy.and.returnValue(true); + + const p = mediaSourceEngine.appendBuffer( + ContentType.VIDEO, buffer, null, fakeStream, + /* hasClosedCaptions= */ false); + + expect(fakeEncryptionSpy).toHaveBeenCalled(); + + expect(videoSourceBuffer.appendBuffer) + .toHaveBeenCalledWith((buffer + 100)); + videoSourceBuffer.updateend(); + + await p; + }); + + it('should not apply fake encryption when config is off', async () => { + requiresEncryptionInfoInAllInitSegmentsSpy.and.returnValue(true); + + const config = shaka.util.PlayerConfiguration.createDefault().mediaSource; + config.insertFakeEncryptionInInit = false; + + mediaSourceEngine.configure(config); + + const p = mediaSourceEngine.appendBuffer( + ContentType.VIDEO, buffer, null, fakeStream, + /* hasClosedCaptions= */ false); + + expect(fakeEncryptionSpy).not.toHaveBeenCalled(); + + expect(videoSourceBuffer.appendBuffer).toHaveBeenCalledWith(buffer); + videoSourceBuffer.updateend(); + + await p; + }); + it('appends the given data', async () => { const p = mediaSourceEngine.appendBuffer( ContentType.AUDIO, buffer, null, fakeStream,