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/. */
5 #include "MediaPlaybackStatus.h"
7 #include "MediaControlUtils.h"
9 namespace mozilla::dom
{
12 #define LOG(msg, ...) \
13 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
14 ("MediaPlaybackStatus=%p, " msg, this, ##__VA_ARGS__))
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());
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();
30 MOZ_ASSERT(aState
== MediaPlaybackState::ePaused
);
31 info
.DecreasePlayingMediaNum();
34 // The context still has controlled media, we should keep its alive.
35 if (info
.IsAnyMediaBeingControlled()) {
38 MOZ_ASSERT(!info
.IsPlaying());
39 MOZ_ASSERT(!info
.IsAudible());
40 // DO NOT access `info` after this line.
41 DestroyContextInfo(aContextId
);
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();
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();
64 MOZ_ASSERT(aState
== MediaAudibleState::eInaudible
);
65 info
.DecreaseAudibleMediaNum();
67 if (ShouldRequestAudioFocusForInfo(info
)) {
68 SetOwningAudioFocusContextId(Some(aContextId
));
69 } else if (ShouldAbandonAudioFocusForInfo(info
)) {
70 ChooseNewContextToOwnAudioFocus();
74 void MediaPlaybackStatus::UpdateGuessedPositionState(
75 uint64_t aContextId
, const nsID
& aElementId
,
76 const Maybe
<PositionState
>& aState
) {
77 MOZ_ASSERT(NS_IsMainThread());
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
);
84 LOG("Clear guessed position state for context %" PRIu64
" element %s",
85 aContextId
, aElementId
.ToString().get());
87 ContextMediaInfo
& info
= GetNotNullContextInfo(aContextId
);
88 info
.UpdateGuessedPositionState(aElementId
, aState
);
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(); });
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(); });
105 bool MediaPlaybackStatus::IsAnyMediaBeingControlled() const {
106 MOZ_ASSERT(NS_IsMainThread());
108 mContextInfoMap
.Values().cbegin(), mContextInfoMap
.Values().cend(),
109 [](const auto& info
) { return info
->IsAnyMediaBeingControlled(); });
112 Maybe
<PositionState
> MediaPlaybackStatus::GuessedMediaPositionState(
113 Maybe
<uint64_t> aPreferredContextId
) const {
114 auto contextId
= aPreferredContextId
;
116 contextId
= mOwningAudioFocusContextId
;
119 // either the preferred or focused context
121 auto entry
= mContextInfoMap
.Lookup(*contextId
);
125 LOG("Using guessed position state from preferred/focused BC %" PRId64
,
127 return entry
.Data()->GuessedPositionState();
130 // look for the first position state
131 for (const auto& context
: mContextInfoMap
.Values()) {
132 auto state
= context
->GuessedPositionState();
134 LOG("Using guessed position state from BC %" PRId64
, context
->Id());
141 MediaPlaybackStatus::ContextMediaInfo
&
142 MediaPlaybackStatus::GetNotNullContextInfo(uint64_t aContextId
) {
143 MOZ_ASSERT(NS_IsMainThread());
144 return *mContextInfoMap
.GetOrInsertNew(aContextId
, aContextId
);
147 Maybe
<uint64_t> MediaPlaybackStatus::GetAudioFocusOwnerContextId() const {
148 return mOwningAudioFocusContextId
;
151 void MediaPlaybackStatus::ChooseNewContextToOwnAudioFocus() {
152 for (const auto& info
: mContextInfoMap
.Values()) {
153 if (info
->IsAudible()) {
154 SetOwningAudioFocusContextId(Some(info
->Id()));
158 // No context is audible, so no one should the own audio focus.
159 SetOwningAudioFocusContextId(Nothing());
162 void MediaPlaybackStatus::SetOwningAudioFocusContextId(
163 Maybe
<uint64_t>&& aContextId
) {
164 if (mOwningAudioFocusContextId
== aContextId
) {
167 mOwningAudioFocusContextId
= aContextId
;
170 bool MediaPlaybackStatus::ShouldRequestAudioFocusForInfo(
171 const ContextMediaInfo
& aInfo
) const {
172 return aInfo
.IsAudible() && !IsContextOwningAudioFocus(aInfo
.Id());
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()) &&
183 bool MediaPlaybackStatus::IsContextOwningAudioFocus(uint64_t aContextId
) const {
184 return mOwningAudioFocusContextId
? *mOwningAudioFocusContextId
== aContextId
189 MediaPlaybackStatus::ContextMediaInfo::GuessedPositionState() const {
190 if (mGuessedPositionStateMap
.Count() != 1) {
191 LOG("Count is %d", mGuessedPositionStateMap
.Count());
194 return Some(mGuessedPositionStateMap
.begin()->GetData());
197 void MediaPlaybackStatus::ContextMediaInfo::UpdateGuessedPositionState(
198 const nsID
& aElementId
, const Maybe
<PositionState
>& aState
) {
200 mGuessedPositionStateMap
.InsertOrUpdate(aElementId
, *aState
);
202 mGuessedPositionStateMap
.Remove(aElementId
);
206 } // namespace mozilla::dom