MediaPlaybackStatus.cpp (7155B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "MediaPlaybackStatus.h" 6 7 #include "MediaControlUtils.h" 8 9 namespace mozilla::dom { 10 11 #undef LOG 12 #define LOG(msg, ...) \ 13 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \ 14 ("MediaPlaybackStatus=%p, " msg, this, ##__VA_ARGS__)) 15 16 void MediaPlaybackStatus::UpdateMediaPlaybackState(uint64_t aContextId, 17 MediaPlaybackState aState) { 18 LOG("Update playback state '%s' for context %" PRIu64, 19 EnumValueToString(aState), aContextId); 20 MOZ_ASSERT(NS_IsMainThread()); 21 22 ContextMediaInfo& info = GetNotNullContextInfo(aContextId); 23 if (aState == MediaPlaybackState::eStarted) { 24 info.IncreaseControlledMediaNum(); 25 } else if (aState == MediaPlaybackState::eStopped) { 26 info.DecreaseControlledMediaNum(); 27 } else if (aState == MediaPlaybackState::ePlayed) { 28 info.IncreasePlayingMediaNum(); 29 } else { 30 MOZ_ASSERT(aState == MediaPlaybackState::ePaused); 31 info.DecreasePlayingMediaNum(); 32 } 33 34 // The context still has controlled media, we should keep its alive. 35 if (info.IsAnyMediaBeingControlled()) { 36 return; 37 } 38 MOZ_ASSERT(!info.IsPlaying()); 39 MOZ_ASSERT(!info.IsAudible()); 40 // DO NOT access `info` after this line. 41 DestroyContextInfo(aContextId); 42 } 43 44 void MediaPlaybackStatus::DestroyContextInfo(uint64_t aContextId) { 45 MOZ_ASSERT(NS_IsMainThread()); 46 LOG("Remove context %" PRIu64, aContextId); 47 mContextInfoMap.Remove(aContextId); 48 // If the removed context is owning the audio focus, we would find another 49 // context to take the audio focus if it's possible. 50 if (IsContextOwningAudioFocus(aContextId)) { 51 ChooseNewContextToOwnAudioFocus(); 52 } 53 } 54 55 void MediaPlaybackStatus::UpdateMediaAudibleState(uint64_t aContextId, 56 MediaAudibleState aState) { 57 LOG("Update audible state '%s' for context %" PRIu64, 58 EnumValueToString(aState), aContextId); 59 MOZ_ASSERT(NS_IsMainThread()); 60 ContextMediaInfo& info = GetNotNullContextInfo(aContextId); 61 if (aState == MediaAudibleState::eAudible) { 62 info.IncreaseAudibleMediaNum(); 63 } else { 64 MOZ_ASSERT(aState == MediaAudibleState::eInaudible); 65 info.DecreaseAudibleMediaNum(); 66 } 67 if (ShouldRequestAudioFocusForInfo(info)) { 68 SetOwningAudioFocusContextId(Some(aContextId)); 69 } else if (ShouldAbandonAudioFocusForInfo(info)) { 70 ChooseNewContextToOwnAudioFocus(); 71 } 72 } 73 74 void MediaPlaybackStatus::UpdateGuessedPositionState( 75 uint64_t aContextId, const nsID& aElementId, 76 const Maybe<PositionState>& aState) { 77 MOZ_ASSERT(NS_IsMainThread()); 78 if (aState) { 79 LOG("Update guessed position state for context %" PRIu64 80 " element %s (duration=%f, playbackRate=%f, position=%f)", 81 aContextId, aElementId.ToString().get(), aState->mDuration, 82 aState->mPlaybackRate, aState->mLastReportedPlaybackPosition); 83 } else { 84 LOG("Clear guessed position state for context %" PRIu64 " element %s", 85 aContextId, aElementId.ToString().get()); 86 } 87 ContextMediaInfo& info = GetNotNullContextInfo(aContextId); 88 info.UpdateGuessedPositionState(aElementId, aState); 89 } 90 91 bool MediaPlaybackStatus::IsPlaying() const { 92 MOZ_ASSERT(NS_IsMainThread()); 93 return std::any_of(mContextInfoMap.Values().cbegin(), 94 mContextInfoMap.Values().cend(), 95 [](const auto& info) { return info->IsPlaying(); }); 96 } 97 98 bool MediaPlaybackStatus::IsAudible() const { 99 MOZ_ASSERT(NS_IsMainThread()); 100 return std::any_of(mContextInfoMap.Values().cbegin(), 101 mContextInfoMap.Values().cend(), 102 [](const auto& info) { return info->IsAudible(); }); 103 } 104 105 bool MediaPlaybackStatus::IsAnyMediaBeingControlled() const { 106 MOZ_ASSERT(NS_IsMainThread()); 107 return std::any_of( 108 mContextInfoMap.Values().cbegin(), mContextInfoMap.Values().cend(), 109 [](const auto& info) { return info->IsAnyMediaBeingControlled(); }); 110 } 111 112 Maybe<PositionState> MediaPlaybackStatus::GuessedMediaPositionState( 113 Maybe<uint64_t> aPreferredContextId) const { 114 auto contextId = aPreferredContextId; 115 if (!contextId) { 116 contextId = mOwningAudioFocusContextId; 117 } 118 119 // either the preferred or focused context 120 if (contextId) { 121 auto entry = mContextInfoMap.Lookup(*contextId); 122 if (!entry) { 123 return Nothing(); 124 } 125 LOG("Using guessed position state from preferred/focused BC %" PRId64, 126 *contextId); 127 return entry.Data()->GuessedPositionState(); 128 } 129 130 // look for the first position state 131 for (const auto& context : mContextInfoMap.Values()) { 132 auto state = context->GuessedPositionState(); 133 if (state) { 134 LOG("Using guessed position state from BC %" PRId64, context->Id()); 135 return state; 136 } 137 } 138 return Nothing(); 139 } 140 141 MediaPlaybackStatus::ContextMediaInfo& 142 MediaPlaybackStatus::GetNotNullContextInfo(uint64_t aContextId) { 143 MOZ_ASSERT(NS_IsMainThread()); 144 return *mContextInfoMap.GetOrInsertNew(aContextId, aContextId); 145 } 146 147 Maybe<uint64_t> MediaPlaybackStatus::GetAudioFocusOwnerContextId() const { 148 return mOwningAudioFocusContextId; 149 } 150 151 void MediaPlaybackStatus::ChooseNewContextToOwnAudioFocus() { 152 for (const auto& info : mContextInfoMap.Values()) { 153 if (info->IsAudible()) { 154 SetOwningAudioFocusContextId(Some(info->Id())); 155 return; 156 } 157 } 158 // No context is audible, so no one should the own audio focus. 159 SetOwningAudioFocusContextId(Nothing()); 160 } 161 162 void MediaPlaybackStatus::SetOwningAudioFocusContextId( 163 Maybe<uint64_t>&& aContextId) { 164 if (mOwningAudioFocusContextId == aContextId) { 165 return; 166 } 167 mOwningAudioFocusContextId = aContextId; 168 } 169 170 bool MediaPlaybackStatus::ShouldRequestAudioFocusForInfo( 171 const ContextMediaInfo& aInfo) const { 172 return aInfo.IsAudible() && !IsContextOwningAudioFocus(aInfo.Id()); 173 } 174 175 bool MediaPlaybackStatus::ShouldAbandonAudioFocusForInfo( 176 const ContextMediaInfo& aInfo) const { 177 // The owner becomes inaudible and there is other context still playing, so we 178 // should switch the audio focus to the audible context. 179 return !aInfo.IsAudible() && IsContextOwningAudioFocus(aInfo.Id()) && 180 IsAudible(); 181 } 182 183 bool MediaPlaybackStatus::IsContextOwningAudioFocus(uint64_t aContextId) const { 184 return mOwningAudioFocusContextId ? *mOwningAudioFocusContextId == aContextId 185 : false; 186 } 187 188 Maybe<PositionState> 189 MediaPlaybackStatus::ContextMediaInfo::GuessedPositionState() const { 190 if (mGuessedPositionStateMap.Count() != 1) { 191 LOG("Count is %d", mGuessedPositionStateMap.Count()); 192 return Nothing(); 193 } 194 return Some(mGuessedPositionStateMap.begin()->GetData()); 195 } 196 197 void MediaPlaybackStatus::ContextMediaInfo::UpdateGuessedPositionState( 198 const nsID& aElementId, const Maybe<PositionState>& aState) { 199 if (aState) { 200 mGuessedPositionStateMap.InsertOrUpdate(aElementId, *aState); 201 } else { 202 mGuessedPositionStateMap.Remove(aElementId); 203 } 204 } 205 206 } // namespace mozilla::dom