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 sXPCOMShuttingDown = false;
34
35
class AudioPlaybackRunnable final : public Runnable {
36
public:
37
AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive,
38
AudioChannelService::AudibleChangedReasons aReason)
39
: mozilla::Runnable("AudioPlaybackRunnable"),
40
mWindow(aWindow),
41
mActive(aActive),
42
mReason(aReason) {}
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
nsAutoString state;
52
GetActiveState(state);
53
54
observerService->NotifyObservers(ToSupports(mWindow), "audio-playback",
55
state.get());
56
57
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
58
("AudioPlaybackRunnable, active = %s, reason = %s\n",
59
mActive ? "true" : "false", AudibleChangedReasonToStr(mReason)));
60
61
return NS_OK;
62
}
63
64
private:
65
void GetActiveState(nsAString& aState) {
66
if (mActive) {
67
aState.AssignLiteral("active");
68
} else {
69
if (mReason ==
70
AudioChannelService::AudibleChangedReasons::ePauseStateChanged) {
71
aState.AssignLiteral("inactive-pause");
72
} else {
73
aState.AssignLiteral("inactive-nonaudible");
74
}
75
}
76
}
77
78
nsCOMPtr<nsPIDOMWindowOuter> mWindow;
79
bool mActive;
80
AudioChannelService::AudibleChangedReasons mReason;
81
};
82
83
} // anonymous namespace
84
85
namespace mozilla {
86
namespace dom {
87
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
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
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 */
144
void AudioChannelService::CreateServiceIfNeeded() {
145
MOZ_ASSERT(NS_IsMainThread());
146
147
if (!gAudioChannelService) {
148
gAudioChannelService = new AudioChannelService();
149
}
150
}
151
152
/* static */
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 */
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 */
174
LogModule* AudioChannelService::GetAudioChannelLog() {
175
return gAudioChannelLog;
176
}
177
178
/* static */
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)
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
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
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
245
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.mVolume *= window->GetAudioVolume();
270
config.mMuted = config.mMuted || window->GetAudioMuted();
271
if (window->GetMediaSuspend() != nsISuspendedTypes::NONE_SUSPENDED) {
272
config.mSuspend = window->GetMediaSuspend();
273
}
274
275
nsCOMPtr<nsPIDOMWindowOuter> win =
276
window->GetInProcessScriptableParentOrNull();
277
if (!win) {
278
break;
279
}
280
281
window = win;
282
283
// If there is no parent, or we are the toplevel we don't continue.
284
} while (window && window != aWindow);
285
286
return config;
287
}
288
289
void AudioChannelService::AudioAudibleChanged(AudioChannelAgent* aAgent,
290
AudibleState aAudible,
291
AudibleChangedReasons aReason) {
292
MOZ_ASSERT(aAgent);
293
294
uint64_t windowID = aAgent->WindowID();
295
AudioChannelWindow* winData = GetWindowData(windowID);
296
if (winData) {
297
winData->AudioAudibleChanged(aAgent, aAudible, aReason);
298
}
299
}
300
301
NS_IMETHODIMP
302
AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic,
303
const char16_t* aData) {
304
if (!strcmp(aTopic, "xpcom-shutdown")) {
305
sXPCOMShuttingDown = true;
306
Shutdown();
307
} else if (!strcmp(aTopic, "outer-window-destroyed")) {
308
nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
309
NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
310
311
uint64_t outerID;
312
nsresult rv = wrapper->GetData(&outerID);
313
if (NS_WARN_IF(NS_FAILED(rv))) {
314
return rv;
315
}
316
317
UniquePtr<AudioChannelWindow> winData;
318
{
319
nsTObserverArray<UniquePtr<AudioChannelWindow>>::ForwardIterator iter(
320
mWindows);
321
while (iter.HasMore()) {
322
auto& next = iter.GetNext();
323
if (next->mWindowID == outerID) {
324
uint32_t pos = mWindows.IndexOf(next);
325
winData = std::move(next);
326
mWindows.RemoveElementAt(pos);
327
break;
328
}
329
}
330
}
331
332
if (winData) {
333
nsTObserverArray<AudioChannelAgent*>::ForwardIterator iter(
334
winData->mAgents);
335
while (iter.HasMore()) {
336
iter.GetNext()->WindowVolumeChanged(winData->mConfig.mVolume,
337
winData->mConfig.mMuted);
338
}
339
}
340
}
341
342
return NS_OK;
343
}
344
345
void AudioChannelService::RefreshAgents(
346
nsPIDOMWindowOuter* aWindow,
347
const std::function<void(AudioChannelAgent*)>& aFunc) {
348
MOZ_ASSERT(aWindow);
349
350
nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
351
if (!topWindow) {
352
return;
353
}
354
355
AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
356
if (!winData) {
357
return;
358
}
359
360
nsTObserverArray<AudioChannelAgent*>::ForwardIterator iter(winData->mAgents);
361
while (iter.HasMore()) {
362
aFunc(iter.GetNext());
363
}
364
}
365
366
void AudioChannelService::RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow,
367
float aVolume, bool aMuted) {
368
RefreshAgents(aWindow, [aVolume, aMuted](AudioChannelAgent* agent) {
369
agent->WindowVolumeChanged(aVolume, aMuted);
370
});
371
}
372
373
void AudioChannelService::RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow,
374
nsSuspendedTypes aSuspend) {
375
RefreshAgents(aWindow, [aSuspend](AudioChannelAgent* agent) {
376
agent->WindowSuspendChanged(aSuspend);
377
});
378
}
379
380
void AudioChannelService::SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
381
uint64_t aInnerWindowID,
382
bool aCapture) {
383
MOZ_ASSERT(NS_IsMainThread());
384
MOZ_ASSERT(aWindow);
385
386
MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
387
("AudioChannelService, SetWindowAudioCaptured, window = %p, "
388
"aCapture = %d\n",
389
aWindow, aCapture));
390
391
nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
392
if (!topWindow) {
393
return;
394
}
395
396
AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
397
398
// This can happen, but only during shutdown, because the the outer window
399
// changes ScriptableTop, so that its ID is different.
400
// In this case either we are capturing, and it's too late because the window
401
// has been closed anyways, or we are un-capturing, and everything has already
402
// been cleaned up by the HTMLMediaElements or the AudioContexts.
403
if (!winData) {
404
return;
405
}
406
407
if (aCapture != winData->mIsAudioCaptured) {
408
winData->mIsAudioCaptured = aCapture;
409
nsTObserverArray<AudioChannelAgent*>::ForwardIterator iter(
410
winData->mAgents);
411
while (iter.HasMore()) {
412
iter.GetNext()->WindowAudioCaptureChanged(aInnerWindowID, aCapture);
413
}
414
}
415
}
416
417
AudioChannelService::AudioChannelWindow*
418
AudioChannelService::GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow) {
419
MOZ_ASSERT(NS_IsMainThread());
420
MOZ_ASSERT(aWindow);
421
422
AudioChannelWindow* winData = GetWindowData(aWindow->WindowID());
423
if (!winData) {
424
winData = new AudioChannelWindow(aWindow->WindowID());
425
mWindows.AppendElement(WrapUnique(winData));
426
}
427
428
return winData;
429
}
430
431
AudioChannelService::AudioChannelWindow* AudioChannelService::GetWindowData(
432
uint64_t aWindowID) const {
433
nsTObserverArray<UniquePtr<AudioChannelWindow>>::ForwardIterator iter(
434
mWindows);
435
while (iter.HasMore()) {
436
AudioChannelWindow* next = iter.GetNext().get();
437
if (next->mWindowID == aWindowID) {
438
return next;
439
}
440
}
441
442
return nullptr;
443
}
444
445
bool AudioChannelService::IsWindowActive(nsPIDOMWindowOuter* aWindow) {
446
MOZ_ASSERT(NS_IsMainThread());
447
448
auto* window = nsPIDOMWindowOuter::From(aWindow)->GetInProcessScriptableTop();
449
if (!window) {
450
return false;
451
}
452
453
AudioChannelWindow* winData = GetWindowData(window->WindowID());
454
if (!winData) {
455
return false;
456
}
457
458
return !winData->mAudibleAgents.IsEmpty();
459
}
460
461
void AudioChannelService::NotifyMediaResumedFromBlock(
462
nsPIDOMWindowOuter* aWindow) {
463
MOZ_ASSERT(aWindow);
464
465
nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
466
if (!topWindow) {
467
return;
468
}
469
470
AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
471
if (!winData) {
472
return;
473
}
474
475
winData->NotifyMediaBlockStop(aWindow);
476
}
477
478
void AudioChannelService::AudioChannelWindow::AppendAgent(
479
AudioChannelAgent* aAgent, AudibleState aAudible) {
480
MOZ_ASSERT(aAgent);
481
482
AppendAgentAndIncreaseAgentsNum(aAgent);
483
AudioAudibleChanged(aAgent, aAudible,
484
AudibleChangedReasons::eDataAudibleChanged);
485
}
486
487
void AudioChannelService::AudioChannelWindow::RemoveAgent(
488
AudioChannelAgent* aAgent) {
489
MOZ_ASSERT(aAgent);
490
491
RemoveAgentAndReduceAgentsNum(aAgent);
492
AudioAudibleChanged(aAgent, AudibleState::eNotAudible,
493
AudibleChangedReasons::ePauseStateChanged);
494
}
495
496
void AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop(
497
nsPIDOMWindowOuter* aWindow) {
498
if (mShouldSendActiveMediaBlockStopEvent) {
499
mShouldSendActiveMediaBlockStopEvent = false;
500
nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
501
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
502
"dom::AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop",
503
[window]() -> void {
504
nsCOMPtr<nsIObserverService> observerService =
505
services::GetObserverService();
506
if (NS_WARN_IF(!observerService)) {
507
return;
508
}
509
510
observerService->NotifyObservers(ToSupports(window), "audio-playback",
511
u"activeMediaBlockStop");
512
}));
513
}
514
}
515
516
void AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(
517
AudioChannelAgent* aAgent) {
518
MOZ_ASSERT(aAgent);
519
MOZ_ASSERT(!mAgents.Contains(aAgent));
520
521
mAgents.AppendElement(aAgent);
522
523
++mConfig.mNumberOfAgents;
524
}
525
526
void AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(
527
AudioChannelAgent* aAgent) {
528
MOZ_ASSERT(aAgent);
529
MOZ_ASSERT(mAgents.Contains(aAgent));
530
531
mAgents.RemoveElement(aAgent);
532
533
MOZ_ASSERT(mConfig.mNumberOfAgents > 0);
534
--mConfig.mNumberOfAgents;
535
}
536
537
void AudioChannelService::AudioChannelWindow::AudioAudibleChanged(
538
AudioChannelAgent* aAgent, AudibleState aAudible,
539
AudibleChangedReasons aReason) {
540
MOZ_ASSERT(aAgent);
541
542
if (aAudible == AudibleState::eAudible) {
543
AppendAudibleAgentIfNotContained(aAgent, aReason);
544
} else {
545
RemoveAudibleAgentIfContained(aAgent, aReason);
546
}
547
548
if (aAudible != AudibleState::eNotAudible) {
549
MaybeNotifyMediaBlockStart(aAgent);
550
}
551
}
552
553
void AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(
554
AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
555
MOZ_ASSERT(aAgent);
556
MOZ_ASSERT(mAgents.Contains(aAgent));
557
558
if (!mAudibleAgents.Contains(aAgent)) {
559
mAudibleAgents.AppendElement(aAgent);
560
if (IsFirstAudibleAgent()) {
561
NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible,
562
aReason);
563
}
564
}
565
}
566
567
void AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(
568
AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
569
MOZ_ASSERT(aAgent);
570
571
if (mAudibleAgents.Contains(aAgent)) {
572
mAudibleAgents.RemoveElement(aAgent);
573
if (IsLastAudibleAgent()) {
574
NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible,
575
aReason);
576
}
577
}
578
}
579
580
bool AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const {
581
return (mAudibleAgents.Length() == 1);
582
}
583
584
bool AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const {
585
return mAudibleAgents.IsEmpty();
586
}
587
588
void AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(
589
nsPIDOMWindowOuter* aWindow, AudibleState aAudible,
590
AudibleChangedReasons aReason) {
591
RefPtr<AudioPlaybackRunnable> runnable = new AudioPlaybackRunnable(
592
aWindow, aAudible == AudibleState::eAudible, aReason);
593
DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
594
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
595
}
596
597
void AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlockStart(
598
AudioChannelAgent* aAgent) {
599
nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
600
if (!window) {
601
return;
602
}
603
604
nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
605
if (!inner) {
606
return;
607
}
608
609
nsCOMPtr<Document> doc = inner->GetExtantDoc();
610
if (!doc) {
611
return;
612
}
613
614
if (window->GetMediaSuspend() != nsISuspendedTypes::SUSPENDED_BLOCK ||
615
!doc->Hidden()) {
616
return;
617
}
618
619
if (!mShouldSendActiveMediaBlockStopEvent) {
620
mShouldSendActiveMediaBlockStopEvent = true;
621
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
622
"dom::AudioChannelService::AudioChannelWindow::"
623
"MaybeNotifyMediaBlockStart",
624
[window]() -> void {
625
nsCOMPtr<nsIObserverService> observerService =
626
services::GetObserverService();
627
if (NS_WARN_IF(!observerService)) {
628
return;
629
}
630
631
observerService->NotifyObservers(ToSupports(window), "audio-playback",
632
u"activeMediaBlockStart");
633
}));
634
}
635
}
636
637
} // namespace dom
638
} // namespace mozilla