Source code

Revision control

Other Tools

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
15
#include "nsContentUtils.h"
16
#include "nsISupportsPrimitives.h"
17
#include "nsThreadUtils.h"
18
#include "nsHashPropertyBag.h"
19
#include "nsComponentManagerUtils.h"
20
#include "nsGlobalWindow.h"
21
#include "nsPIDOMWindow.h"
22
#include "nsServiceManagerUtils.h"
23
24
#include "mozilla/Preferences.h"
25
26
using namespace mozilla;
27
using namespace mozilla::dom;
28
29
mozilla::LazyLogModule gAudioChannelLog("AudioChannel");
30
31
namespace {
32
33
bool sAudioChannelCompeting = false;
34
bool sAudioChannelCompetingAllAgents = false;
35
bool sXPCOMShuttingDown = false;
36
37
class NotifyChannelActiveRunnable final : public Runnable {
38
public:
39
NotifyChannelActiveRunnable(uint64_t aWindowID, bool aActive)
40
: Runnable("NotifyChannelActiveRunnable"),
41
mWindowID(aWindowID),
42
mActive(aActive) {}
43
44
NS_IMETHOD Run() override {
45
nsCOMPtr<nsIObserverService> observerService =
46
services::GetObserverService();
47
if (NS_WARN_IF(!observerService)) {
48
return NS_ERROR_FAILURE;
49
}
50
51
nsCOMPtr<nsISupportsPRUint64> wrapper =
52
do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
53
if (NS_WARN_IF(!wrapper)) {
54
return NS_ERROR_FAILURE;
55
}
56
57
wrapper->SetData(mWindowID);
58
59
observerService->NotifyObservers(wrapper, "media-playback",
60
mActive ? u"active" : u"inactive");
61
62
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
63
("NotifyChannelActiveRunnable, active = %s\n",
64
mActive ? "true" : "false"));
65
66
return NS_OK;
67
}
68
69
private:
70
const uint64_t mWindowID;
71
const bool mActive;
72
};
73
74
class AudioPlaybackRunnable final : public Runnable {
75
public:
76
AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive,
77
AudioChannelService::AudibleChangedReasons aReason)
78
: mozilla::Runnable("AudioPlaybackRunnable"),
79
mWindow(aWindow),
80
mActive(aActive),
81
mReason(aReason) {}
82
83
NS_IMETHOD Run() override {
84
nsCOMPtr<nsIObserverService> observerService =
85
services::GetObserverService();
86
if (NS_WARN_IF(!observerService)) {
87
return NS_ERROR_FAILURE;
88
}
89
90
nsAutoString state;
91
GetActiveState(state);
92
93
observerService->NotifyObservers(ToSupports(mWindow), "audio-playback",
94
state.get());
95
96
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
97
("AudioPlaybackRunnable, active = %s, reason = %s\n",
98
mActive ? "true" : "false", AudibleChangedReasonToStr(mReason)));
99
100
return NS_OK;
101
}
102
103
private:
104
void GetActiveState(nsAString& aState) {
105
if (mActive) {
106
aState.AssignLiteral("active");
107
} else {
108
if (mReason ==
109
AudioChannelService::AudibleChangedReasons::ePauseStateChanged) {
110
aState.AssignLiteral("inactive-pause");
111
} else {
112
aState.AssignLiteral("inactive-nonaudible");
113
}
114
}
115
}
116
117
nsCOMPtr<nsPIDOMWindowOuter> mWindow;
118
bool mActive;
119
AudioChannelService::AudibleChangedReasons mReason;
120
};
121
122
bool IsEnableAudioCompetingForAllAgents() {
123
// In general, the audio competing should only be for audible media and it
124
// helps user can focus on one media at the same time. However, we hope to
125
// treat all media as the same in the mobile device. First reason is we have
126
// media control on fennec and we just want to control one media at once time.
127
// Second reason is to reduce the bandwidth, avoiding to play any non-audible
128
// media in background which user doesn't notice about.
129
#ifdef MOZ_WIDGET_ANDROID
130
return true;
131
#else
132
return sAudioChannelCompetingAllAgents;
133
#endif
134
}
135
136
} // anonymous namespace
137
138
namespace mozilla {
139
namespace dom {
140
141
const char* SuspendTypeToStr(const nsSuspendedTypes& aSuspend) {
142
MOZ_ASSERT(aSuspend == nsISuspendedTypes::NONE_SUSPENDED ||
143
aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE ||
144
aSuspend == nsISuspendedTypes::SUSPENDED_BLOCK ||
145
aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
146
aSuspend == nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE);
147
148
switch (aSuspend) {
149
case nsISuspendedTypes::NONE_SUSPENDED:
150
return "none";
151
case nsISuspendedTypes::SUSPENDED_PAUSE:
152
return "pause";
153
case nsISuspendedTypes::SUSPENDED_BLOCK:
154
return "block";
155
case nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE:
156
return "disposable-pause";
157
case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
158
return "disposable-stop";
159
default:
160
return "unknown";
161
}
162
}
163
164
const char* AudibleStateToStr(
165
const AudioChannelService::AudibleState& aAudible) {
166
MOZ_ASSERT(aAudible == AudioChannelService::AudibleState::eNotAudible ||
167
aAudible == AudioChannelService::AudibleState::eMaybeAudible ||
168
aAudible == AudioChannelService::AudibleState::eAudible);
169
170
switch (aAudible) {
171
case AudioChannelService::AudibleState::eNotAudible:
172
return "not-audible";
173
case AudioChannelService::AudibleState::eMaybeAudible:
174
return "maybe-audible";
175
case AudioChannelService::AudibleState::eAudible:
176
return "audible";
177
default:
178
return "unknown";
179
}
180
}
181
182
const char* AudibleChangedReasonToStr(
183
const AudioChannelService::AudibleChangedReasons& aReason) {
184
MOZ_ASSERT(
185
aReason == AudioChannelService::AudibleChangedReasons::eVolumeChanged ||
186
aReason ==
187
AudioChannelService::AudibleChangedReasons::eDataAudibleChanged ||
188
aReason ==
189
AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
190
191
switch (aReason) {
192
case AudioChannelService::AudibleChangedReasons::eVolumeChanged:
193
return "volume";
194
case AudioChannelService::AudibleChangedReasons::eDataAudibleChanged:
195
return "data-audible";
196
case AudioChannelService::AudibleChangedReasons::ePauseStateChanged:
197
return "pause-state";
198
default:
199
return "unknown";
200
}
201
}
202
203
StaticRefPtr<AudioChannelService> gAudioChannelService;
204
205
/* static */
206
void AudioChannelService::CreateServiceIfNeeded() {
207
MOZ_ASSERT(NS_IsMainThread());
208
209
if (!gAudioChannelService) {
210
gAudioChannelService = new AudioChannelService();
211
}
212
}
213
214
/* static */
215
already_AddRefed<AudioChannelService> AudioChannelService::GetOrCreate() {
216
if (sXPCOMShuttingDown) {
217
return nullptr;
218
}
219
220
CreateServiceIfNeeded();
221
RefPtr<AudioChannelService> service = gAudioChannelService.get();
222
return service.forget();
223
}
224
225
/* static */
226
already_AddRefed<AudioChannelService> AudioChannelService::Get() {
227
if (sXPCOMShuttingDown) {
228
return nullptr;
229
}
230
231
RefPtr<AudioChannelService> service = gAudioChannelService.get();
232
return service.forget();
233
}
234
235
/* static */
236
LogModule* AudioChannelService::GetAudioChannelLog() {
237
return gAudioChannelLog;
238
}
239
240
/* static */
241
void AudioChannelService::Shutdown() {
242
if (gAudioChannelService) {
243
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
244
if (obs) {
245
obs->RemoveObserver(gAudioChannelService, "xpcom-shutdown");
246
obs->RemoveObserver(gAudioChannelService, "outer-window-destroyed");
247
}
248
249
gAudioChannelService->mWindows.Clear();
250
251
gAudioChannelService = nullptr;
252
}
253
}
254
255
/* static */
256
bool AudioChannelService::IsEnableAudioCompeting() {
257
CreateServiceIfNeeded();
258
return sAudioChannelCompeting;
259
}
260
261
NS_INTERFACE_MAP_BEGIN(AudioChannelService)
262
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
263
NS_INTERFACE_MAP_ENTRY(nsIObserver)
264
NS_INTERFACE_MAP_END
265
266
NS_IMPL_ADDREF(AudioChannelService)
267
NS_IMPL_RELEASE(AudioChannelService)
268
269
AudioChannelService::AudioChannelService() {
270
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
271
if (obs) {
272
obs->AddObserver(this, "xpcom-shutdown", false);
273
obs->AddObserver(this, "outer-window-destroyed", false);
274
}
275
276
Preferences::AddBoolVarCache(&sAudioChannelCompeting,
277
"dom.audiochannel.audioCompeting");
278
Preferences::AddBoolVarCache(&sAudioChannelCompetingAllAgents,
279
"dom.audiochannel.audioCompeting.allAgents");
280
}
281
282
AudioChannelService::~AudioChannelService() {}
283
284
void AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
285
AudibleState aAudible) {
286
MOZ_ASSERT(aAgent);
287
288
uint64_t windowID = aAgent->WindowID();
289
AudioChannelWindow* winData = GetWindowData(windowID);
290
if (!winData) {
291
winData = new AudioChannelWindow(windowID);
292
mWindows.AppendElement(winData);
293
}
294
295
// To make sure agent would be alive because AppendAgent() would trigger the
296
// callback function of AudioChannelAgentOwner that means the agent might be
297
// released in their callback.
298
RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
299
winData->AppendAgent(aAgent, aAudible);
300
}
301
302
void AudioChannelService::UnregisterAudioChannelAgent(
303
AudioChannelAgent* aAgent) {
304
MOZ_ASSERT(aAgent);
305
306
AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
307
if (!winData) {
308
return;
309
}
310
311
// To make sure agent would be alive because AppendAgent() would trigger the
312
// callback function of AudioChannelAgentOwner that means the agent might be
313
// released in their callback.
314
RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
315
winData->RemoveAgent(aAgent);
316
}
317
318
AudioPlaybackConfig AudioChannelService::GetMediaConfig(
319
nsPIDOMWindowOuter* aWindow) const {
320
AudioPlaybackConfig config(1.0, false, nsISuspendedTypes::NONE_SUSPENDED);
321
322
if (!aWindow) {
323
config.mVolume = 0.0;
324
config.mMuted = true;
325
config.mSuspend = nsISuspendedTypes::SUSPENDED_BLOCK;
326
return config;
327
}
328
329
AudioChannelWindow* winData = nullptr;
330
nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
331
332
// The volume must be calculated based on the window hierarchy. Here we go up
333
// to the top window and we calculate the volume and the muted flag.
334
do {
335
winData = GetWindowData(window->WindowID());
336
if (winData) {
337
config.mVolume *= winData->mConfig.mVolume;
338
config.mMuted = config.mMuted || winData->mConfig.mMuted;
339
config.mSuspend = winData->mOwningAudioFocus
340
? config.mSuspend
341
: nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
342
config.mCapturedAudio = winData->mIsAudioCaptured;
343
}
344
345
config.mVolume *= window->GetAudioVolume();
346
config.mMuted = config.mMuted || window->GetAudioMuted();
347
if (window->GetMediaSuspend() != nsISuspendedTypes::NONE_SUSPENDED) {
348
config.mSuspend = window->GetMediaSuspend();
349
}
350
351
nsCOMPtr<nsPIDOMWindowOuter> win =
352
window->GetInProcessScriptableParentOrNull();
353
if (!win) {
354
break;
355
}
356
357
window = win;
358
359
// If there is no parent, or we are the toplevel we don't continue.
360
} while (window && window != aWindow);
361
362
return config;
363
}
364
365
void AudioChannelService::AudioAudibleChanged(AudioChannelAgent* aAgent,
366
AudibleState aAudible,
367
AudibleChangedReasons aReason) {
368
MOZ_ASSERT(aAgent);
369
370
uint64_t windowID = aAgent->WindowID();
371
AudioChannelWindow* winData = GetWindowData(windowID);
372
if (winData) {
373
winData->AudioAudibleChanged(aAgent, aAudible, aReason);
374
}
375
}
376
377
NS_IMETHODIMP
378
AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic,
379
const char16_t* aData) {
380
if (!strcmp(aTopic, "xpcom-shutdown")) {
381
sXPCOMShuttingDown = true;
382
Shutdown();
383
} else if (!strcmp(aTopic, "outer-window-destroyed")) {
384
nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
385
NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
386
387
uint64_t outerID;
388
nsresult rv = wrapper->GetData(&outerID);
389
if (NS_WARN_IF(NS_FAILED(rv))) {
390
return rv;
391
}
392
393
nsAutoPtr<AudioChannelWindow> winData;
394
{
395
nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator iter(
396
mWindows);
397
while (iter.HasMore()) {
398
nsAutoPtr<AudioChannelWindow>& next = iter.GetNext();
399
if (next->mWindowID == outerID) {
400
uint32_t pos = mWindows.IndexOf(next);
401
winData = next.forget();
402
mWindows.RemoveElementAt(pos);
403
break;
404
}
405
}
406
}
407
408
if (winData) {
409
nsTObserverArray<AudioChannelAgent*>::ForwardIterator iter(
410
winData->mAgents);
411
while (iter.HasMore()) {
412
iter.GetNext()->WindowVolumeChanged(winData->mConfig.mVolume,
413
winData->mConfig.mMuted);
414
}
415
}
416
}
417
418
return NS_OK;
419
}
420
421
void AudioChannelService::RefreshAgents(
422
nsPIDOMWindowOuter* aWindow,
423
const std::function<void(AudioChannelAgent*)>& aFunc) {
424
MOZ_ASSERT(aWindow);
425
426
nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
427
if (!topWindow) {
428
return;
429
}
430
431
AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
432
if (!winData) {
433
return;
434
}
435
436
nsTObserverArray<AudioChannelAgent*>::ForwardIterator iter(winData->mAgents);
437
while (iter.HasMore()) {
438
aFunc(iter.GetNext());
439
}
440
}
441
442
void AudioChannelService::RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow,
443
float aVolume, bool aMuted) {
444
RefreshAgents(aWindow, [aVolume, aMuted](AudioChannelAgent* agent) {
445
agent->WindowVolumeChanged(aVolume, aMuted);
446
});
447
}
448
449
void AudioChannelService::RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow,
450
nsSuspendedTypes aSuspend) {
451
RefreshAgents(aWindow, [aSuspend](AudioChannelAgent* agent) {
452
agent->WindowSuspendChanged(aSuspend);
453
});
454
}
455
456
void AudioChannelService::SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
457
uint64_t aInnerWindowID,
458
bool aCapture) {
459
MOZ_ASSERT(NS_IsMainThread());
460
MOZ_ASSERT(aWindow);
461
462
MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
463
("AudioChannelService, SetWindowAudioCaptured, window = %p, "
464
"aCapture = %d\n",
465
aWindow, aCapture));
466
467
nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
468
if (!topWindow) {
469
return;
470
}
471
472
AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
473
474
// This can happen, but only during shutdown, because the the outer window
475
// changes ScriptableTop, so that its ID is different.
476
// In this case either we are capturing, and it's too late because the window
477
// has been closed anyways, or we are un-capturing, and everything has already
478
// been cleaned up by the HTMLMediaElements or the AudioContexts.
479
if (!winData) {
480
return;
481
}
482
483
if (aCapture != winData->mIsAudioCaptured) {
484
winData->mIsAudioCaptured = aCapture;
485
nsTObserverArray<AudioChannelAgent*>::ForwardIterator iter(
486
winData->mAgents);
487
while (iter.HasMore()) {
488
iter.GetNext()->WindowAudioCaptureChanged(aInnerWindowID, aCapture);
489
}
490
}
491
}
492
493
AudioChannelService::AudioChannelWindow*
494
AudioChannelService::GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow) {
495
MOZ_ASSERT(NS_IsMainThread());
496
MOZ_ASSERT(aWindow);
497
498
AudioChannelWindow* winData = GetWindowData(aWindow->WindowID());
499
if (!winData) {
500
winData = new AudioChannelWindow(aWindow->WindowID());
501
mWindows.AppendElement(winData);
502
}
503
504
return winData;
505
}
506
507
AudioChannelService::AudioChannelWindow* AudioChannelService::GetWindowData(
508
uint64_t aWindowID) const {
509
nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator iter(
510
mWindows);
511
while (iter.HasMore()) {
512
AudioChannelWindow* next = iter.GetNext();
513
if (next->mWindowID == aWindowID) {
514
return next;
515
}
516
}
517
518
return nullptr;
519
}
520
521
bool AudioChannelService::IsWindowActive(nsPIDOMWindowOuter* aWindow) {
522
MOZ_ASSERT(NS_IsMainThread());
523
524
auto* window = nsPIDOMWindowOuter::From(aWindow)->GetInProcessScriptableTop();
525
if (!window) {
526
return false;
527
}
528
529
AudioChannelWindow* winData = GetWindowData(window->WindowID());
530
if (!winData) {
531
return false;
532
}
533
534
return !winData->mAudibleAgents.IsEmpty();
535
}
536
537
void AudioChannelService::RefreshAgentsAudioFocusChanged(
538
AudioChannelAgent* aAgent) {
539
MOZ_ASSERT(aAgent);
540
541
nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator iter(
542
mWindows);
543
while (iter.HasMore()) {
544
AudioChannelWindow* winData = iter.GetNext();
545
if (winData->mOwningAudioFocus) {
546
winData->AudioFocusChanged(aAgent);
547
}
548
}
549
}
550
551
void AudioChannelService::NotifyMediaResumedFromBlock(
552
nsPIDOMWindowOuter* aWindow) {
553
MOZ_ASSERT(aWindow);
554
555
nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
556
if (!topWindow) {
557
return;
558
}
559
560
AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
561
if (!winData) {
562
return;
563
}
564
565
winData->NotifyMediaBlockStop(aWindow);
566
}
567
568
void AudioChannelService::AudioChannelWindow::RequestAudioFocus(
569
AudioChannelAgent* aAgent) {
570
MOZ_ASSERT(aAgent);
571
572
// Don't need to check audio focus for window-less agent.
573
if (!aAgent->Window()) {
574
return;
575
}
576
577
// We already have the audio focus. No operation is needed.
578
if (mOwningAudioFocus) {
579
return;
580
}
581
582
// Only foreground window can request audio focus, but it would still own the
583
// audio focus even it goes to background. Audio focus would be abandoned
584
// only when other foreground window starts audio competing.
585
// One exception is if the pref "media.block-autoplay-until-in-foreground"
586
// is on and the background page is the non-visited before. Because the media
587
// in that page would be blocked until the page is going to foreground.
588
mOwningAudioFocus = (!(aAgent->Window()->IsBackground()) ||
589
aAgent->Window()->GetMediaSuspend() ==
590
nsISuspendedTypes::SUSPENDED_BLOCK);
591
592
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
593
("AudioChannelWindow, RequestAudioFocus, this = %p, "
594
"agent = %p, owning audio focus = %s\n",
595
this, aAgent, mOwningAudioFocus ? "true" : "false"));
596
}
597
598
void AudioChannelService::AudioChannelWindow::NotifyAudioCompetingChanged(
599
AudioChannelAgent* aAgent) {
600
// This function may be called after RemoveAgentAndReduceAgentsNum(), so the
601
// agent may be not contained in mAgent. In addition, the agent would still
602
// be alive because we have kungFuDeathGrip in UnregisterAudioChannelAgent().
603
MOZ_ASSERT(aAgent);
604
605
RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
606
MOZ_ASSERT(service);
607
608
if (!service->IsEnableAudioCompeting()) {
609
return;
610
}
611
612
if (!IsAgentInvolvingInAudioCompeting(aAgent)) {
613
return;
614
}
615
616
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
617
("AudioChannelWindow, NotifyAudioCompetingChanged, this = %p, "
618
"agent = %p\n",
619
this, aAgent));
620
621
service->RefreshAgentsAudioFocusChanged(aAgent);
622
}
623
624
bool AudioChannelService::AudioChannelWindow::IsAgentInvolvingInAudioCompeting(
625
AudioChannelAgent* aAgent) const {
626
MOZ_ASSERT(aAgent);
627
628
if (!mOwningAudioFocus) {
629
return false;
630
}
631
632
if (IsAudioCompetingInSameTab()) {
633
return false;
634
}
635
636
// TODO : add MediaSession::ambient kind, because it doens't interact with
637
// other kinds.
638
return true;
639
}
640
641
bool AudioChannelService::AudioChannelWindow::IsAudioCompetingInSameTab()
642
const {
643
bool hasMultipleActiveAgents = IsEnableAudioCompetingForAllAgents()
644
? mAgents.Length() > 1
645
: mAudibleAgents.Length() > 1;
646
return mOwningAudioFocus && hasMultipleActiveAgents;
647
}
648
649
void AudioChannelService::AudioChannelWindow::AudioFocusChanged(
650
AudioChannelAgent* aNewPlayingAgent) {
651
// This agent isn't always known for the current window, because it can comes
652
// from other window.
653
MOZ_ASSERT(aNewPlayingAgent);
654
655
if (IsInactiveWindow()) {
656
// These would happen in two situations,
657
// (1) Audio in page A was ended, and another page B want to play audio.
658
// Page A should abandon its focus.
659
// (2) Audio was paused by remote-control, page should still own the focus.
660
mOwningAudioFocus = IsContainingPlayingAgent(aNewPlayingAgent);
661
} else {
662
nsTObserverArray<AudioChannelAgent*>::ForwardIterator iter(
663
IsEnableAudioCompetingForAllAgents() ? mAgents : mAudibleAgents);
664
while (iter.HasMore()) {
665
AudioChannelAgent* agent = iter.GetNext();
666
MOZ_ASSERT(agent);
667
668
// Don't need to update the playing state of new playing agent.
669
if (agent == aNewPlayingAgent) {
670
continue;
671
}
672
673
uint32_t type = GetCompetingBehavior(agent);
674
675
// If window will be suspended, it needs to abandon the audio focus
676
// because only one window can own audio focus at a time. However, we
677
// would support multiple audio focus at the same time in the future.
678
mOwningAudioFocus = (type == nsISuspendedTypes::NONE_SUSPENDED);
679
680
// TODO : support other behaviors which are definded in MediaSession API.
681
switch (type) {
682
case nsISuspendedTypes::NONE_SUSPENDED:
683
case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
684
agent->WindowSuspendChanged(type);
685
break;
686
}
687
}
688
}
689
690
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
691
("AudioChannelWindow, AudioFocusChanged, this = %p, "
692
"OwningAudioFocus = %s\n",
693
this, mOwningAudioFocus ? "true" : "false"));
694
}
695
696
bool AudioChannelService::AudioChannelWindow::IsContainingPlayingAgent(
697
AudioChannelAgent* aAgent) const {
698
return (aAgent->WindowID() == mWindowID);
699
}
700
701
uint32_t AudioChannelService::AudioChannelWindow::GetCompetingBehavior(
702
AudioChannelAgent* aAgent) const {
703
MOZ_ASSERT(aAgent);
704
MOZ_ASSERT(IsEnableAudioCompetingForAllAgents()
705
? mAgents.Contains(aAgent)
706
: mAudibleAgents.Contains(aAgent));
707
708
uint32_t competingBehavior = nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
709
710
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
711
("AudioChannelWindow, GetCompetingBehavior, this = %p, "
712
"behavior = %s\n",
713
this, SuspendTypeToStr(competingBehavior)));
714
715
return competingBehavior;
716
}
717
718
void AudioChannelService::AudioChannelWindow::AppendAgent(
719
AudioChannelAgent* aAgent, AudibleState aAudible) {
720
MOZ_ASSERT(aAgent);
721
722
RequestAudioFocus(aAgent);
723
AppendAgentAndIncreaseAgentsNum(aAgent);
724
AudioAudibleChanged(aAgent, aAudible,
725
AudibleChangedReasons::eDataAudibleChanged);
726
if (IsEnableAudioCompetingForAllAgents() &&
727
aAudible != AudibleState::eAudible) {
728
NotifyAudioCompetingChanged(aAgent);
729
}
730
}
731
732
void AudioChannelService::AudioChannelWindow::RemoveAgent(
733
AudioChannelAgent* aAgent) {
734
MOZ_ASSERT(aAgent);
735
736
RemoveAgentAndReduceAgentsNum(aAgent);
737
AudioAudibleChanged(aAgent, AudibleState::eNotAudible,
738
AudibleChangedReasons::ePauseStateChanged);
739
}
740
741
void AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop(
742
nsPIDOMWindowOuter* aWindow) {
743
if (mShouldSendActiveMediaBlockStopEvent) {
744
mShouldSendActiveMediaBlockStopEvent = false;
745
nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
746
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
747
"dom::AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop",
748
[window]() -> void {
749
nsCOMPtr<nsIObserverService> observerService =
750
services::GetObserverService();
751
if (NS_WARN_IF(!observerService)) {
752
return;
753
}
754
755
observerService->NotifyObservers(ToSupports(window), "audio-playback",
756
u"activeMediaBlockStop");
757
}));
758
}
759
}
760
761
void AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(
762
AudioChannelAgent* aAgent) {
763
MOZ_ASSERT(aAgent);
764
MOZ_ASSERT(!mAgents.Contains(aAgent));
765
766
mAgents.AppendElement(aAgent);
767
768
++mConfig.mNumberOfAgents;
769
770
// TODO: Make NotifyChannelActiveRunnable irrelevant to
771
// BrowserElementAudioChannel
772
if (mConfig.mNumberOfAgents == 1) {
773
NotifyChannelActive(aAgent->WindowID(), true);
774
}
775
}
776
777
void AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(
778
AudioChannelAgent* aAgent) {
779
MOZ_ASSERT(aAgent);
780
MOZ_ASSERT(mAgents.Contains(aAgent));
781
782
mAgents.RemoveElement(aAgent);
783
784
MOZ_ASSERT(mConfig.mNumberOfAgents > 0);
785
--mConfig.mNumberOfAgents;
786
787
if (mConfig.mNumberOfAgents == 0) {
788
NotifyChannelActive(aAgent->WindowID(), false);
789
}
790
}
791
792
void AudioChannelService::AudioChannelWindow::AudioAudibleChanged(
793
AudioChannelAgent* aAgent, AudibleState aAudible,
794
AudibleChangedReasons aReason) {
795
MOZ_ASSERT(aAgent);
796
797
if (aAudible == AudibleState::eAudible) {
798
AppendAudibleAgentIfNotContained(aAgent, aReason);
799
NotifyAudioCompetingChanged(aAgent);
800
} else {
801
RemoveAudibleAgentIfContained(aAgent, aReason);
802
}
803
804
if (aAudible != AudibleState::eNotAudible) {
805
MaybeNotifyMediaBlockStart(aAgent);
806
}
807
}
808
809
void AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(
810
AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
811
MOZ_ASSERT(aAgent);
812
MOZ_ASSERT(mAgents.Contains(aAgent));
813
814
if (!mAudibleAgents.Contains(aAgent)) {
815
mAudibleAgents.AppendElement(aAgent);
816
if (IsFirstAudibleAgent()) {
817
NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible,
818
aReason);
819
}
820
}
821
}
822
823
void AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(
824
AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
825
MOZ_ASSERT(aAgent);
826
827
if (mAudibleAgents.Contains(aAgent)) {
828
mAudibleAgents.RemoveElement(aAgent);
829
if (IsLastAudibleAgent()) {
830
NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible,
831
aReason);
832
}
833
}
834
}
835
836
bool AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const {
837
return (mAudibleAgents.Length() == 1);
838
}
839
840
bool AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const {
841
return mAudibleAgents.IsEmpty();
842
}
843
844
bool AudioChannelService::AudioChannelWindow::IsInactiveWindow() const {
845
return IsEnableAudioCompetingForAllAgents()
846
? mAudibleAgents.IsEmpty() && mAgents.IsEmpty()
847
: mAudibleAgents.IsEmpty();
848
}
849
850
void AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(
851
nsPIDOMWindowOuter* aWindow, AudibleState aAudible,
852
AudibleChangedReasons aReason) {
853
RefPtr<AudioPlaybackRunnable> runnable = new AudioPlaybackRunnable(
854
aWindow, aAudible == AudibleState::eAudible, aReason);
855
DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
856
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
857
}
858
859
void AudioChannelService::AudioChannelWindow::NotifyChannelActive(
860
uint64_t aWindowID, bool aActive) {
861
RefPtr<NotifyChannelActiveRunnable> runnable =
862
new NotifyChannelActiveRunnable(aWindowID, aActive);
863
DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
864
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
865
}
866
867
void AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlockStart(
868
AudioChannelAgent* aAgent) {
869
nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
870
if (!window) {
871
return;
872
}
873
874
nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
875
if (!inner) {
876
return;
877
}
878
879
nsCOMPtr<Document> doc = inner->GetExtantDoc();
880
if (!doc) {
881
return;
882
}
883
884
if (window->GetMediaSuspend() != nsISuspendedTypes::SUSPENDED_BLOCK ||
885
!doc->Hidden()) {
886
return;
887
}
888
889
if (!mShouldSendActiveMediaBlockStopEvent) {
890
mShouldSendActiveMediaBlockStopEvent = true;
891
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
892
"dom::AudioChannelService::AudioChannelWindow::"
893
"MaybeNotifyMediaBlockStart",
894
[window]() -> void {
895
nsCOMPtr<nsIObserverService> observerService =
896
services::GetObserverService();
897
if (NS_WARN_IF(!observerService)) {
898
return;
899
}
900
901
observerService->NotifyObservers(ToSupports(window), "audio-playback",
902
u"activeMediaBlockStart");
903
}));
904
}
905
}
906
907
} // namespace dom
908
} // namespace mozilla