/* 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, ToMediaPlaybackStateStr(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, ToMediaAudibleStateStr(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(); } } 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(); }); } MediaPlaybackStatus::ContextMediaInfo& MediaPlaybackStatus::GetNotNullContextInfo(uint64_t aContextId) { MOZ_ASSERT(NS_IsMainThread()); return *mContextInfoMap.GetOrInsertNew(aContextId, aContextId); } Maybe 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&& 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; } } // namespace mozilla::dom