diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/media/mediacontrol/ContentMediaController.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/mediacontrol/ContentMediaController.cpp')
-rw-r--r-- | dom/media/mediacontrol/ContentMediaController.cpp | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/dom/media/mediacontrol/ContentMediaController.cpp b/dom/media/mediacontrol/ContentMediaController.cpp new file mode 100644 index 0000000000..a5d19a57c4 --- /dev/null +++ b/dom/media/mediacontrol/ContentMediaController.cpp @@ -0,0 +1,376 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ContentMediaController.h" + +#include "MediaControlUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "nsGlobalWindowOuter.h" + +namespace mozilla::dom { + +#undef LOG +#define LOG(msg, ...) \ + MOZ_LOG(gMediaControlLog, LogLevel::Debug, \ + ("ContentMediaController=%p, " msg, this, ##__VA_ARGS__)) + +static Maybe<bool> sXPCOMShutdown; + +static void InitXPCOMShutdownMonitor() { + if (sXPCOMShutdown) { + return; + } + sXPCOMShutdown.emplace(false); + RunOnShutdown([&] { sXPCOMShutdown = Some(true); }); +} + +static ContentMediaController* GetContentMediaControllerFromBrowsingContext( + BrowsingContext* aBrowsingContext) { + MOZ_ASSERT(NS_IsMainThread()); + InitXPCOMShutdownMonitor(); + if (!aBrowsingContext || aBrowsingContext->IsDiscarded()) { + return nullptr; + } + + nsPIDOMWindowOuter* outer = aBrowsingContext->GetDOMWindow(); + if (!outer) { + return nullptr; + } + + nsGlobalWindowInner* inner = + nsGlobalWindowInner::Cast(outer->GetCurrentInnerWindow()); + return inner ? inner->GetContentMediaController() : nullptr; +} + +static already_AddRefed<BrowsingContext> GetBrowsingContextForAgent( + uint64_t aBrowsingContextId) { + // If XPCOM has been shutdown, then we're not able to access browsing context. + if (sXPCOMShutdown && *sXPCOMShutdown) { + return nullptr; + } + return BrowsingContext::Get(aBrowsingContextId); +} + +/* static */ +ContentMediaControlKeyReceiver* ContentMediaControlKeyReceiver::Get( + BrowsingContext* aBC) { + MOZ_ASSERT(NS_IsMainThread()); + return GetContentMediaControllerFromBrowsingContext(aBC); +} + +/* static */ +ContentMediaAgent* ContentMediaAgent::Get(BrowsingContext* aBC) { + MOZ_ASSERT(NS_IsMainThread()); + return GetContentMediaControllerFromBrowsingContext(aBC); +} + +void ContentMediaAgent::NotifyMediaPlaybackChanged(uint64_t aBrowsingContextId, + MediaPlaybackState aState) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); + if (!bc || bc->IsDiscarded()) { + return; + } + + LOG("Notify media %s in BC %" PRId64, ToMediaPlaybackStateStr(aState), + bc->Id()); + if (XRE_IsContentProcess()) { + ContentChild* contentChild = ContentChild::GetSingleton(); + Unused << contentChild->SendNotifyMediaPlaybackChanged(bc, aState); + } else { + // Currently this only happen when we disable e10s, otherwise all controlled + // media would be run in the content process. + if (RefPtr<IMediaInfoUpdater> updater = + bc->Canonical()->GetMediaController()) { + updater->NotifyMediaPlaybackChanged(bc->Id(), aState); + } + } +} + +void ContentMediaAgent::NotifyMediaAudibleChanged(uint64_t aBrowsingContextId, + MediaAudibleState aState) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); + if (!bc || bc->IsDiscarded()) { + return; + } + + LOG("Notify media became %s in BC %" PRId64, + aState == MediaAudibleState::eAudible ? "audible" : "inaudible", + bc->Id()); + if (XRE_IsContentProcess()) { + ContentChild* contentChild = ContentChild::GetSingleton(); + Unused << contentChild->SendNotifyMediaAudibleChanged(bc, aState); + } else { + // Currently this only happen when we disable e10s, otherwise all controlled + // media would be run in the content process. + if (RefPtr<IMediaInfoUpdater> updater = + bc->Canonical()->GetMediaController()) { + updater->NotifyMediaAudibleChanged(bc->Id(), aState); + } + } +} + +void ContentMediaAgent::SetIsInPictureInPictureMode( + uint64_t aBrowsingContextId, bool aIsInPictureInPictureMode) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); + if (!bc || bc->IsDiscarded()) { + return; + } + + LOG("Notify media Picture-in-Picture mode '%s' in BC %" PRId64, + aIsInPictureInPictureMode ? "enabled" : "disabled", bc->Id()); + if (XRE_IsContentProcess()) { + ContentChild* contentChild = ContentChild::GetSingleton(); + Unused << contentChild->SendNotifyPictureInPictureModeChanged( + bc, aIsInPictureInPictureMode); + } else { + // Currently this only happen when we disable e10s, otherwise all controlled + // media would be run in the content process. + if (RefPtr<IMediaInfoUpdater> updater = + bc->Canonical()->GetMediaController()) { + updater->SetIsInPictureInPictureMode(bc->Id(), aIsInPictureInPictureMode); + } + } +} + +void ContentMediaAgent::SetDeclaredPlaybackState( + uint64_t aBrowsingContextId, MediaSessionPlaybackState aState) { + RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); + if (!bc || bc->IsDiscarded()) { + return; + } + + LOG("Notify declared playback state '%s' in BC %" PRId64, + ToMediaSessionPlaybackStateStr(aState), bc->Id()); + if (XRE_IsContentProcess()) { + ContentChild* contentChild = ContentChild::GetSingleton(); + Unused << contentChild->SendNotifyMediaSessionPlaybackStateChanged(bc, + aState); + return; + } + // This would only happen when we disable e10s. + if (RefPtr<IMediaInfoUpdater> updater = + bc->Canonical()->GetMediaController()) { + updater->SetDeclaredPlaybackState(bc->Id(), aState); + } +} + +void ContentMediaAgent::NotifySessionCreated(uint64_t aBrowsingContextId) { + RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); + if (!bc || bc->IsDiscarded()) { + return; + } + + LOG("Notify media session being created in BC %" PRId64, bc->Id()); + if (XRE_IsContentProcess()) { + ContentChild* contentChild = ContentChild::GetSingleton(); + Unused << contentChild->SendNotifyMediaSessionUpdated(bc, true); + return; + } + // This would only happen when we disable e10s. + if (RefPtr<IMediaInfoUpdater> updater = + bc->Canonical()->GetMediaController()) { + updater->NotifySessionCreated(bc->Id()); + } +} + +void ContentMediaAgent::NotifySessionDestroyed(uint64_t aBrowsingContextId) { + RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); + if (!bc || bc->IsDiscarded()) { + return; + } + + LOG("Notify media session being destroyed in BC %" PRId64, bc->Id()); + if (XRE_IsContentProcess()) { + ContentChild* contentChild = ContentChild::GetSingleton(); + Unused << contentChild->SendNotifyMediaSessionUpdated(bc, false); + return; + } + // This would only happen when we disable e10s. + if (RefPtr<IMediaInfoUpdater> updater = + bc->Canonical()->GetMediaController()) { + updater->NotifySessionDestroyed(bc->Id()); + } +} + +void ContentMediaAgent::UpdateMetadata( + uint64_t aBrowsingContextId, const Maybe<MediaMetadataBase>& aMetadata) { + RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); + if (!bc || bc->IsDiscarded()) { + return; + } + + LOG("Notify media session metadata change in BC %" PRId64, bc->Id()); + if (XRE_IsContentProcess()) { + ContentChild* contentChild = ContentChild::GetSingleton(); + Unused << contentChild->SendNotifyUpdateMediaMetadata(bc, aMetadata); + return; + } + // This would only happen when we disable e10s. + if (RefPtr<IMediaInfoUpdater> updater = + bc->Canonical()->GetMediaController()) { + updater->UpdateMetadata(bc->Id(), aMetadata); + } +} + +void ContentMediaAgent::EnableAction(uint64_t aBrowsingContextId, + MediaSessionAction aAction) { + RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); + if (!bc || bc->IsDiscarded()) { + return; + } + + LOG("Notify to enable action '%s' in BC %" PRId64, + ToMediaSessionActionStr(aAction), bc->Id()); + if (XRE_IsContentProcess()) { + ContentChild* contentChild = ContentChild::GetSingleton(); + Unused << contentChild->SendNotifyMediaSessionSupportedActionChanged( + bc, aAction, true); + return; + } + // This would only happen when we disable e10s. + if (RefPtr<IMediaInfoUpdater> updater = + bc->Canonical()->GetMediaController()) { + updater->EnableAction(bc->Id(), aAction); + } +} + +void ContentMediaAgent::DisableAction(uint64_t aBrowsingContextId, + MediaSessionAction aAction) { + RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); + if (!bc || bc->IsDiscarded()) { + return; + } + + LOG("Notify to disable action '%s' in BC %" PRId64, + ToMediaSessionActionStr(aAction), bc->Id()); + if (XRE_IsContentProcess()) { + ContentChild* contentChild = ContentChild::GetSingleton(); + Unused << contentChild->SendNotifyMediaSessionSupportedActionChanged( + bc, aAction, false); + return; + } + // This would only happen when we disable e10s. + if (RefPtr<IMediaInfoUpdater> updater = + bc->Canonical()->GetMediaController()) { + updater->DisableAction(bc->Id(), aAction); + } +} + +void ContentMediaAgent::NotifyMediaFullScreenState(uint64_t aBrowsingContextId, + bool aIsInFullScreen) { + RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); + if (!bc || bc->IsDiscarded()) { + return; + } + + LOG("Notify %s fullscreen in BC %" PRId64, + aIsInFullScreen ? "entered" : "left", bc->Id()); + if (XRE_IsContentProcess()) { + ContentChild* contentChild = ContentChild::GetSingleton(); + Unused << contentChild->SendNotifyMediaFullScreenState(bc, aIsInFullScreen); + return; + } + // This would only happen when we disable e10s. + if (RefPtr<IMediaInfoUpdater> updater = + bc->Canonical()->GetMediaController()) { + updater->NotifyMediaFullScreenState(bc->Id(), aIsInFullScreen); + } +} + +void ContentMediaAgent::UpdatePositionState(uint64_t aBrowsingContextId, + const PositionState& aState) { + RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId); + if (!bc || bc->IsDiscarded()) { + return; + } + if (XRE_IsContentProcess()) { + ContentChild* contentChild = ContentChild::GetSingleton(); + Unused << contentChild->SendNotifyPositionStateChanged(bc, aState); + return; + } + // This would only happen when we disable e10s. + if (RefPtr<IMediaInfoUpdater> updater = + bc->Canonical()->GetMediaController()) { + updater->UpdatePositionState(bc->Id(), aState); + } +} + +ContentMediaController::ContentMediaController(uint64_t aId) { + LOG("Create content media controller for BC %" PRId64, aId); +} + +void ContentMediaController::AddReceiver( + ContentMediaControlKeyReceiver* aListener) { + MOZ_ASSERT(NS_IsMainThread()); + mReceivers.AppendElement(aListener); +} + +void ContentMediaController::RemoveReceiver( + ContentMediaControlKeyReceiver* aListener) { + MOZ_ASSERT(NS_IsMainThread()); + mReceivers.RemoveElement(aListener); +} + +void ContentMediaController::HandleMediaKey(MediaControlKey aKey) { + MOZ_ASSERT(NS_IsMainThread()); + if (mReceivers.IsEmpty()) { + return; + } + LOG("Handle '%s' event, receiver num=%zu", ToMediaControlKeyStr(aKey), + mReceivers.Length()); + // We have default handlers for play, pause and stop. + // https://w3c.github.io/mediasession/#ref-for-dom-mediasessionaction-play%E2%91%A3 + switch (aKey) { + case MediaControlKey::Pause: + PauseOrStopMedia(); + return; + case MediaControlKey::Play: + [[fallthrough]]; + case MediaControlKey::Stop: + // When receiving `Stop`, the amount of receiver would vary during the + // iteration, so we use the backward iteration to avoid accessing the + // index which is over the array length. + for (auto& receiver : Reversed(mReceivers)) { + receiver->HandleMediaKey(aKey); + } + return; + default: + MOZ_ASSERT_UNREACHABLE("Not supported media key for default handler"); + } +} + +void ContentMediaController::PauseOrStopMedia() { + // When receiving `pause`, if a page contains playing media and paused media + // at that moment, that means a user intends to pause those playing + // media, not the already paused ones. Then, we're going to stop those already + // paused media and keep those latest paused media in `mReceivers`. + // The reason for doing that is, when resuming paused media, we only want to + // resume latest paused media, not all media, in order to get a better user + // experience, which matches Chrome's behavior. + bool isAnyMediaPlaying = false; + for (const auto& receiver : mReceivers) { + if (receiver->IsPlaying()) { + isAnyMediaPlaying = true; + break; + } + } + + for (auto& receiver : Reversed(mReceivers)) { + if (isAnyMediaPlaying && !receiver->IsPlaying()) { + receiver->HandleMediaKey(MediaControlKey::Stop); + } else { + receiver->HandleMediaKey(MediaControlKey::Pause); + } + } +} + +} // namespace mozilla::dom |