diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:37 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:37 +0000 |
commit | a90a5cba08fdf6c0ceb95101c275108a152a3aed (patch) | |
tree | 532507288f3defd7f4dcf1af49698bcb76034855 /dom/media/mediacontrol | |
parent | Adding debian version 126.0.1-1. (diff) | |
download | firefox-a90a5cba08fdf6c0ceb95101c275108a152a3aed.tar.xz firefox-a90a5cba08fdf6c0ceb95101c275108a152a3aed.zip |
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/mediacontrol')
-rw-r--r-- | dom/media/mediacontrol/ContentMediaController.cpp | 31 | ||||
-rw-r--r-- | dom/media/mediacontrol/ContentMediaController.h | 3 | ||||
-rw-r--r-- | dom/media/mediacontrol/MediaControlKeyManager.cpp | 7 | ||||
-rw-r--r-- | dom/media/mediacontrol/MediaControlService.cpp | 1 | ||||
-rw-r--r-- | dom/media/mediacontrol/MediaPlaybackStatus.cpp | 64 | ||||
-rw-r--r-- | dom/media/mediacontrol/MediaPlaybackStatus.h | 16 | ||||
-rw-r--r-- | dom/media/mediacontrol/MediaStatusManager.cpp | 29 | ||||
-rw-r--r-- | dom/media/mediacontrol/MediaStatusManager.h | 17 | ||||
-rw-r--r-- | dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js | 243 | ||||
-rw-r--r-- | dom/media/mediacontrol/tests/browser/head.js | 81 |
10 files changed, 446 insertions, 46 deletions
diff --git a/dom/media/mediacontrol/ContentMediaController.cpp b/dom/media/mediacontrol/ContentMediaController.cpp index e1fe574d9b..0c3bbbecdc 100644 --- a/dom/media/mediacontrol/ContentMediaController.cpp +++ b/dom/media/mediacontrol/ContentMediaController.cpp @@ -304,6 +304,37 @@ void ContentMediaAgent::UpdatePositionState( } } +void ContentMediaAgent::UpdateGuessedPositionState( + uint64_t aBrowsingContextId, const nsID& aMediaId, + const Maybe<PositionState>& aState) { + RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); + if (!bc || bc->IsDiscarded()) { + return; + } + + if (aState) { + LOG("Update guessed position state for BC %" PRId64 + " media id %s (duration=%f, playbackRate=%f, position=%f)", + bc->Id(), aMediaId.ToString().get(), aState->mDuration, + aState->mPlaybackRate, aState->mLastReportedPlaybackPosition); + } else { + LOG("Clear guessed position state for BC %" PRId64 " media id %s", bc->Id(), + aMediaId.ToString().get()); + } + + if (XRE_IsContentProcess()) { + ContentChild* contentChild = ContentChild::GetSingleton(); + Unused << contentChild->SendNotifyGuessedPositionStateChanged(bc, aMediaId, + aState); + return; + } + // This would only happen when we disable e10s. + if (RefPtr<IMediaInfoUpdater> updater = + bc->Canonical()->GetMediaController()) { + updater->UpdateGuessedPositionState(bc->Id(), aMediaId, aState); + } +} + ContentMediaController::ContentMediaController(uint64_t aId) { LOG("Create content media controller for BC %" PRId64, aId); } diff --git a/dom/media/mediacontrol/ContentMediaController.h b/dom/media/mediacontrol/ContentMediaController.h index a58be24b9d..236b3b254d 100644 --- a/dom/media/mediacontrol/ContentMediaController.h +++ b/dom/media/mediacontrol/ContentMediaController.h @@ -67,6 +67,9 @@ class ContentMediaAgent : public IMediaInfoUpdater { bool aIsInFullScreen) override; void UpdatePositionState(uint64_t aBrowsingContextId, const Maybe<PositionState>& aState) override; + void UpdateGuessedPositionState(uint64_t aBrowsingContextId, + const nsID& aMediaId, + const Maybe<PositionState>& aState) override; // Use these methods to register/unregister `ContentMediaControlKeyReceiver` // in order to listen to media control key events. diff --git a/dom/media/mediacontrol/MediaControlKeyManager.cpp b/dom/media/mediacontrol/MediaControlKeyManager.cpp index b40d3af91e..92e2679bdd 100644 --- a/dom/media/mediacontrol/MediaControlKeyManager.cpp +++ b/dom/media/mediacontrol/MediaControlKeyManager.cpp @@ -107,6 +107,7 @@ void MediaControlKeyManager::StopMonitoringControlKeys() { nullptr); obs->NotifyObservers(nullptr, "media-displayed-metadata-changed", nullptr); + obs->NotifyObservers(nullptr, "media-position-state-changed", nullptr); } } } @@ -197,6 +198,12 @@ void MediaControlKeyManager::SetPositionState( if (mEventSource && mEventSource->IsOpened()) { mEventSource->SetPositionState(aState); } + + if (StaticPrefs::media_mediacontrol_testingevents_enabled()) { + if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) { + obs->NotifyObservers(nullptr, "media-position-state-changed", nullptr); + } + } } void MediaControlKeyManager::OnPreferenceChange() { diff --git a/dom/media/mediacontrol/MediaControlService.cpp b/dom/media/mediacontrol/MediaControlService.cpp index f45ab4253d..c64749f556 100644 --- a/dom/media/mediacontrol/MediaControlService.cpp +++ b/dom/media/mediacontrol/MediaControlService.cpp @@ -482,6 +482,7 @@ void MediaControlService::ControllerManager::UpdateMainControllerInternal( mSource->SetPlaybackState(mMainController->PlaybackState()); mSource->SetMediaMetadata(mMainController->GetCurrentMediaMetadata()); mSource->SetSupportedMediaKeys(mMainController->GetSupportedMediaKeys()); + mSource->SetPositionState(mMainController->GetCurrentPositionState()); ConnectMainControllerEvents(); } diff --git a/dom/media/mediacontrol/MediaPlaybackStatus.cpp b/dom/media/mediacontrol/MediaPlaybackStatus.cpp index 80dedf8599..434d6dbd7e 100644 --- a/dom/media/mediacontrol/MediaPlaybackStatus.cpp +++ b/dom/media/mediacontrol/MediaPlaybackStatus.cpp @@ -71,6 +71,23 @@ void MediaPlaybackStatus::UpdateMediaAudibleState(uint64_t aContextId, } } +void MediaPlaybackStatus::UpdateGuessedPositionState( + uint64_t aContextId, const nsID& aElementId, + const Maybe<PositionState>& aState) { + MOZ_ASSERT(NS_IsMainThread()); + if (aState) { + LOG("Update guessed position state for context %" PRIu64 + " element %s (duration=%f, playbackRate=%f, position=%f)", + aContextId, aElementId.ToString().get(), aState->mDuration, + aState->mPlaybackRate, aState->mLastReportedPlaybackPosition); + } else { + LOG("Clear guessed position state for context %" PRIu64 " element %s", + aContextId, aElementId.ToString().get()); + } + ContextMediaInfo& info = GetNotNullContextInfo(aContextId); + info.UpdateGuessedPositionState(aElementId, aState); +} + bool MediaPlaybackStatus::IsPlaying() const { MOZ_ASSERT(NS_IsMainThread()); return std::any_of(mContextInfoMap.Values().cbegin(), @@ -92,6 +109,35 @@ bool MediaPlaybackStatus::IsAnyMediaBeingControlled() const { [](const auto& info) { return info->IsAnyMediaBeingControlled(); }); } +Maybe<PositionState> MediaPlaybackStatus::GuessedMediaPositionState( + Maybe<uint64_t> aPreferredContextId) const { + auto contextId = aPreferredContextId; + if (!contextId) { + contextId = mOwningAudioFocusContextId; + } + + // either the preferred or focused context + if (contextId) { + auto entry = mContextInfoMap.Lookup(*contextId); + if (!entry) { + return Nothing(); + } + LOG("Using guessed position state from preferred/focused BC %" PRId64, + *contextId); + return entry.Data()->GuessedPositionState(); + } + + // look for the first position state + for (const auto& context : mContextInfoMap.Values()) { + auto state = context->GuessedPositionState(); + if (state) { + LOG("Using guessed position state from BC %" PRId64, context->Id()); + return state; + } + } + return Nothing(); +} + MediaPlaybackStatus::ContextMediaInfo& MediaPlaybackStatus::GetNotNullContextInfo(uint64_t aContextId) { MOZ_ASSERT(NS_IsMainThread()); @@ -139,4 +185,22 @@ bool MediaPlaybackStatus::IsContextOwningAudioFocus(uint64_t aContextId) const { : false; } +Maybe<PositionState> +MediaPlaybackStatus::ContextMediaInfo::GuessedPositionState() const { + if (mGuessedPositionStateMap.Count() != 1) { + LOG("Count is %d", mGuessedPositionStateMap.Count()); + return Nothing(); + } + return Some(mGuessedPositionStateMap.begin()->GetData()); +} + +void MediaPlaybackStatus::ContextMediaInfo::UpdateGuessedPositionState( + const nsID& aElementId, const Maybe<PositionState>& aState) { + if (aState) { + mGuessedPositionStateMap.InsertOrUpdate(aElementId, *aState); + } else { + mGuessedPositionStateMap.Remove(aElementId); + } +} + } // namespace mozilla::dom diff --git a/dom/media/mediacontrol/MediaPlaybackStatus.h b/dom/media/mediacontrol/MediaPlaybackStatus.h index da597e4dfa..f9ac25f73d 100644 --- a/dom/media/mediacontrol/MediaPlaybackStatus.h +++ b/dom/media/mediacontrol/MediaPlaybackStatus.h @@ -7,9 +7,11 @@ #include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" +#include "mozilla/dom/MediaSession.h" #include "nsISupportsImpl.h" #include "nsTArray.h" #include "nsTHashMap.h" +#include "nsID.h" namespace mozilla::dom { @@ -63,10 +65,14 @@ class MediaPlaybackStatus final { public: void UpdateMediaPlaybackState(uint64_t aContextId, MediaPlaybackState aState); void UpdateMediaAudibleState(uint64_t aContextId, MediaAudibleState aState); + void UpdateGuessedPositionState(uint64_t aContextId, const nsID& aElementId, + const Maybe<PositionState>& aState); bool IsPlaying() const; bool IsAudible() const; bool IsAnyMediaBeingControlled() const; + Maybe<PositionState> GuessedMediaPositionState( + Maybe<uint64_t> aPreferredContextId) const; Maybe<uint64_t> GetAudioFocusOwnerContextId() const; @@ -121,6 +127,10 @@ class MediaPlaybackStatus final { bool IsAnyMediaBeingControlled() const { return mControlledMediaNum > 0; } uint64_t Id() const { return mContextId; } + Maybe<PositionState> GuessedPositionState() const; + void UpdateGuessedPositionState(const nsID& aElementId, + const Maybe<PositionState>& aState); + private: /** * The possible value for those three numbers should follow this rule, @@ -130,6 +140,12 @@ class MediaPlaybackStatus final { uint32_t mAudibleMediaNum = 0; uint32_t mPlayingMediaNum = 0; uint64_t mContextId = 0; + + /** + * Contains the guessed position state of all media elements in this + * browsing context identified by their ID. + */ + nsTHashMap<nsID, PositionState> mGuessedPositionStateMap; }; ContextMediaInfo& GetNotNullContextInfo(uint64_t aContextId); diff --git a/dom/media/mediacontrol/MediaStatusManager.cpp b/dom/media/mediacontrol/MediaStatusManager.cpp index 633ae19a44..6e86dbf2eb 100644 --- a/dom/media/mediacontrol/MediaStatusManager.cpp +++ b/dom/media/mediacontrol/MediaStatusManager.cpp @@ -380,6 +380,29 @@ void MediaStatusManager::UpdatePositionState( mPositionStateChangedEvent.Notify(aState); } +void MediaStatusManager::UpdateGuessedPositionState( + uint64_t aBrowsingContextId, const nsID& aMediaId, + const Maybe<PositionState>& aGuessedState) { + mPlaybackStatusDelegate.UpdateGuessedPositionState(aBrowsingContextId, + aMediaId, aGuessedState); + + // The position state comes from a non-active media session and + // there is another one active (with some metadata). + if (mActiveMediaSessionContextId && + *mActiveMediaSessionContextId != aBrowsingContextId) { + return; + } + + // media session is declared for the updated session, but there's no active + // session - it will get emitted once the session becomes active + if (mMediaSessionInfoMap.Contains(aBrowsingContextId) && + !mActiveMediaSessionContextId) { + return; + } + + mPositionStateChangedEvent.Notify(GetCurrentPositionState()); +} + void MediaStatusManager::NotifySupportedKeysChangedIfNeeded( uint64_t aBrowsingContextId) { // Only the active media session's supported actions would be shown in virtual @@ -431,11 +454,13 @@ MediaMetadataBase MediaStatusManager::GetCurrentMediaMetadata() const { Maybe<PositionState> MediaStatusManager::GetCurrentPositionState() const { if (mActiveMediaSessionContextId) { auto info = mMediaSessionInfoMap.Lookup(*mActiveMediaSessionContextId); - if (info) { + if (info && info->mPositionState) { return info->mPositionState; } } - return Nothing(); + + return mPlaybackStatusDelegate.GuessedMediaPositionState( + mActiveMediaSessionContextId); } void MediaStatusManager::FillMissingTitleAndArtworkIfNeeded( diff --git a/dom/media/mediacontrol/MediaStatusManager.h b/dom/media/mediacontrol/MediaStatusManager.h index a4216c8453..45f3ccccc5 100644 --- a/dom/media/mediacontrol/MediaStatusManager.h +++ b/dom/media/mediacontrol/MediaStatusManager.h @@ -120,6 +120,12 @@ class IMediaInfoUpdater { // Use this method when media session update its position state. virtual void UpdatePositionState(uint64_t aBrowsingContextId, const Maybe<PositionState>& aState) = 0; + + // Use this method to update controlled media's position state and the + // browsing context where controlled media exists. + virtual void UpdateGuessedPositionState( + uint64_t aBrowsingContextId, const nsID& aMediaId, + const Maybe<PositionState>& aGuessedState) = 0; }; /** @@ -165,12 +171,19 @@ class MediaStatusManager : public IMediaInfoUpdater { MediaSessionAction aAction) override; void UpdatePositionState(uint64_t aBrowsingContextId, const Maybe<PositionState>& aState) override; + void UpdateGuessedPositionState( + uint64_t aBrowsingContextId, const nsID& aMediaId, + const Maybe<PositionState>& aGuessedState) override; // Return active media session's metadata if active media session exists and // it has already set its metadata. Otherwise, return default media metadata // which is based on website's title and favicon. MediaMetadataBase GetCurrentMediaMetadata() const; + // Return the active media session's position state. If the active media + // session doesn't exist or doesn't have any state, Nothing is returned. + Maybe<PositionState> GetCurrentPositionState() const; + bool IsMediaAudible() const; bool IsMediaPlaying() const; bool IsAnyMediaBeingControlled() const; @@ -247,10 +260,6 @@ class MediaStatusManager : public IMediaInfoUpdater { // media session doesn't exist, return 'None' instead. MediaSessionPlaybackState GetCurrentDeclaredPlaybackState() const; - // Return the active media session's position state. If the active media - // session doesn't exist or doesn't have any state, Nothing is returned. - Maybe<PositionState> GetCurrentPositionState() const; - // This state can match to the `guessed playback state` in the spec [1], it // indicates if we have any media element playing within the tab which this // controller belongs to. But currently we only take media elements into diff --git a/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js b/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js index 6074e2ee16..75f65eb34b 100644 --- a/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js +++ b/dom/media/mediacontrol/tests/browser/browser_media_control_position_state.js @@ -4,6 +4,7 @@ const IFRAME_URL = "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_iframe_media.html"; const testVideoId = "video"; +const videoDuration = 5.589333; add_task(async function setupTestingPref() { await SpecialPowers.pushPrefEnv({ @@ -18,9 +19,15 @@ add_task(async function setupTestingPref() { add_task(async function testSetPositionState() { info(`open media page`); const tab = await createLoadedTabWrapper(PAGE_URL); + logPositionStateChangeEvents(tab); + + info(`apply initial position state`); + await applyPositionState(tab, { duration: 10 }); info(`start media`); + const initialPositionState = isNextPositionState(tab, { duration: 10 }); await playMedia(tab, testVideoId); + await initialPositionState; info(`set duration only`); await setPositionState(tab, { @@ -47,9 +54,15 @@ add_task(async function testSetPositionState() { add_task(async function testSetPositionStateFromInactiveMediaSession() { info(`open media page`); const tab = await createLoadedTabWrapper(PAGE_URL); + logPositionStateChangeEvents(tab); + + info(`apply initial position state`); + await applyPositionState(tab, { duration: 10 }); info(`start media`); + const initialPositionState = isNextPositionState(tab, { duration: 10 }); await playMedia(tab, testVideoId); + await initialPositionState; info( `add an event listener to measure how many times the position state changes` @@ -82,48 +95,193 @@ add_task(async function testSetPositionStateFromInactiveMediaSession() { }); /** - * The following are helper functions. + * + * @param {boolean} withMetadata + * Specifies if the tab should set metadata for the playing video */ -async function setPositionState(tab, positionState) { +async function testGuessedPositionState(withMetadata) { + info(`open media page`); + const tab = await createLoadedTabWrapper(PAGE_URL); + logPositionStateChangeEvents(tab); + + if (withMetadata) { + info(`set media metadata`); + await setMediaMetadata(tab, { title: "A Video" }); + } + + info(`start media`); + await emitsPositionState(() => playMedia(tab, testVideoId), tab, { + duration: videoDuration, + position: 0, + playbackRate: 1.0, + }); + + info(`set playback rate to 2x`); + await emitsPositionState(() => setPlaybackRate(tab, testVideoId, 2.0), tab, { + duration: videoDuration, + position: null, // ignored, + playbackRate: 2.0, + }); + + info(`seek to 1s`); + await emitsPositionState(() => setCurrentTime(tab, testVideoId, 1.0), tab, { + duration: videoDuration, + position: 1.0, + playbackRate: 2.0, + }); + + let positionChangedNum = 0; const controller = tab.linkedBrowser.browsingContext.mediaController; - const positionStateChanged = new Promise(r => { - controller.addEventListener( - "positionstatechange", - event => { - const { duration, playbackRate, position } = positionState; - // duration is mandatory. - is( - event.duration, - duration, - `expected duration ${event.duration} is equal to ${duration}` - ); - - // Playback rate is optional, if it's not present, default should be 1.0 - if (playbackRate) { - is( - event.playbackRate, - playbackRate, - `expected playbackRate ${event.playbackRate} is equal to ${playbackRate}` - ); - } else { - is(event.playbackRate, 1.0, `expected default playbackRate is 1.0`); - } - - // Position state is optional, if it's not present, default should be 0.0 - if (position) { - is( - event.position, - position, - `expected position ${event.position} is equal to ${position}` - ); - } else { - is(event.position, 0.0, `expected default position is 0.0`); - } - r(); - }, - { once: true } - ); + controller.onpositionstatechange = () => positionChangedNum++; + + info(`pause media`); + // shouldn't generate an event + await pauseMedia(tab, testVideoId); + + info(`seek to 2s`); + await emitsPositionState(() => setCurrentTime(tab, testVideoId, 2.0), tab, { + duration: videoDuration, + position: 2.0, + playbackRate: 2.0, + }); + + info(`start media`); + await emitsPositionState(() => playMedia(tab, testVideoId), tab, { + duration: videoDuration, + position: 2.0, + playbackRate: 2.0, }); + + is( + positionChangedNum, + 2, + `We should only receive two of position changes, because pausing is effectless` + ); + + info(`remove tab`); + await tab.close(); +} + +add_task(async function testGuessedPositionStateWithMetadata() { + testGuessedPositionState(true); +}); + +add_task(async function testGuessedPositionStateWithoutMetadata() { + testGuessedPositionState(false); +}); + +/** + * @typedef {{ + * duration: number, + * playbackRate?: number | null, + * position?: number | null, + * }} ExpectedPositionState + */ + +/** + * Checks if the next received position state matches the expected one. + * + * @param {tab} tab + * The tab that contains the media + * @param {ExpectedPositionState} positionState + * The expected position state. `duration` is mandatory. `playbackRate` + * and `position` are optional. If they're `null`, they're ignored, + * otherwise if they're not present or undefined, they're expected to + * be the default value. + * @returns {Promise} + * Resolves when the event has been received + */ +async function isNextPositionState(tab, positionState) { + const got = await nextPositionState(tab); + isPositionState(got, positionState); +} + +/** + * Waits for the next position state and returns it + * + * @param {tab} tab The tab to receive position state from + * @returns {Promise<MediaPositionState>} The emitted position state + */ +function nextPositionState(tab) { + const controller = tab.linkedBrowser.browsingContext.mediaController; + return new Promise(r => { + controller.addEventListener("positionstatechange", r, { once: true }); + }); +} + +/** + * @param {MediaPositionState} got + * The received position state + * @param {ExpectedPositionState} expected + * The expected position state. `duration` is mandatory. `playbackRate` + * and `position` are optional. If they're `null`, they're ignored, + * otherwise if they're not present or undefined, they're expected to + * be the default value. + */ +function isPositionState(got, expected) { + const { duration, playbackRate, position } = expected; + // duration is mandatory. + isFuzzyEq(got.duration, duration, "duration"); + + // Playback rate is optional, if it's not present, default should be 1.0 + if (typeof playbackRate === "number") { + isFuzzyEq(got.playbackRate, playbackRate, "playbackRate"); + } else if (playbackRate !== null) { + is(got.playbackRate, 1.0, `expected default playbackRate is 1.0`); + } + + // Position is optional, if it's not present, default should be 0.0 + if (typeof position === "number") { + isFuzzyEq(got.position, position, "position"); + } else if (position !== null) { + is(got.position, 0.0, `expected default position is 0.0`); + } +} + +/** + * Checks if two numbers are equal within one significant digit + * + * @param {number} got + * The value received while testing + * @param {number} expected + * The expected value + * @param {string} role + * The role of the check (used for formatting) + */ +function isFuzzyEq(got, expected, role) { + expected = expected.toFixed(1); + got = got.toFixed(1); + is(got, expected, `expected ${role} ${got} to equal ${expected}`); +} + +/** + * Test if `cb` emits a position state event. + * + * @param {() => (void | Promise<void>)} cb + * A callback that is expected to generate a position state event + * @param {tab} tab + * The tab that contains the media + * @param {ExpectedPositionState} positionState + * The expected position state to be generated. + */ +async function emitsPositionState(cb, tab, positionState) { + const positionStateChanged = isNextPositionState(tab, positionState); + await cb(); + await positionStateChanged; +} + +/** + * The following are helper functions. + */ +async function setPositionState(tab, positionState) { + await emitsPositionState( + () => applyPositionState(tab, positionState), + tab, + positionState + ); +} + +async function applyPositionState(tab, positionState) { await SpecialPowers.spawn( tab.linkedBrowser, [positionState], @@ -131,7 +289,12 @@ async function setPositionState(tab, positionState) { content.navigator.mediaSession.setPositionState(positionState); } ); - await positionStateChanged; +} + +async function setMediaMetadata(tab, metadata) { + await SpecialPowers.spawn(tab.linkedBrowser, [metadata], data => { + content.navigator.mediaSession.metadata = new content.MediaMetadata(data); + }); } async function setPositionStateOnInactiveMediaSession(tab) { diff --git a/dom/media/mediacontrol/tests/browser/head.js b/dom/media/mediacontrol/tests/browser/head.js index cac96c0bff..7c6a1e37e4 100644 --- a/dom/media/mediacontrol/tests/browser/head.js +++ b/dom/media/mediacontrol/tests/browser/head.js @@ -195,6 +195,58 @@ function checkOrWaitUntilMediaStartedPlaying(tab, elementId) { } /** + * Set the playback rate on a media element. + * + * @param {tab} tab + * The tab that contains the media which we would check + * @param {string} elementId + * The element Id of the media which we would check + * @param {number} rate + * The playback rate to set + * @return {Promise} + * Resolve when the playback rate has been set + */ +function setPlaybackRate(tab, elementId, rate) { + return SpecialPowers.spawn( + tab.linkedBrowser, + [elementId, rate], + (Id, rate) => { + const video = content.document.getElementById(Id); + if (!video) { + ok(false, `can't get the media element!`); + } + video.playbackRate = rate; + } + ); +} + +/** + * Set the time on a media element. + * + * @param {tab} tab + * The tab that contains the media which we would check + * @param {string} elementId + * The element Id of the media which we would check + * @param {number} currentTime + * The time to set + * @return {Promise} + * Resolve when the time has been set + */ +function setCurrentTime(tab, elementId, currentTime) { + return SpecialPowers.spawn( + tab.linkedBrowser, + [elementId, currentTime], + (Id, currentTime) => { + const video = content.document.getElementById(Id); + if (!video) { + ok(false, `can't get the media element!`); + } + video.currentTime = currentTime; + } + ); +} + +/** * Returns a promise that resolves when the specific media stops playing. * * @param {tab} tab @@ -390,6 +442,18 @@ function waitUntilMediaControllerAmountChanged() { } /** + * Wait until the position state that would be displayed on the virtual control + * interface changes. we would observe that by listening for + * `media-position-state-changed` notification. + * + * @return {Promise} + * Resolve when observing `media-position-state-changed` + */ +function waitUntilPositionStateChanged() { + return BrowserUtils.promiseObserved("media-position-state-changed"); +} + +/** * check if the media controll from given tab is active. If not, return a * promise and resolve it when controller become active. */ @@ -400,3 +464,20 @@ async function checkOrWaitUntilControllerBecomeActive(tab) { } await new Promise(r => (controller.onactivated = r)); } + +/** + * Logs all `positionstatechange` events in a tab. + */ +function logPositionStateChangeEvents(tab) { + tab.linkedBrowser.browsingContext.mediaController.addEventListener( + "positionstatechange", + event => + info( + `got position state: ${JSON.stringify({ + duration: event.duration, + playbackRate: event.playbackRate, + position: event.position, + })}` + ) + ); +} |