diff --git a/dom/media/mediacontrol/MediaPlaybackStatus.cpp b/dom/media/mediacontrol/MediaPlaybackStatus.cpp index 42e6a8da1e1d..10566d8c086a 100644 --- a/dom/media/mediacontrol/MediaPlaybackStatus.cpp +++ b/dom/media/mediacontrol/MediaPlaybackStatus.cpp @@ -46,6 +46,11 @@ 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, @@ -60,6 +65,11 @@ void MediaPlaybackStatus::UpdateMediaAudibleState(uint64_t aContextId, MOZ_ASSERT(aState == MediaAudibleState::eInaudible); info.DecreaseAudibleMediaNum(); } + if (ShouldRequestAudioFocusForInfo(info)) { + SetOwningAudioFocusContextId(Some(aContextId)); + } else if (ShouldAbandonAudioFocusForInfo(info)) { + ChooseNewContextToOwnAudioFocus(); + } } bool MediaPlaybackStatus::IsPlaying() const { @@ -101,5 +111,46 @@ MediaPlaybackStatus::GetNotNullContextInfo(uint64_t aContextId) { return *(mContextInfoMap.GetValue(aContextId)->get()); } +Maybe MediaPlaybackStatus::GetAudioFocusOwnerContextId() const { + return mOwningAudioFocusContextId; +} + +void MediaPlaybackStatus::ChooseNewContextToOwnAudioFocus() { + for (auto iter = mContextInfoMap.ConstIter(); !iter.Done(); iter.Next()) { + if (iter.Data()->IsAudible()) { + SetOwningAudioFocusContextId(Some(iter.Data()->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 dom } // namespace mozilla diff --git a/dom/media/mediacontrol/MediaPlaybackStatus.h b/dom/media/mediacontrol/MediaPlaybackStatus.h index a7b8834a8038..8fe467f8154e 100644 --- a/dom/media/mediacontrol/MediaPlaybackStatus.h +++ b/dom/media/mediacontrol/MediaPlaybackStatus.h @@ -27,6 +27,13 @@ namespace dom { * * Use `UpdateMediaXXXState()` to update controlled media status, and use * `IsXXX()` methods to acquire the playback status of the tab. + * + * As we know each context's audible state, we can decide which context should + * owns the audio focus when multiple contexts are all playing audible media at + * the same time. In that cases, the latest context that plays media would own + * the audio focus. When the context owning the audio focus is destroyed, we + * would see if there is another other context still playing audible media, and + * switch the audio focus to another context. */ class MediaPlaybackStatus final { public: @@ -37,6 +44,8 @@ class MediaPlaybackStatus final { bool IsAudible() const; bool IsAnyMediaBeingControlled() const; + Maybe GetAudioFocusOwnerContextId() const; + private: /** * This internal class stores detailed media status of controlled media for @@ -90,8 +99,15 @@ class MediaPlaybackStatus final { ContextMediaInfo& GetNotNullContextInfo(uint64_t aContextId); void DestroyContextInfo(uint64_t aContextId); + void ChooseNewContextToOwnAudioFocus(); + void SetOwningAudioFocusContextId(Maybe&& aContextId); + bool IsContextOwningAudioFocus(uint64_t aContextId) const; + bool ShouldRequestAudioFocusForInfo(const ContextMediaInfo& aInfo) const; + bool ShouldAbandonAudioFocusForInfo(const ContextMediaInfo& aInfo) const; + // This contains all the media status of browsing contexts within a tab. nsDataHashtable> mContextInfoMap; + Maybe mOwningAudioFocusContextId; }; } // namespace dom