From 01da5fa8a4b32c14582bd3cb865b5d5eca591d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Velad=20Galv=C3=A1n?= Date: Wed, 11 Oct 2023 11:22:17 +0200 Subject: [PATCH] feat: Use ManagedMediaSource when available (#5683) The spec can be seen at https://github.com/w3c/media-source/issues/320 Closes https://github.com/shaka-project/shaka-player/issues/5271 --- externs/managedmediasource.js | 24 +++++++ lib/media/media_source_capabilities.js | 13 +++- lib/media/media_source_engine.js | 73 +++++++++++++++++----- lib/media/streaming_engine.js | 8 +++ lib/util/platform.js | 5 +- test/test/util/fake_media_source_engine.js | 5 ++ 6 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 externs/managedmediasource.js diff --git a/externs/managedmediasource.js b/externs/managedmediasource.js new file mode 100644 index 0000000000..cca0dbd843 --- /dev/null +++ b/externs/managedmediasource.js @@ -0,0 +1,24 @@ +/*! @license + * Shaka Player + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Externs for ManagedMediaSource which were missing in the + * Closure compiler. + * + * @externs + */ + +/** + * @constructor + * @extends {MediaSource} + */ +function ManagedMediaSource() {} + +/** + * @param {string} type + * @return {boolean} + */ +ManagedMediaSource.isTypeSupported = function(type) {}; diff --git a/lib/media/media_source_capabilities.js b/lib/media/media_source_capabilities.js index fa6c90cc58..eb0d0e0f80 100644 --- a/lib/media/media_source_capabilities.js +++ b/lib/media/media_source_capabilities.js @@ -23,9 +23,16 @@ shaka.media.Capabilities = class { if (supportMap.has(type)) { return supportMap.get(type); } - const currentSupport = MediaSource.isTypeSupported(type); - supportMap.set(type, currentSupport); - return currentSupport; + if (window.ManagedMediaSource) { + const currentSupport = ManagedMediaSource.isTypeSupported(type); + supportMap.set(type, currentSupport); + return currentSupport; + } else if (window.MediaSource) { + const currentSupport = MediaSource.isTypeSupported(type); + supportMap.set(type, currentSupport); + return currentSupport; + } + return false; } /** diff --git a/lib/media/media_source_engine.js b/lib/media/media_source_engine.js index 2c6d9a5ce6..de21378331 100644 --- a/lib/media/media_source_engine.js +++ b/lib/media/media_source_engine.js @@ -119,7 +119,7 @@ shaka.media.MediaSourceEngine = class { /** @private {boolean} */ this.playbackHasBegun_ = false; - /** @private {MediaSource} */ + /** @private {(MediaSource|ManagedMediaSource)} */ this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_); /** @private {boolean} */ @@ -145,6 +145,9 @@ shaka.media.MediaSourceEngine = class { /** @private {boolean} */ this.needSplitMuxedContent_ = false; + + /** @private {boolean} */ + this.streamingAllowed_ = true; } /** @@ -154,30 +157,60 @@ shaka.media.MediaSourceEngine = class { * Replaced by unit tests. * * @param {!shaka.util.PublicPromise} p - * @return {!MediaSource} + * @return {!(MediaSource|ManagedMediaSource)} */ createMediaSource(p) { - const mediaSource = new MediaSource(); + if (window.ManagedMediaSource) { + this.video_.disableRemotePlayback = true; - // Set up MediaSource on the video element. - this.eventManager_.listenOnce( - mediaSource, 'sourceopen', () => this.onSourceOpen_(p)); + const mediaSource = new ManagedMediaSource(); - // Correctly set when playback has begun. - this.eventManager_.listenOnce(this.video_, 'playing', () => { - this.playbackHasBegun_ = true; - }); + this.eventManager_.listenOnce( + mediaSource, 'sourceopen', () => this.onSourceOpen_(p)); + + this.eventManager_.listen( + mediaSource, 'startstreaming', () => { + this.streamingAllowed_ = true; + }); + + this.eventManager_.listen( + mediaSource, 'endstreaming', () => { + this.streamingAllowed_ = false; + }); + + // Correctly set when playback has begun. + this.eventManager_.listenOnce(this.video_, 'playing', () => { + this.playbackHasBegun_ = true; + }); + + this.url_ = shaka.media.MediaSourceEngine.createObjectURL(mediaSource); + + this.video_.src = this.url_; + + return mediaSource; + } else { + const mediaSource = new MediaSource(); + + // Set up MediaSource on the video element. + this.eventManager_.listenOnce( + mediaSource, 'sourceopen', () => this.onSourceOpen_(p)); + + // Correctly set when playback has begun. + this.eventManager_.listenOnce(this.video_, 'playing', () => { + this.playbackHasBegun_ = true; + }); - // Store the object URL for releasing it later. - this.url_ = shaka.media.MediaSourceEngine.createObjectURL(mediaSource); + // Store the object URL for releasing it later. + this.url_ = shaka.media.MediaSourceEngine.createObjectURL(mediaSource); - this.video_.src = this.url_; + this.video_.src = this.url_; - return mediaSource; + return mediaSource; + } } /** - * @param {!shaka.util.PublicPromise} p + * @param {shaka.util.PublicPromise} p * @private */ onSourceOpen_(p) { @@ -511,6 +544,16 @@ shaka.media.MediaSourceEngine = class { this.config_ = config; } + /** + * Indicate if the streaming is allowed by MediaSourceEngine. + * If we using MediaSource we allways returns true. + * + * @return {boolean} + */ + isStreamingAllowed() { + return this.streamingAllowed_; + } + /** * Reinitialize the TextEngine for a new text type. * @param {string} mimeType diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index 691912d155..16f8be75d1 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -1054,6 +1054,14 @@ shaka.media.StreamingEngine = class { this.playerInterface_.mediaSourceEngine.clearSelectedClosedCaptionId(); } + if (!this.playerInterface_.mediaSourceEngine.isStreamingAllowed() && + mediaState.type != ContentType.TEXT) { + // It is not allowed to add segments yet, so we schedule an update to + // check again later. So any prediction we make now could be terribly + // invalid soon. + return this.config_.updateIntervalSeconds / 2; + } + const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState); // Compute how far we've buffered ahead of the playhead. diff --git a/lib/util/platform.js b/lib/util/platform.js index 92f1ce7eec..a0c518bd9a 100644 --- a/lib/util/platform.js +++ b/lib/util/platform.js @@ -23,15 +23,16 @@ shaka.util.Platform = class { * @return {boolean} */ static supportsMediaSource() { + const mediaSource = window.ManagedMediaSource || window.MediaSource; // Browsers that lack a media source implementation will have no reference // to |window.MediaSource|. Platforms that we see having problematic media // source implementations will have this reference removed via a polyfill. - if (!window.MediaSource) { + if (!mediaSource) { return false; } // Some very old MediaSource implementations didn't have isTypeSupported. - if (!MediaSource.isTypeSupported) { + if (!mediaSource.isTypeSupported) { return false; } diff --git a/test/test/util/fake_media_source_engine.js b/test/test/util/fake_media_source_engine.js index 04df9e9a9f..b64a25d47a 100644 --- a/test/test/util/fake_media_source_engine.js +++ b/test/test/util/fake_media_source_engine.js @@ -147,6 +147,11 @@ shaka.test.FakeMediaSourceEngine = class { return Promise.resolve(); } + /** @override */ + isStreamingAllowed() { + return true; + } + /** * @param {string} type * @return {?number}