1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "AudioChannelService.h"
8
9 #include "base/basictypes.h"
10
11 #include "mozilla/Services.h"
12 #include "mozilla/StaticPtr.h"
13 #include "mozilla/Unused.h"
14 #include "mozilla/dom/Document.h"
15
16 #include "nsContentUtils.h"
17 #include "nsISupportsPrimitives.h"
18 #include "nsThreadUtils.h"
19 #include "nsHashPropertyBag.h"
20 #include "nsComponentManagerUtils.h"
21 #include "nsGlobalWindow.h"
22 #include "nsPIDOMWindow.h"
23 #include "nsServiceManagerUtils.h"
24
25 #include "mozilla/Preferences.h"
26
27 using namespace mozilla;
28 using namespace mozilla::dom;
29
30 mozilla::LazyLogModule gAudioChannelLog("AudioChannel");
31
32 namespace {
33
34 bool sXPCOMShuttingDown = false;
35
36 class AudioPlaybackRunnable final : public Runnable {
37 public:
AudioPlaybackRunnable(nsPIDOMWindowOuter * aWindow,bool aActive,AudioChannelService::AudibleChangedReasons aReason)38 AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive,
39 AudioChannelService::AudibleChangedReasons aReason)
40 : mozilla::Runnable("AudioPlaybackRunnable"),
41 mWindow(aWindow),
42 mActive(aActive),
43 mReason(aReason) {}
44
Run()45 NS_IMETHOD Run() override {
46 nsCOMPtr<nsIObserverService> observerService =
47 services::GetObserverService();
48 if (NS_WARN_IF(!observerService)) {
49 return NS_ERROR_FAILURE;
50 }
51
52 nsAutoString state;
53 GetActiveState(state);
54
55 observerService->NotifyObservers(ToSupports(mWindow), "audio-playback",
56 state.get());
57
58 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
59 ("AudioPlaybackRunnable, active = %s, reason = %s\n",
60 mActive ? "true" : "false", AudibleChangedReasonToStr(mReason)));
61
62 return NS_OK;
63 }
64
65 private:
GetActiveState(nsAString & aState)66 void GetActiveState(nsAString& aState) {
67 if (mActive) {
68 aState.AssignLiteral("active");
69 } else {
70 if (mReason ==
71 AudioChannelService::AudibleChangedReasons::ePauseStateChanged) {
72 aState.AssignLiteral("inactive-pause");
73 } else {
74 aState.AssignLiteral("inactive-nonaudible");
75 }
76 }
77 }
78
79 nsCOMPtr<nsPIDOMWindowOuter> mWindow;
80 bool mActive;
81 AudioChannelService::AudibleChangedReasons mReason;
82 };
83
84 } // anonymous namespace
85
86 namespace mozilla::dom {
87
SuspendTypeToStr(const nsSuspendedTypes & aSuspend)88 const char* SuspendTypeToStr(const nsSuspendedTypes& aSuspend) {
89 MOZ_ASSERT(aSuspend == nsISuspendedTypes::NONE_SUSPENDED ||
90 aSuspend == nsISuspendedTypes::SUSPENDED_BLOCK);
91
92 switch (aSuspend) {
93 case nsISuspendedTypes::NONE_SUSPENDED:
94 return "none";
95 case nsISuspendedTypes::SUSPENDED_BLOCK:
96 return "block";
97 default:
98 return "unknown";
99 }
100 }
101
AudibleStateToStr(const AudioChannelService::AudibleState & aAudible)102 const char* AudibleStateToStr(
103 const AudioChannelService::AudibleState& aAudible) {
104 MOZ_ASSERT(aAudible == AudioChannelService::AudibleState::eNotAudible ||
105 aAudible == AudioChannelService::AudibleState::eMaybeAudible ||
106 aAudible == AudioChannelService::AudibleState::eAudible);
107
108 switch (aAudible) {
109 case AudioChannelService::AudibleState::eNotAudible:
110 return "not-audible";
111 case AudioChannelService::AudibleState::eMaybeAudible:
112 return "maybe-audible";
113 case AudioChannelService::AudibleState::eAudible:
114 return "audible";
115 default:
116 return "unknown";
117 }
118 }
119
AudibleChangedReasonToStr(const AudioChannelService::AudibleChangedReasons & aReason)120 const char* AudibleChangedReasonToStr(
121 const AudioChannelService::AudibleChangedReasons& aReason) {
122 MOZ_ASSERT(
123 aReason == AudioChannelService::AudibleChangedReasons::eVolumeChanged ||
124 aReason ==
125 AudioChannelService::AudibleChangedReasons::eDataAudibleChanged ||
126 aReason ==
127 AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
128
129 switch (aReason) {
130 case AudioChannelService::AudibleChangedReasons::eVolumeChanged:
131 return "volume";
132 case AudioChannelService::AudibleChangedReasons::eDataAudibleChanged:
133 return "data-audible";
134 case AudioChannelService::AudibleChangedReasons::ePauseStateChanged:
135 return "pause-state";
136 default:
137 return "unknown";
138 }
139 }
140
141 StaticRefPtr<AudioChannelService> gAudioChannelService;
142
143 /* static */
CreateServiceIfNeeded()144 void AudioChannelService::CreateServiceIfNeeded() {
145 MOZ_ASSERT(NS_IsMainThread());
146
147 if (!gAudioChannelService) {
148 gAudioChannelService = new AudioChannelService();
149 }
150 }
151
152 /* static */
GetOrCreate()153 already_AddRefed<AudioChannelService> AudioChannelService::GetOrCreate() {
154 if (sXPCOMShuttingDown) {
155 return nullptr;
156 }
157
158 CreateServiceIfNeeded();
159 RefPtr<AudioChannelService> service = gAudioChannelService.get();
160 return service.forget();
161 }
162
163 /* static */
Get()164 already_AddRefed<AudioChannelService> AudioChannelService::Get() {
165 if (sXPCOMShuttingDown) {
166 return nullptr;
167 }
168
169 RefPtr<AudioChannelService> service = gAudioChannelService.get();
170 return service.forget();
171 }
172
173 /* static */
GetAudioChannelLog()174 LogModule* AudioChannelService::GetAudioChannelLog() {
175 return gAudioChannelLog;
176 }
177
178 /* static */
Shutdown()179 void AudioChannelService::Shutdown() {
180 if (gAudioChannelService) {
181 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
182 if (obs) {
183 obs->RemoveObserver(gAudioChannelService, "xpcom-shutdown");
184 obs->RemoveObserver(gAudioChannelService, "outer-window-destroyed");
185 }
186
187 gAudioChannelService->mWindows.Clear();
188
189 gAudioChannelService = nullptr;
190 }
191 }
192
193 NS_INTERFACE_MAP_BEGIN(AudioChannelService)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsIObserver)194 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
195 NS_INTERFACE_MAP_ENTRY(nsIObserver)
196 NS_INTERFACE_MAP_END
197
198 NS_IMPL_ADDREF(AudioChannelService)
199 NS_IMPL_RELEASE(AudioChannelService)
200
201 AudioChannelService::AudioChannelService() {
202 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
203 if (obs) {
204 obs->AddObserver(this, "xpcom-shutdown", false);
205 obs->AddObserver(this, "outer-window-destroyed", false);
206 }
207 }
208
209 AudioChannelService::~AudioChannelService() = default;
210
RegisterAudioChannelAgent(AudioChannelAgent * aAgent,AudibleState aAudible)211 void AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
212 AudibleState aAudible) {
213 MOZ_ASSERT(aAgent);
214
215 uint64_t windowID = aAgent->WindowID();
216 AudioChannelWindow* winData = GetWindowData(windowID);
217 if (!winData) {
218 winData = new AudioChannelWindow(windowID);
219 mWindows.AppendElement(WrapUnique(winData));
220 }
221
222 // To make sure agent would be alive because AppendAgent() would trigger the
223 // callback function of AudioChannelAgentOwner that means the agent might be
224 // released in their callback.
225 RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
226 winData->AppendAgent(aAgent, aAudible);
227 }
228
UnregisterAudioChannelAgent(AudioChannelAgent * aAgent)229 void AudioChannelService::UnregisterAudioChannelAgent(
230 AudioChannelAgent* aAgent) {
231 MOZ_ASSERT(aAgent);
232
233 AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
234 if (!winData) {
235 return;
236 }
237
238 // To make sure agent would be alive because AppendAgent() would trigger the
239 // callback function of AudioChannelAgentOwner that means the agent might be
240 // released in their callback.
241 RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
242 winData->RemoveAgent(aAgent);
243 }
244
GetMediaConfig(nsPIDOMWindowOuter * aWindow) const245 AudioPlaybackConfig AudioChannelService::GetMediaConfig(
246 nsPIDOMWindowOuter* aWindow) const {
247 AudioPlaybackConfig config(1.0, false, nsISuspendedTypes::NONE_SUSPENDED);
248
249 if (!aWindow) {
250 config.mVolume = 0.0;
251 config.mMuted = true;
252 config.mSuspend = nsISuspendedTypes::SUSPENDED_BLOCK;
253 return config;
254 }
255
256 AudioChannelWindow* winData = nullptr;
257 nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
258
259 // The volume must be calculated based on the window hierarchy. Here we go up
260 // to the top window and we calculate the volume and the muted flag.
261 do {
262 winData = GetWindowData(window->WindowID());
263 if (winData) {
264 config.mVolume *= winData->mConfig.mVolume;
265 config.mMuted = config.mMuted || winData->mConfig.mMuted;
266 config.mCapturedAudio = winData->mIsAudioCaptured;
267 }
268
269 config.mMuted = config.mMuted || window->GetAudioMuted();
270 if (window->GetMediaSuspend() != nsISuspendedTypes::NONE_SUSPENDED) {
271 config.mSuspend = window->GetMediaSuspend();
272 }
273
274 nsCOMPtr<nsPIDOMWindowOuter> win =
275 window->GetInProcessScriptableParentOrNull();
276 if (!win) {
277 break;
278 }
279
280 window = win;
281
282 // If there is no parent, or we are the toplevel we don't continue.
283 } while (window && window != aWindow);
284
285 return config;
286 }
287
AudioAudibleChanged(AudioChannelAgent * aAgent,AudibleState aAudible,AudibleChangedReasons aReason)288 void AudioChannelService::AudioAudibleChanged(AudioChannelAgent* aAgent,
289 AudibleState aAudible,
290 AudibleChangedReasons aReason) {
291 MOZ_ASSERT(aAgent);
292
293 uint64_t windowID = aAgent->WindowID();
294 AudioChannelWindow* winData = GetWindowData(windowID);
295 if (winData) {
296 winData->AudioAudibleChanged(aAgent, aAudible, aReason);
297 }
298 }
299
300 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)301 AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic,
302 const char16_t* aData) {
303 if (!strcmp(aTopic, "xpcom-shutdown")) {
304 sXPCOMShuttingDown = true;
305 Shutdown();
306 } else if (!strcmp(aTopic, "outer-window-destroyed")) {
307 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
308 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
309
310 uint64_t outerID;
311 nsresult rv = wrapper->GetData(&outerID);
312 if (NS_WARN_IF(NS_FAILED(rv))) {
313 return rv;
314 }
315
316 UniquePtr<AudioChannelWindow> winData;
317 {
318 nsTObserverArray<UniquePtr<AudioChannelWindow>>::ForwardIterator iter(
319 mWindows);
320 while (iter.HasMore()) {
321 auto& next = iter.GetNext();
322 if (next->mWindowID == outerID) {
323 winData = std::move(next);
324 iter.Remove();
325 break;
326 }
327 }
328 }
329
330 if (winData) {
331 for (AudioChannelAgent* agent : winData->mAgents.ForwardRange()) {
332 agent->WindowVolumeChanged(winData->mConfig.mVolume,
333 winData->mConfig.mMuted);
334 }
335 }
336 }
337
338 return NS_OK;
339 }
340
RefreshAgents(nsPIDOMWindowOuter * aWindow,const std::function<void (AudioChannelAgent *)> & aFunc)341 void AudioChannelService::RefreshAgents(
342 nsPIDOMWindowOuter* aWindow,
343 const std::function<void(AudioChannelAgent*)>& aFunc) {
344 MOZ_ASSERT(aWindow);
345
346 nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
347 if (!topWindow) {
348 return;
349 }
350
351 AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
352 if (!winData) {
353 return;
354 }
355
356 for (AudioChannelAgent* agent : winData->mAgents.ForwardRange()) {
357 aFunc(agent);
358 }
359 }
360
RefreshAgentsVolume(nsPIDOMWindowOuter * aWindow,float aVolume,bool aMuted)361 void AudioChannelService::RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow,
362 float aVolume, bool aMuted) {
363 RefreshAgents(aWindow, [aVolume, aMuted](AudioChannelAgent* agent) {
364 agent->WindowVolumeChanged(aVolume, aMuted);
365 });
366 }
367
RefreshAgentsSuspend(nsPIDOMWindowOuter * aWindow,nsSuspendedTypes aSuspend)368 void AudioChannelService::RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow,
369 nsSuspendedTypes aSuspend) {
370 RefreshAgents(aWindow, [aSuspend](AudioChannelAgent* agent) {
371 agent->WindowSuspendChanged(aSuspend);
372 });
373 }
374
SetWindowAudioCaptured(nsPIDOMWindowOuter * aWindow,uint64_t aInnerWindowID,bool aCapture)375 void AudioChannelService::SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
376 uint64_t aInnerWindowID,
377 bool aCapture) {
378 MOZ_ASSERT(NS_IsMainThread());
379 MOZ_ASSERT(aWindow);
380
381 MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
382 ("AudioChannelService, SetWindowAudioCaptured, window = %p, "
383 "aCapture = %d\n",
384 aWindow, aCapture));
385
386 nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
387 if (!topWindow) {
388 return;
389 }
390
391 AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
392
393 // This can happen, but only during shutdown, because the the outer window
394 // changes ScriptableTop, so that its ID is different.
395 // In this case either we are capturing, and it's too late because the window
396 // has been closed anyways, or we are un-capturing, and everything has already
397 // been cleaned up by the HTMLMediaElements or the AudioContexts.
398 if (!winData) {
399 return;
400 }
401
402 if (aCapture != winData->mIsAudioCaptured) {
403 winData->mIsAudioCaptured = aCapture;
404 for (AudioChannelAgent* agent : winData->mAgents.ForwardRange()) {
405 agent->WindowAudioCaptureChanged(aInnerWindowID, aCapture);
406 }
407 }
408 }
409
410 AudioChannelService::AudioChannelWindow*
GetOrCreateWindowData(nsPIDOMWindowOuter * aWindow)411 AudioChannelService::GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow) {
412 MOZ_ASSERT(NS_IsMainThread());
413 MOZ_ASSERT(aWindow);
414
415 AudioChannelWindow* winData = GetWindowData(aWindow->WindowID());
416 if (!winData) {
417 winData = new AudioChannelWindow(aWindow->WindowID());
418 mWindows.AppendElement(WrapUnique(winData));
419 }
420
421 return winData;
422 }
423
GetWindowData(uint64_t aWindowID) const424 AudioChannelService::AudioChannelWindow* AudioChannelService::GetWindowData(
425 uint64_t aWindowID) const {
426 const auto [begin, end] = mWindows.NonObservingRange();
427 const auto foundIt = std::find_if(begin, end, [aWindowID](const auto& next) {
428 return next->mWindowID == aWindowID;
429 });
430 return foundIt != end ? foundIt->get() : nullptr;
431 }
432
IsWindowActive(nsPIDOMWindowOuter * aWindow)433 bool AudioChannelService::IsWindowActive(nsPIDOMWindowOuter* aWindow) {
434 MOZ_ASSERT(NS_IsMainThread());
435
436 auto* window = nsPIDOMWindowOuter::From(aWindow)->GetInProcessScriptableTop();
437 if (!window) {
438 return false;
439 }
440
441 AudioChannelWindow* winData = GetWindowData(window->WindowID());
442 if (!winData) {
443 return false;
444 }
445
446 return !winData->mAudibleAgents.IsEmpty();
447 }
448
NotifyMediaResumedFromBlock(nsPIDOMWindowOuter * aWindow)449 void AudioChannelService::NotifyMediaResumedFromBlock(
450 nsPIDOMWindowOuter* aWindow) {
451 MOZ_ASSERT(aWindow);
452
453 nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
454 if (!topWindow) {
455 return;
456 }
457
458 AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
459 if (!winData) {
460 return;
461 }
462
463 winData->NotifyMediaBlockStop(aWindow);
464 }
465
AppendAgent(AudioChannelAgent * aAgent,AudibleState aAudible)466 void AudioChannelService::AudioChannelWindow::AppendAgent(
467 AudioChannelAgent* aAgent, AudibleState aAudible) {
468 MOZ_ASSERT(aAgent);
469
470 AppendAgentAndIncreaseAgentsNum(aAgent);
471 AudioAudibleChanged(aAgent, aAudible,
472 AudibleChangedReasons::eDataAudibleChanged);
473 }
474
RemoveAgent(AudioChannelAgent * aAgent)475 void AudioChannelService::AudioChannelWindow::RemoveAgent(
476 AudioChannelAgent* aAgent) {
477 MOZ_ASSERT(aAgent);
478
479 RemoveAgentAndReduceAgentsNum(aAgent);
480 AudioAudibleChanged(aAgent, AudibleState::eNotAudible,
481 AudibleChangedReasons::ePauseStateChanged);
482 }
483
NotifyMediaBlockStop(nsPIDOMWindowOuter * aWindow)484 void AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop(
485 nsPIDOMWindowOuter* aWindow) {
486 if (mShouldSendActiveMediaBlockStopEvent) {
487 mShouldSendActiveMediaBlockStopEvent = false;
488 nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
489 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
490 "dom::AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop",
491 [window]() -> void {
492 nsCOMPtr<nsIObserverService> observerService =
493 services::GetObserverService();
494 if (NS_WARN_IF(!observerService)) {
495 return;
496 }
497
498 observerService->NotifyObservers(ToSupports(window), "audio-playback",
499 u"activeMediaBlockStop");
500 }));
501 }
502 }
503
AppendAgentAndIncreaseAgentsNum(AudioChannelAgent * aAgent)504 void AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(
505 AudioChannelAgent* aAgent) {
506 MOZ_ASSERT(aAgent);
507 MOZ_ASSERT(!mAgents.Contains(aAgent));
508
509 mAgents.AppendElement(aAgent);
510
511 ++mConfig.mNumberOfAgents;
512 }
513
RemoveAgentAndReduceAgentsNum(AudioChannelAgent * aAgent)514 void AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(
515 AudioChannelAgent* aAgent) {
516 MOZ_ASSERT(aAgent);
517 MOZ_ASSERT(mAgents.Contains(aAgent));
518
519 mAgents.RemoveElement(aAgent);
520
521 MOZ_ASSERT(mConfig.mNumberOfAgents > 0);
522 --mConfig.mNumberOfAgents;
523 }
524
AudioAudibleChanged(AudioChannelAgent * aAgent,AudibleState aAudible,AudibleChangedReasons aReason)525 void AudioChannelService::AudioChannelWindow::AudioAudibleChanged(
526 AudioChannelAgent* aAgent, AudibleState aAudible,
527 AudibleChangedReasons aReason) {
528 MOZ_ASSERT(aAgent);
529
530 if (aAudible == AudibleState::eAudible) {
531 AppendAudibleAgentIfNotContained(aAgent, aReason);
532 } else {
533 RemoveAudibleAgentIfContained(aAgent, aReason);
534 }
535
536 if (aAudible != AudibleState::eNotAudible) {
537 MaybeNotifyMediaBlockStart(aAgent);
538 }
539 }
540
AppendAudibleAgentIfNotContained(AudioChannelAgent * aAgent,AudibleChangedReasons aReason)541 void AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(
542 AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
543 MOZ_ASSERT(aAgent);
544 MOZ_ASSERT(mAgents.Contains(aAgent));
545
546 if (!mAudibleAgents.Contains(aAgent)) {
547 mAudibleAgents.AppendElement(aAgent);
548 if (IsFirstAudibleAgent()) {
549 NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible,
550 aReason);
551 }
552 }
553 }
554
RemoveAudibleAgentIfContained(AudioChannelAgent * aAgent,AudibleChangedReasons aReason)555 void AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(
556 AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
557 MOZ_ASSERT(aAgent);
558
559 if (mAudibleAgents.Contains(aAgent)) {
560 mAudibleAgents.RemoveElement(aAgent);
561 if (IsLastAudibleAgent()) {
562 NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible,
563 aReason);
564 }
565 }
566 }
567
IsFirstAudibleAgent() const568 bool AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const {
569 return (mAudibleAgents.Length() == 1);
570 }
571
IsLastAudibleAgent() const572 bool AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const {
573 return mAudibleAgents.IsEmpty();
574 }
575
NotifyAudioAudibleChanged(nsPIDOMWindowOuter * aWindow,AudibleState aAudible,AudibleChangedReasons aReason)576 void AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(
577 nsPIDOMWindowOuter* aWindow, AudibleState aAudible,
578 AudibleChangedReasons aReason) {
579 RefPtr<AudioPlaybackRunnable> runnable = new AudioPlaybackRunnable(
580 aWindow, aAudible == AudibleState::eAudible, aReason);
581 DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
582 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
583 }
584
MaybeNotifyMediaBlockStart(AudioChannelAgent * aAgent)585 void AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlockStart(
586 AudioChannelAgent* aAgent) {
587 nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
588 if (!window) {
589 return;
590 }
591
592 nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
593 if (!inner) {
594 return;
595 }
596
597 nsCOMPtr<Document> doc = inner->GetExtantDoc();
598 if (!doc) {
599 return;
600 }
601
602 if (window->GetMediaSuspend() != nsISuspendedTypes::SUSPENDED_BLOCK ||
603 !doc->Hidden()) {
604 return;
605 }
606
607 if (!mShouldSendActiveMediaBlockStopEvent) {
608 mShouldSendActiveMediaBlockStopEvent = true;
609 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
610 "dom::AudioChannelService::AudioChannelWindow::"
611 "MaybeNotifyMediaBlockStart",
612 [window]() -> void {
613 nsCOMPtr<nsIObserverService> observerService =
614 services::GetObserverService();
615 if (NS_WARN_IF(!observerService)) {
616 return;
617 }
618
619 observerService->NotifyObservers(ToSupports(window), "audio-playback",
620 u"activeMediaBlockStart");
621 }));
622 }
623 }
624
625 } // namespace mozilla::dom
626