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 "ContentMediaController.h"
6
7 #include "MediaControlUtils.h"
8 #include "mozilla/ClearOnShutdown.h"
9 #include "mozilla/dom/BrowsingContext.h"
10 #include "mozilla/dom/CanonicalBrowsingContext.h"
11 #include "mozilla/dom/ContentChild.h"
12 #include "mozilla/StaticPtr.h"
13 #include "nsDataHashtable.h"
14 #include "nsGlobalWindowOuter.h"
15
16 namespace mozilla {
17 namespace dom {
18
19 using ControllerMap =
20 nsDataHashtable<nsUint64HashKey, RefPtr<ContentMediaController>>;
21 static StaticAutoPtr<ControllerMap> sControllers;
22
23 #undef LOG
24 #define LOG(msg, ...) \
25 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
26 ("ContentMediaController=%p, " msg, this, ##__VA_ARGS__))
27
28 static already_AddRefed<ContentMediaController>
GetContentMediaControllerFromBrowsingContext(BrowsingContext * aBrowsingContext)29 GetContentMediaControllerFromBrowsingContext(
30 BrowsingContext* aBrowsingContext) {
31 MOZ_ASSERT(NS_IsMainThread());
32 if (!sControllers) {
33 sControllers = new ControllerMap();
34 ClearOnShutdown(&sControllers);
35 }
36
37 RefPtr<BrowsingContext> topLevelBC =
38 GetAliveTopBrowsingContext(aBrowsingContext);
39 if (!topLevelBC) {
40 return nullptr;
41 }
42
43 const uint64_t topLevelBCId = topLevelBC->Id();
44 RefPtr<ContentMediaController> controller;
45 if (!sControllers->Contains(topLevelBCId)) {
46 controller = new ContentMediaController(topLevelBCId);
47 sControllers->Put(topLevelBCId, controller);
48 } else {
49 controller = sControllers->Get(topLevelBCId);
50 }
51 return controller.forget();
52 }
53
54 /* static */
Get(BrowsingContext * aBC)55 ContentControlKeyEventReceiver* ContentControlKeyEventReceiver::Get(
56 BrowsingContext* aBC) {
57 MOZ_ASSERT(NS_IsMainThread());
58 RefPtr<ContentMediaController> controller =
59 GetContentMediaControllerFromBrowsingContext(aBC);
60 return controller
61 ? static_cast<ContentControlKeyEventReceiver*>(controller.get())
62 : nullptr;
63 }
64
65 /* static */
Get(BrowsingContext * aBC)66 ContentMediaAgent* ContentMediaAgent::Get(BrowsingContext* aBC) {
67 MOZ_ASSERT(NS_IsMainThread());
68 RefPtr<ContentMediaController> controller =
69 GetContentMediaControllerFromBrowsingContext(aBC);
70 return controller ? static_cast<ContentMediaAgent*>(controller.get())
71 : nullptr;
72 }
73
ContentMediaController(uint64_t aId)74 ContentMediaController::ContentMediaController(uint64_t aId)
75 : mTopLevelBrowsingContextId(aId) {}
76
AddReceiver(ContentControlKeyEventReceiver * aListener)77 void ContentMediaController::AddReceiver(
78 ContentControlKeyEventReceiver* aListener) {
79 MOZ_ASSERT(NS_IsMainThread());
80 mReceivers.AppendElement(aListener);
81 }
82
RemoveReceiver(ContentControlKeyEventReceiver * aListener)83 void ContentMediaController::RemoveReceiver(
84 ContentControlKeyEventReceiver* aListener) {
85 MOZ_ASSERT(NS_IsMainThread());
86 mReceivers.RemoveElement(aListener);
87 // No more media needs to be controlled, so we can release this and recreate
88 // it when someone needs it. We have to check `sControllers` because this can
89 // be called via CC after we clear `sControllers`.
90 if (mReceivers.IsEmpty() && sControllers) {
91 sControllers->Remove(mTopLevelBrowsingContextId);
92 }
93 }
94
NotifyPlaybackStateChanged(const ContentControlKeyEventReceiver * aMedia,MediaPlaybackState aState)95 void ContentMediaController::NotifyPlaybackStateChanged(
96 const ContentControlKeyEventReceiver* aMedia, MediaPlaybackState aState) {
97 MOZ_ASSERT(NS_IsMainThread());
98 if (!mReceivers.Contains(aMedia)) {
99 return;
100 }
101
102 RefPtr<BrowsingContext> bc = aMedia->GetBrowsingContext();
103 if (!bc || bc->IsDiscarded()) {
104 return;
105 }
106
107 LOG("Notify media %s in BC %" PRId64, ToMediaPlaybackStateStr(aState),
108 bc->Id());
109 if (XRE_IsContentProcess()) {
110 ContentChild* contentChild = ContentChild::GetSingleton();
111 Unused << contentChild->SendNotifyMediaPlaybackChanged(bc, aState);
112 } else {
113 // Currently this only happen when we disable e10s, otherwise all controlled
114 // media would be run in the content process.
115 if (RefPtr<IMediaInfoUpdater> updater =
116 bc->Canonical()->GetMediaController()) {
117 updater->NotifyMediaPlaybackChanged(bc->Id(), aState);
118 }
119 }
120 }
121
NotifyAudibleStateChanged(const ContentControlKeyEventReceiver * aMedia,MediaAudibleState aState)122 void ContentMediaController::NotifyAudibleStateChanged(
123 const ContentControlKeyEventReceiver* aMedia, MediaAudibleState aState) {
124 MOZ_ASSERT(NS_IsMainThread());
125 if (!mReceivers.Contains(aMedia)) {
126 return;
127 }
128
129 RefPtr<BrowsingContext> bc = aMedia->GetBrowsingContext();
130 if (!bc || bc->IsDiscarded()) {
131 return;
132 }
133
134 LOG("Notify media became %s in BC %" PRId64,
135 aState == MediaAudibleState::eAudible ? "audible" : "inaudible",
136 bc->Id());
137 if (XRE_IsContentProcess()) {
138 ContentChild* contentChild = ContentChild::GetSingleton();
139 Unused << contentChild->SendNotifyMediaAudibleChanged(bc, aState);
140 } else {
141 // Currently this only happen when we disable e10s, otherwise all controlled
142 // media would be run in the content process.
143 if (RefPtr<IMediaInfoUpdater> updater =
144 bc->Canonical()->GetMediaController()) {
145 updater->NotifyMediaAudibleChanged(bc->Id(), aState);
146 }
147 }
148 }
149
NotifyPictureInPictureModeChanged(const ContentControlKeyEventReceiver * aMedia,bool aEnabled)150 void ContentMediaController::NotifyPictureInPictureModeChanged(
151 const ContentControlKeyEventReceiver* aMedia, bool aEnabled) {
152 MOZ_ASSERT(NS_IsMainThread());
153 if (!mReceivers.Contains(aMedia)) {
154 return;
155 }
156
157 RefPtr<BrowsingContext> bc = aMedia->GetBrowsingContext();
158 if (!bc || bc->IsDiscarded()) {
159 return;
160 }
161
162 LOG("Notify media Picture-in-Picture mode '%s' in BC %" PRId64,
163 aEnabled ? "enabled" : "disabled", bc->Id());
164 if (XRE_IsContentProcess()) {
165 ContentChild* contentChild = ContentChild::GetSingleton();
166 Unused << contentChild->SendNotifyPictureInPictureModeChanged(bc, aEnabled);
167 } else {
168 // Currently this only happen when we disable e10s, otherwise all controlled
169 // media would be run in the content process.
170 if (RefPtr<IMediaInfoUpdater> updater =
171 bc->Canonical()->GetMediaController()) {
172 updater->SetIsInPictureInPictureMode(aEnabled);
173 }
174 }
175 }
176
HandleEvent(MediaControlKeysEvent aEvent)177 void ContentMediaController::HandleEvent(MediaControlKeysEvent aEvent) {
178 MOZ_ASSERT(NS_IsMainThread());
179 LOG("Handle '%s' event, receiver num=%zu", ToMediaControlKeysEventStr(aEvent),
180 mReceivers.Length());
181 for (auto& receiver : mReceivers) {
182 receiver->HandleEvent(aEvent);
183 }
184 }
185
186 } // namespace dom
187 } // namespace mozilla
188