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
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include "AudioChannelAgent.h"
6 #include "AudioChannelService.h"
7 #include "mozilla/Preferences.h"
8 #include "nsContentUtils.h"
9 #include "mozilla/dom/Document.h"
10 #include "nsPIDOMWindow.h"
11
12 using namespace mozilla::dom;
13
14 NS_IMPL_CYCLE_COLLECTION_CLASS(AudioChannelAgent)
15
16 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioChannelAgent)
17 tmp->Shutdown();
18 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)19 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
20 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
21
22 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioChannelAgent)
23 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
24 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
25 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
26
27 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioChannelAgent)
28 NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgent)
29 NS_INTERFACE_MAP_ENTRY(nsISupports)
30 NS_INTERFACE_MAP_END
31
32 NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioChannelAgent)
33 NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioChannelAgent)
34
35 AudioChannelAgent::AudioChannelAgent()
36 : mInnerWindowID(0), mIsRegToService(false) {
37 // Init service in the begining, it can help us to know whether there is any
38 // created media component via AudioChannelService::IsServiceStarted().
39 RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
40 }
41
~AudioChannelAgent()42 AudioChannelAgent::~AudioChannelAgent() { Shutdown(); }
43
Shutdown()44 void AudioChannelAgent::Shutdown() {
45 if (mIsRegToService) {
46 NotifyStoppedPlaying();
47 }
48 }
49
50 NS_IMETHODIMP
Init(mozIDOMWindow * aWindow,nsIAudioChannelAgentCallback * aCallback)51 AudioChannelAgent::Init(mozIDOMWindow* aWindow,
52 nsIAudioChannelAgentCallback* aCallback) {
53 return InitInternal(nsPIDOMWindowInner::From(aWindow), aCallback,
54 /* useWeakRef = */ false);
55 }
56
57 NS_IMETHODIMP
InitWithWeakCallback(mozIDOMWindow * aWindow,nsIAudioChannelAgentCallback * aCallback)58 AudioChannelAgent::InitWithWeakCallback(
59 mozIDOMWindow* aWindow, nsIAudioChannelAgentCallback* aCallback) {
60 return InitInternal(nsPIDOMWindowInner::From(aWindow), aCallback,
61 /* useWeakRef = */ true);
62 }
63
FindCorrectWindow(nsPIDOMWindowInner * aWindow)64 nsresult AudioChannelAgent::FindCorrectWindow(nsPIDOMWindowInner* aWindow) {
65 mWindow = aWindow->GetInProcessScriptableTop();
66 if (NS_WARN_IF(!mWindow)) {
67 return NS_OK;
68 }
69
70 // From here we do an hack for nested iframes.
71 // The system app doesn't have access to the nested iframe objects so it
72 // cannot control the volume of the agents running in nested apps. What we do
73 // here is to assign those Agents to the top scriptable window of the parent
74 // iframe (what is controlled by the system app).
75 // For doing this we go recursively back into the chain of windows until we
76 // find apps that are not the system one.
77 nsCOMPtr<nsPIDOMWindowOuter> outerParent = mWindow->GetInProcessParent();
78 if (!outerParent || outerParent == mWindow) {
79 return NS_OK;
80 }
81
82 nsCOMPtr<nsPIDOMWindowInner> parent = outerParent->GetCurrentInnerWindow();
83 if (!parent) {
84 return NS_OK;
85 }
86
87 nsCOMPtr<Document> doc = parent->GetExtantDoc();
88 if (!doc) {
89 return NS_OK;
90 }
91
92 if (nsContentUtils::IsChromeDoc(doc)) {
93 return NS_OK;
94 }
95
96 return FindCorrectWindow(parent);
97 }
98
InitInternal(nsPIDOMWindowInner * aWindow,nsIAudioChannelAgentCallback * aCallback,bool aUseWeakRef)99 nsresult AudioChannelAgent::InitInternal(
100 nsPIDOMWindowInner* aWindow, nsIAudioChannelAgentCallback* aCallback,
101 bool aUseWeakRef) {
102 if (NS_WARN_IF(!aWindow)) {
103 return NS_ERROR_FAILURE;
104 }
105
106 mInnerWindowID = aWindow->WindowID();
107
108 nsresult rv = FindCorrectWindow(aWindow);
109 if (NS_WARN_IF(NS_FAILED(rv))) {
110 return rv;
111 }
112
113 if (aUseWeakRef) {
114 mWeakCallback = do_GetWeakReference(aCallback);
115 } else {
116 mCallback = aCallback;
117 }
118
119 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
120 ("AudioChannelAgent, InitInternal, this = %p, "
121 "owner = %p, hasCallback = %d\n",
122 this, mWindow.get(), (!!mCallback || !!mWeakCallback)));
123
124 return NS_OK;
125 }
126
PullInitialUpdate()127 void AudioChannelAgent::PullInitialUpdate() {
128 RefPtr<AudioChannelService> service = AudioChannelService::Get();
129 MOZ_ASSERT(service);
130 MOZ_ASSERT(mIsRegToService);
131
132 AudioPlaybackConfig config = service->GetMediaConfig(mWindow);
133 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
134 ("AudioChannelAgent, PullInitialUpdate, this=%p, "
135 "mute=%s, volume=%f, suspend=%s, audioCapturing=%s\n",
136 this, config.mMuted ? "true" : "false", config.mVolume,
137 SuspendTypeToStr(config.mSuspend),
138 config.mCapturedAudio ? "true" : "false"));
139 WindowVolumeChanged(config.mVolume, config.mMuted);
140 WindowSuspendChanged(config.mSuspend);
141 WindowAudioCaptureChanged(InnerWindowID(), config.mCapturedAudio);
142 }
143
144 NS_IMETHODIMP
NotifyStartedPlaying(uint8_t aAudible)145 AudioChannelAgent::NotifyStartedPlaying(uint8_t aAudible) {
146 RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
147 if (service == nullptr || mIsRegToService) {
148 return NS_ERROR_FAILURE;
149 }
150
151 MOZ_ASSERT(AudioChannelService::AudibleState::eNotAudible == 0 &&
152 AudioChannelService::AudibleState::eMaybeAudible == 1 &&
153 AudioChannelService::AudibleState::eAudible == 2);
154 service->RegisterAudioChannelAgent(
155 this, static_cast<AudioChannelService::AudibleState>(aAudible));
156
157 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
158 ("AudioChannelAgent, NotifyStartedPlaying, this = %p, audible = %s\n",
159 this,
160 AudibleStateToStr(
161 static_cast<AudioChannelService::AudibleState>(aAudible))));
162
163 mIsRegToService = true;
164 return NS_OK;
165 }
166
167 NS_IMETHODIMP
NotifyStoppedPlaying()168 AudioChannelAgent::NotifyStoppedPlaying() {
169 if (!mIsRegToService) {
170 return NS_ERROR_FAILURE;
171 }
172
173 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
174 ("AudioChannelAgent, NotifyStoppedPlaying, this = %p\n", this));
175
176 RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
177 if (service) {
178 service->UnregisterAudioChannelAgent(this);
179 }
180
181 mIsRegToService = false;
182 return NS_OK;
183 }
184
185 NS_IMETHODIMP
NotifyStartedAudible(uint8_t aAudible,uint32_t aReason)186 AudioChannelAgent::NotifyStartedAudible(uint8_t aAudible, uint32_t aReason) {
187 MOZ_LOG(
188 AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
189 ("AudioChannelAgent, NotifyStartedAudible, this = %p, "
190 "audible = %s, reason = %s\n",
191 this,
192 AudibleStateToStr(
193 static_cast<AudioChannelService::AudibleState>(aAudible)),
194 AudibleChangedReasonToStr(
195 static_cast<AudioChannelService::AudibleChangedReasons>(aReason))));
196
197 RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
198 if (NS_WARN_IF(!service)) {
199 return NS_ERROR_FAILURE;
200 }
201
202 service->AudioAudibleChanged(
203 this, static_cast<AudioChannelService::AudibleState>(aAudible),
204 static_cast<AudioChannelService::AudibleChangedReasons>(aReason));
205 return NS_OK;
206 }
207
208 already_AddRefed<nsIAudioChannelAgentCallback>
GetCallback()209 AudioChannelAgent::GetCallback() {
210 nsCOMPtr<nsIAudioChannelAgentCallback> callback = mCallback;
211 if (!callback) {
212 callback = do_QueryReferent(mWeakCallback);
213 }
214 return callback.forget();
215 }
216
WindowVolumeChanged(float aVolume,bool aMuted)217 void AudioChannelAgent::WindowVolumeChanged(float aVolume, bool aMuted) {
218 nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
219 if (!callback) {
220 return;
221 }
222
223 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
224 ("AudioChannelAgent, WindowVolumeChanged, this = %p, mute = %s, "
225 "volume = %f\n",
226 this, aMuted ? "true" : "false", aVolume));
227 callback->WindowVolumeChanged(aVolume, aMuted);
228 }
229
WindowSuspendChanged(nsSuspendedTypes aSuspend)230 void AudioChannelAgent::WindowSuspendChanged(nsSuspendedTypes aSuspend) {
231 nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
232 if (!callback) {
233 return;
234 }
235
236 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
237 ("AudioChannelAgent, WindowSuspendChanged, this = %p, "
238 "suspended = %s\n",
239 this, SuspendTypeToStr(aSuspend)));
240 callback->WindowSuspendChanged(aSuspend);
241 }
242
GetMediaConfig() const243 AudioPlaybackConfig AudioChannelAgent::GetMediaConfig() const {
244 RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
245 AudioPlaybackConfig config(1.0, false, nsISuspendedTypes::NONE_SUSPENDED);
246 if (service) {
247 config = service->GetMediaConfig(mWindow);
248 }
249 return config;
250 }
251
WindowID() const252 uint64_t AudioChannelAgent::WindowID() const {
253 return mWindow ? mWindow->WindowID() : 0;
254 }
255
InnerWindowID() const256 uint64_t AudioChannelAgent::InnerWindowID() const { return mInnerWindowID; }
257
WindowAudioCaptureChanged(uint64_t aInnerWindowID,bool aCapture)258 void AudioChannelAgent::WindowAudioCaptureChanged(uint64_t aInnerWindowID,
259 bool aCapture) {
260 if (aInnerWindowID != mInnerWindowID) {
261 return;
262 }
263
264 nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
265 if (!callback) {
266 return;
267 }
268
269 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
270 ("AudioChannelAgent, WindowAudioCaptureChanged, this = %p, "
271 "capture = %d\n",
272 this, aCapture));
273
274 callback->WindowAudioCaptureChanged(aCapture);
275 }
276
IsWindowAudioCapturingEnabled() const277 bool AudioChannelAgent::IsWindowAudioCapturingEnabled() const {
278 return GetMediaConfig().mCapturedAudio;
279 }
280
IsPlayingStarted() const281 bool AudioChannelAgent::IsPlayingStarted() const { return mIsRegToService; }
282