- : 70 %
- : 70 %
- : 79 %
- : 79 %
- : 79 %
- : 70 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 70 %
- : 79 %
- : 79 %
- : 70 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 70 %
- : 70 %
- : 70 %
- : 70 %
- : 79 %
- : 79 %
- : 70 %
- : 70 %
- : 79 %
- : 79 %
- : 79 %
- : 70 %
- : 70 %
- : 79 %
- : 70 %
- : 70 %
- : 70 %
- : 70 %
- : 79 %
- : 79 %
- : 79 %
- : 70 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 70 %
- : 79 %
- : 70 %
- : 70 %
- : 79 %
- : 70 %
- : 79 %
- : 79 %
- : 79 %
- : 70 %
- : 70 %
- : 70 %
- : 79 %
- : 79 %
- : 79 %
- : 70 %
- : 70 %
- : 79 %
- : 79 %
- : 70 %
- : 79 %
- : 79 %
- : 70 %
- : 79 %
- : 70 %
- : 79 %
- : 79 %
- : 70 %
- : 79 %
- : 70 %
- : 70 %
- : 79 %
- : 79 %
- : 70 %
- : 70 %
- : 79 %
- : 70 %
- : 70 %
- : 70 %
- : 79 %
- : 70 %
- : 70 %
- : 79 %
- : 79 %
- : 79 %
- : 70 %
- : 79 %
- : 70 %
- : 79 %
- : 70 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 70 %
- : 79 %
- : 79 %
- : 70 %
- : 70 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 95 %
- : 100 %
- : 100 %
- : 95 %
- : 100 %
- : 91 %
- : 100 %
- : 100 %
- : 91 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 91 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 100 %
- : 91 %
- : 82 %
- : 100 %
- : 91 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 100 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 74 %
- : 79 %
- : 74 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 75 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 75 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 79 %
- : 78 %
- : 78 %
- : 78 %
- : 74 %
- : 74 %
- : 74 %
- : 74 %
- : 74 %
- : 74 %
- : 74 %
- : 74 %
- : 74 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
- : 0 %
Source code
Revision control
Copy as Markdown
Other Tools
/* 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,
#include "ContentMediaController.h"
#include "MediaControlUtils.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/ToString.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ContentChild.h"
#include "nsGlobalWindowInner.h"
namespace mozilla::dom {
#undef LOG
#define LOG(msg, ...) \
MOZ_LOG_FMT(gMediaControlLog, LogLevel::Debug, \
"ContentMediaController={}, " msg, fmt::ptr(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 {} in BC {}", ToString(aState).c_str(), bc->Id());
if (XRE_IsContentProcess()) {
ContentChild* contentChild = ContentChild::GetSingleton();
(void)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, ControlType aType,
AudioSessionType aSessionType) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
if (!bc || bc->IsDiscarded()) {
return;
}
LOG("Notify media became {} in BC {}",
aState == MediaAudibleState::eAudible ? "audible" : "inaudible",
bc->Id());
if (XRE_IsContentProcess()) {
ContentChild* contentChild = ContentChild::GetSingleton();
(void)contentChild->SendNotifyMediaAudibleChanged(bc, aState, aType,
aSessionType);
} 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, aType, aSessionType);
}
}
}
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 '{}' in BC {}",
aIsInPictureInPictureMode ? "enabled" : "disabled", bc->Id());
if (XRE_IsContentProcess()) {
ContentChild* contentChild = ContentChild::GetSingleton();
(void)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 '{}' in BC {}",
ToMediaSessionPlaybackStateStr(aState), bc->Id());
if (XRE_IsContentProcess()) {
ContentChild* contentChild = ContentChild::GetSingleton();
(void)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 {}", bc->Id());
if (XRE_IsContentProcess()) {
ContentChild* contentChild = ContentChild::GetSingleton();
(void)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 {}", bc->Id());
if (XRE_IsContentProcess()) {
ContentChild* contentChild = ContentChild::GetSingleton();
(void)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 {}", bc->Id());
if (XRE_IsContentProcess()) {
ContentChild* contentChild = ContentChild::GetSingleton();
(void)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 '{}' in BC {}", GetEnumString(aAction).get(),
bc->Id());
if (XRE_IsContentProcess()) {
ContentChild* contentChild = ContentChild::GetSingleton();
(void)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 '{}' in BC {}", GetEnumString(aAction).get(),
bc->Id());
if (XRE_IsContentProcess()) {
ContentChild* contentChild = ContentChild::GetSingleton();
(void)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 {} fullscreen in BC {}", aIsInFullScreen ? "entered" : "left",
bc->Id());
if (XRE_IsContentProcess()) {
ContentChild* contentChild = ContentChild::GetSingleton();
(void)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 Maybe<PositionState>& aState) {
RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
if (!bc || bc->IsDiscarded()) {
return;
}
if (XRE_IsContentProcess()) {
ContentChild* contentChild = ContentChild::GetSingleton();
(void)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);
}
}
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 {} media id {} (duration={}, "
"playbackRate={}, position={})",
bc->Id(), aMediaId.ToString().get(), aState->mDuration,
aState->mPlaybackRate, aState->mLastReportedPlaybackPosition);
} else {
LOG("Clear guessed position state for BC {} media id {}", bc->Id(),
aMediaId.ToString().get());
}
if (XRE_IsContentProcess()) {
ContentChild* contentChild = ContentChild::GetSingleton();
(void)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 {}", aId);
}
void ContentMediaController::AddReceiver(
ContentMediaControlKeyReceiver* aListener, ControlType aType) {
MOZ_ASSERT(NS_IsMainThread());
if (aType == ControlType::eControllable) {
mControllableReceivers.AppendElement(aListener);
} else {
mUncontrollableReceivers.AppendElement(aListener);
}
}
void ContentMediaController::RemoveReceiver(
ContentMediaControlKeyReceiver* aListener, ControlType aType) {
MOZ_ASSERT(NS_IsMainThread());
if (aType == ControlType::eControllable) {
mControllableReceivers.RemoveElement(aListener);
} else {
mUncontrollableReceivers.RemoveElement(aListener);
}
}
void ContentMediaController::HandleMediaKey(
MediaControlKey aKey, const MediaControlActionParams& aParams) {
MOZ_ASSERT(NS_IsMainThread());
if (mControllableReceivers.IsEmpty() && mUncontrollableReceivers.IsEmpty()) {
return;
}
LOG("Handle '{}' event, controllable num={}, uncontrollable num={}",
GetEnumString(aKey).get(), mControllableReceivers.Length(),
mUncontrollableReceivers.Length());
// We have default handlers for these actions
switch (aKey) {
case MediaControlKey::Pause:
PauseOrStopMedia();
return;
case MediaControlKey::Play:
case MediaControlKey::Seekto:
case MediaControlKey::Seekforward:
case MediaControlKey::Seekbackward:
for (auto& receiver : Reversed(mControllableReceivers)) {
receiver->HandleMediaKey(aKey, aParams);
}
return;
case MediaControlKey::Stop:
case MediaControlKey::Setvolume:
case MediaControlKey::Mute:
case MediaControlKey::Unmute:
// Audio focus loss arrives as Stop and must silence uncontrollable
// sources too; volume/mute always target both lists. Iterate backward
// because Stop can shrink the controllable list during iteration.
for (auto& receiver : Reversed(mControllableReceivers)) {
receiver->HandleMediaKey(aKey, aParams);
}
for (auto& receiver : Reversed(mUncontrollableReceivers)) {
receiver->HandleMediaKey(aKey, aParams);
}
return;
default:
MOZ_ASSERT_UNREACHABLE("Not supported media key for default handler");
}
}
void ContentMediaController::HandleAudioFocusInterrupt(
AudioFocusInterruptAction aAction) {
MOZ_ASSERT(NS_IsMainThread());
const bool suspend = aAction == AudioFocusInterruptAction::Suspend;
LOG("Handle audio-focus interrupt {}, controllable num={}, uncontrollable "
"num={}",
EnumValueToString(aAction), mControllableReceivers.Length(),
mUncontrollableReceivers.Length());
mAudioInterruptedByPlatform = suspend;
// An interrupt targets every potentially audible receiver in both buckets.
// Iterate backward because a receiver may unregister itself while handling
// the verb.
for (auto& receiver : Reversed(mControllableReceivers)) {
if (suspend) {
receiver->SuspendForInterrupt();
} else {
receiver->ResumeFromInterrupt();
}
}
for (auto& receiver : Reversed(mUncontrollableReceivers)) {
if (suspend) {
receiver->SuspendForInterrupt();
} else {
receiver->ResumeFromInterrupt();
}
}
}
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
// `mControllableReceivers`. 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 : mControllableReceivers) {
if (receiver->IsPlaying()) {
isAnyMediaPlaying = true;
break;
}
}
for (auto& receiver : Reversed(mControllableReceivers)) {
if (isAnyMediaPlaying && !receiver->IsPlaying()) {
receiver->HandleMediaKey(MediaControlKey::Stop);
} else {
receiver->HandleMediaKey(MediaControlKey::Pause);
}
}
}
} // namespace mozilla::dom