/* 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 "MediaPlaybackStatus.h"
#include "MediaControlUtils.h"
namespace mozilla::dom {
#undef LOG
#define LOG(msg, ...) \
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
(
"MediaPlaybackStatus=%p, " msg,
this,
##__VA_ARGS__))
void MediaPlaybackStatus::UpdateMediaPlaybackState(uint64_t aContextId,
MediaPlaybackState aState) {
LOG(
"Update playback state '%s' for context %" PRIu64,
EnumValueToString(aState), aContextId);
MOZ_ASSERT(NS_IsMainThread());
ContextMediaInfo& info = GetNotNullContextInfo(aContextId);
if (aState == MediaPlaybackState::eStarted) {
info.IncreaseControlledMediaNum();
}
else if (aState == MediaPlaybackState::eStopped) {
info.DecreaseControlledMediaNum();
}
else if (aState == MediaPlaybackState::ePlayed) {
info.IncreasePlayingMediaNum();
}
else {
MOZ_ASSERT(aState == MediaPlaybackState::ePaused);
info.DecreasePlayingMediaNum();
}
// The context still has controlled media, we should keep its alive.
if (info.IsAnyMediaBeingControlled()) {
return;
}
MOZ_ASSERT(!info.IsPlaying());
MOZ_ASSERT(!info.IsAudible());
// DO NOT access `info` after this line.
DestroyContextInfo(aContextId);
}
void MediaPlaybackStatus::DestroyContextInfo(uint64_t aContextId) {
MOZ_ASSERT(NS_IsMainThread());
LOG(
"Remove context %" PRIu64, aContextId);
mContextInfoMap.Remove(aContextId);
// If the removed context is owning the audio focus, we would find another
// context to take the audio focus if it's possible.
if (IsContextOwningAudioFocus(aContextId)) {
ChooseNewContextToOwnAudioFocus();
}
}
void MediaPlaybackStatus::UpdateMediaAudibleState(uint64_t aContextId,
MediaAudibleState aState) {
LOG(
"Update audible state '%s' for context %" PRIu64,
EnumValueToString(aState), aContextId);
MOZ_ASSERT(NS_IsMainThread());
ContextMediaInfo& info = GetNotNullContextInfo(aContextId);
if (aState == MediaAudibleState::eAudible) {
info.IncreaseAudibleMediaNum();
}
else {
MOZ_ASSERT(aState == MediaAudibleState::eInaudible);
info.DecreaseAudibleMediaNum();
}
if (ShouldRequestAudioFocusForInfo(info)) {
SetOwningAudioFocusContextId(Some(aContextId));
}
else if (ShouldAbandonAudioFocusForInfo(info)) {
ChooseNewContextToOwnAudioFocus();
}
}
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(),
mContextInfoMap.Values().cend(),
[](
const auto& info) {
return info->IsPlaying(); });
}
bool MediaPlaybackStatus::IsAudible()
const {
MOZ_ASSERT(NS_IsMainThread());
return std::any_of(mContextInfoMap.Values().cbegin(),
mContextInfoMap.Values().cend(),
[](
const auto& info) {
return info->IsAudible(); });
}
bool MediaPlaybackStatus::IsAnyMediaBeingControlled()
const {
MOZ_ASSERT(NS_IsMainThread());
return std::any_of(
mContextInfoMap.Values().cbegin(), mContextInfoMap.Values().cend(),
[](
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());
return *mContextInfoMap.GetOrInsertNew(aContextId, aContextId);
}
Maybe<uint64_t> MediaPlaybackStatus::GetAudioFocusOwnerContextId()
const {
return mOwningAudioFocusContextId;
}
void MediaPlaybackStatus::ChooseNewContextToOwnAudioFocus() {
for (
const auto& info : mContextInfoMap.Values()) {
if (info->IsAudible()) {
SetOwningAudioFocusContextId(Some(info->Id()));
return;
}
}
// No context is audible, so no one should the own audio focus.
SetOwningAudioFocusContextId(Nothing());
}
void MediaPlaybackStatus::SetOwningAudioFocusContextId(
Maybe<uint64_t>&& aContextId) {
if (mOwningAudioFocusContextId == aContextId) {
return;
}
mOwningAudioFocusContextId = aContextId;
}
bool MediaPlaybackStatus::ShouldRequestAudioFocusForInfo(
const ContextMediaInfo& aInfo)
const {
return aInfo.IsAudible() && !IsContextOwningAudioFocus(aInfo.Id());
}
bool MediaPlaybackStatus::ShouldAbandonAudioFocusForInfo(
const ContextMediaInfo& aInfo)
const {
// The owner becomes inaudible and there is other context still playing, so we
// should switch the audio focus to the audible context.
return !aInfo.IsAudible() && IsContextOwningAudioFocus(aInfo.Id()) &&
IsAudible();
}
bool MediaPlaybackStatus::IsContextOwningAudioFocus(uint64_t aContextId)
const {
return mOwningAudioFocusContextId ? *mOwningAudioFocusContextId == aContextId
:
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