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