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->ShouldDelayMediaFromStart()) {
271 config.mSuspend = nsISuspendedTypes::SUSPENDED_BLOCK;
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
NotifyResumingDelayedMedia(nsPIDOMWindowOuter * aWindow)449 void AudioChannelService::NotifyResumingDelayedMedia(
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 RefreshAgentsSuspend(aWindow, nsISuspendedTypes::NONE_SUSPENDED);
465 }
466
AppendAgent(AudioChannelAgent * aAgent,AudibleState aAudible)467 void AudioChannelService::AudioChannelWindow::AppendAgent(
468 AudioChannelAgent* aAgent, AudibleState aAudible) {
469 MOZ_ASSERT(aAgent);
470
471 AppendAgentAndIncreaseAgentsNum(aAgent);
472 AudioAudibleChanged(aAgent, aAudible,
473 AudibleChangedReasons::eDataAudibleChanged);
474 }
475
RemoveAgent(AudioChannelAgent * aAgent)476 void AudioChannelService::AudioChannelWindow::RemoveAgent(
477 AudioChannelAgent* aAgent) {
478 MOZ_ASSERT(aAgent);
479
480 RemoveAgentAndReduceAgentsNum(aAgent);
481 AudioAudibleChanged(aAgent, AudibleState::eNotAudible,
482 AudibleChangedReasons::ePauseStateChanged);
483 }
484
NotifyMediaBlockStop(nsPIDOMWindowOuter * aWindow)485 void AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop(
486 nsPIDOMWindowOuter* aWindow) {
487 if (mShouldSendActiveMediaBlockStopEvent) {
488 mShouldSendActiveMediaBlockStopEvent = false;
489 nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
490 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
491 "dom::AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop",
492 [window]() -> void {
493 nsCOMPtr<nsIObserverService> observerService =
494 services::GetObserverService();
495 if (NS_WARN_IF(!observerService)) {
496 return;
497 }
498
499 observerService->NotifyObservers(ToSupports(window), "audio-playback",
500 u"activeMediaBlockStop");
501 }));
502 }
503 }
504
AppendAgentAndIncreaseAgentsNum(AudioChannelAgent * aAgent)505 void AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(
506 AudioChannelAgent* aAgent) {
507 MOZ_ASSERT(aAgent);
508 MOZ_ASSERT(!mAgents.Contains(aAgent));
509
510 mAgents.AppendElement(aAgent);
511
512 ++mConfig.mNumberOfAgents;
513 }
514
RemoveAgentAndReduceAgentsNum(AudioChannelAgent * aAgent)515 void AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(
516 AudioChannelAgent* aAgent) {
517 MOZ_ASSERT(aAgent);
518 MOZ_ASSERT(mAgents.Contains(aAgent));
519
520 mAgents.RemoveElement(aAgent);
521
522 MOZ_ASSERT(mConfig.mNumberOfAgents > 0);
523 --mConfig.mNumberOfAgents;
524 }
525
AudioAudibleChanged(AudioChannelAgent * aAgent,AudibleState aAudible,AudibleChangedReasons aReason)526 void AudioChannelService::AudioChannelWindow::AudioAudibleChanged(
527 AudioChannelAgent* aAgent, AudibleState aAudible,
528 AudibleChangedReasons aReason) {
529 MOZ_ASSERT(aAgent);
530
531 if (aAudible == AudibleState::eAudible) {
532 AppendAudibleAgentIfNotContained(aAgent, aReason);
533 } else {
534 RemoveAudibleAgentIfContained(aAgent, aReason);
535 }
536
537 if (aAudible != AudibleState::eNotAudible) {
538 MaybeNotifyMediaBlockStart(aAgent);
539 }
540 }
541
AppendAudibleAgentIfNotContained(AudioChannelAgent * aAgent,AudibleChangedReasons aReason)542 void AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(
543 AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
544 MOZ_ASSERT(aAgent);
545 MOZ_ASSERT(mAgents.Contains(aAgent));
546
547 if (!mAudibleAgents.Contains(aAgent)) {
548 mAudibleAgents.AppendElement(aAgent);
549 if (IsFirstAudibleAgent()) {
550 NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible,
551 aReason);
552 }
553 }
554 }
555
RemoveAudibleAgentIfContained(AudioChannelAgent * aAgent,AudibleChangedReasons aReason)556 void AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(
557 AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
558 MOZ_ASSERT(aAgent);
559
560 if (mAudibleAgents.Contains(aAgent)) {
561 mAudibleAgents.RemoveElement(aAgent);
562 if (IsLastAudibleAgent()) {
563 NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible,
564 aReason);
565 }
566 }
567 }
568
IsFirstAudibleAgent() const569 bool AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const {
570 return (mAudibleAgents.Length() == 1);
571 }
572
IsLastAudibleAgent() const573 bool AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const {
574 return mAudibleAgents.IsEmpty();
575 }
576
NotifyAudioAudibleChanged(nsPIDOMWindowOuter * aWindow,AudibleState aAudible,AudibleChangedReasons aReason)577 void AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(
578 nsPIDOMWindowOuter* aWindow, AudibleState aAudible,
579 AudibleChangedReasons aReason) {
580 RefPtr<AudioPlaybackRunnable> runnable = new AudioPlaybackRunnable(
581 aWindow, aAudible == AudibleState::eAudible, aReason);
582 DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
583 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
584 }
585
MaybeNotifyMediaBlockStart(AudioChannelAgent * aAgent)586 void AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlockStart(
587 AudioChannelAgent* aAgent) {
588 nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
589 if (!window) {
590 return;
591 }
592
593 nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
594 if (!inner) {
595 return;
596 }
597
598 nsCOMPtr<Document> doc = inner->GetExtantDoc();
599 if (!doc) {
600 return;
601 }
602
603 if (!window->ShouldDelayMediaFromStart() || !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