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 "mozilla/dom/ContentChild.h"
16 #include "mozilla/dom/ContentParent.h"
17 #include "mozilla/dom/TabParent.h"
18
19 #include "nsContentUtils.h"
20 #include "nsIScriptSecurityManager.h"
21 #include "nsISupportsPrimitives.h"
22 #include "nsThreadUtils.h"
23 #include "nsHashPropertyBag.h"
24 #include "nsComponentManagerUtils.h"
25 #include "nsGlobalWindow.h"
26 #include "nsPIDOMWindow.h"
27 #include "nsServiceManagerUtils.h"
28 #include "mozilla/dom/SettingChangeNotificationBinding.h"
29
30 #ifdef MOZ_WIDGET_GONK
31 #include "nsJSUtils.h"
32 #include "SpeakerManagerService.h"
33 #endif
34
35 #include "mozilla/Preferences.h"
36
37 using namespace mozilla;
38 using namespace mozilla::dom;
39 using namespace mozilla::hal;
40
41 namespace {
42
43 // If true, any new AudioChannelAgent will be muted when created.
44 bool sAudioChannelMutedByDefault = false;
45 bool sAudioChannelCompeting = false;
46 bool sAudioChannelCompetingAllAgents = false;
47 bool sXPCOMShuttingDown = false;
48
49 class NotifyChannelActiveRunnable final : public Runnable
50 {
51 public:
NotifyChannelActiveRunnable(uint64_t aWindowID,AudioChannel aAudioChannel,bool aActive)52 NotifyChannelActiveRunnable(uint64_t aWindowID, AudioChannel aAudioChannel,
53 bool aActive)
54 : mWindowID(aWindowID)
55 , mAudioChannel(aAudioChannel)
56 , mActive(aActive)
57 {}
58
Run()59 NS_IMETHOD Run() override
60 {
61 nsCOMPtr<nsIObserverService> observerService =
62 services::GetObserverService();
63 if (NS_WARN_IF(!observerService)) {
64 return NS_ERROR_FAILURE;
65 }
66
67 nsCOMPtr<nsISupportsPRUint64> wrapper =
68 do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
69 if (NS_WARN_IF(!wrapper)) {
70 return NS_ERROR_FAILURE;
71 }
72
73 wrapper->SetData(mWindowID);
74
75 nsAutoString name;
76 AudioChannelService::GetAudioChannelString(mAudioChannel, name);
77
78 nsAutoCString topic;
79 topic.Assign("audiochannel-activity-");
80 topic.Append(NS_ConvertUTF16toUTF8(name));
81
82 observerService->NotifyObservers(wrapper, topic.get(),
83 mActive
84 ? u"active"
85 : u"inactive");
86
87 // TODO : remove b2g related event in bug1299390.
88 observerService->NotifyObservers(wrapper,
89 "media-playback",
90 mActive
91 ? u"active"
92 : u"inactive");
93
94 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
95 ("NotifyChannelActiveRunnable, type = %d, active = %d\n",
96 mAudioChannel, mActive));
97
98 return NS_OK;
99 }
100
101 private:
102 const uint64_t mWindowID;
103 const AudioChannel mAudioChannel;
104 const bool mActive;
105 };
106
107 bool
IsParentProcess()108 IsParentProcess()
109 {
110 return XRE_GetProcessType() == GeckoProcessType_Default;
111 }
112
113 class AudioPlaybackRunnable final : public Runnable
114 {
115 public:
AudioPlaybackRunnable(nsPIDOMWindowOuter * aWindow,bool aActive,AudioChannelService::AudibleChangedReasons aReason)116 AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive,
117 AudioChannelService::AudibleChangedReasons aReason)
118 : mWindow(aWindow)
119 , mActive(aActive)
120 , mReason(aReason)
121 {}
122
Run()123 NS_IMETHOD Run() override
124 {
125 nsCOMPtr<nsIObserverService> observerService =
126 services::GetObserverService();
127 if (NS_WARN_IF(!observerService)) {
128 return NS_ERROR_FAILURE;
129 }
130
131 nsAutoString state;
132 GetActiveState(state);
133
134 observerService->NotifyObservers(ToSupports(mWindow),
135 "audio-playback",
136 state.get());
137
138 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
139 ("AudioPlaybackRunnable, active = %d, reason = %d\n",
140 mActive, mReason));
141
142 return NS_OK;
143 }
144
145 private:
GetActiveState(nsAString & astate)146 void GetActiveState(nsAString& astate)
147 {
148 if (mActive) {
149 CopyASCIItoUTF16("active", astate);
150 } else {
151 if(mReason == AudioChannelService::AudibleChangedReasons::ePauseStateChanged) {
152 CopyASCIItoUTF16("inactive-pause", astate);
153 } else {
154 CopyASCIItoUTF16("inactive-nonaudible", astate);
155 }
156 }
157 }
158
159 nsCOMPtr<nsPIDOMWindowOuter> mWindow;
160 bool mActive;
161 AudioChannelService::AudibleChangedReasons mReason;
162 };
163
164 bool
IsEnableAudioCompetingForAllAgents()165 IsEnableAudioCompetingForAllAgents()
166 {
167 // In general, the audio competing should only be for audible media and it
168 // helps user can focus on one media at the same time. However, we hope to
169 // treat all media as the same in the mobile device. First reason is we have
170 // media control on fennec and we just want to control one media at once time.
171 // Second reason is to reduce the bandwidth, avoiding to play any non-audible
172 // media in background which user doesn't notice about.
173 #ifdef MOZ_WIDGET_ANDROID
174 return true;
175 #else
176 return sAudioChannelCompetingAllAgents;
177 #endif
178 }
179
180 } // anonymous namespace
181
182 StaticRefPtr<AudioChannelService> gAudioChannelService;
183
184 // Mappings from 'mozaudiochannel' attribute strings to an enumeration.
185 static const nsAttrValue::EnumTable kMozAudioChannelAttributeTable[] = {
186 { "normal", (int16_t)AudioChannel::Normal },
187 { "content", (int16_t)AudioChannel::Content },
188 { "notification", (int16_t)AudioChannel::Notification },
189 { "alarm", (int16_t)AudioChannel::Alarm },
190 { "telephony", (int16_t)AudioChannel::Telephony },
191 { "ringer", (int16_t)AudioChannel::Ringer },
192 { "publicnotification", (int16_t)AudioChannel::Publicnotification },
193 { "system", (int16_t)AudioChannel::System },
194 { nullptr, 0 }
195 };
196
197 /* static */ void
CreateServiceIfNeeded()198 AudioChannelService::CreateServiceIfNeeded()
199 {
200 MOZ_ASSERT(NS_IsMainThread());
201
202 if (!gAudioChannelService) {
203 gAudioChannelService = new AudioChannelService();
204 }
205 }
206
207 /* static */ already_AddRefed<AudioChannelService>
GetOrCreate()208 AudioChannelService::GetOrCreate()
209 {
210 if (sXPCOMShuttingDown) {
211 return nullptr;
212 }
213
214 CreateServiceIfNeeded();
215 RefPtr<AudioChannelService> service = gAudioChannelService.get();
216 return service.forget();
217 }
218
219 /* static */ PRLogModuleInfo*
GetAudioChannelLog()220 AudioChannelService::GetAudioChannelLog()
221 {
222 static PRLogModuleInfo *gAudioChannelLog;
223 if (!gAudioChannelLog) {
224 gAudioChannelLog = PR_NewLogModule("AudioChannel");
225 }
226 return gAudioChannelLog;
227 }
228
229 /* static */ void
Shutdown()230 AudioChannelService::Shutdown()
231 {
232 if (gAudioChannelService) {
233 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
234 if (obs) {
235 obs->RemoveObserver(gAudioChannelService, "xpcom-shutdown");
236 obs->RemoveObserver(gAudioChannelService, "outer-window-destroyed");
237
238 if (IsParentProcess()) {
239 obs->RemoveObserver(gAudioChannelService, "ipc:content-shutdown");
240
241 #ifdef MOZ_WIDGET_GONK
242 // To monitor the volume settings based on audio channel.
243 obs->RemoveObserver(gAudioChannelService, "mozsettings-changed");
244 #endif
245 }
246 }
247
248 gAudioChannelService->mWindows.Clear();
249 gAudioChannelService->mPlayingChildren.Clear();
250 gAudioChannelService->mTabParents.Clear();
251 #ifdef MOZ_WIDGET_GONK
252 gAudioChannelService->mSpeakerManager.Clear();
253 #endif
254
255 gAudioChannelService = nullptr;
256 }
257 }
258
259 /* static */ bool
IsEnableAudioCompeting()260 AudioChannelService::IsEnableAudioCompeting()
261 {
262 CreateServiceIfNeeded();
263 return sAudioChannelCompeting;
264 }
265
266 NS_INTERFACE_MAP_BEGIN(AudioChannelService)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsIAudioChannelService)267 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAudioChannelService)
268 NS_INTERFACE_MAP_ENTRY(nsIAudioChannelService)
269 NS_INTERFACE_MAP_ENTRY(nsIObserver)
270 NS_INTERFACE_MAP_END
271
272 NS_IMPL_ADDREF(AudioChannelService)
273 NS_IMPL_RELEASE(AudioChannelService)
274
275 AudioChannelService::AudioChannelService()
276 : mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN)
277 , mTelephonyChannel(false)
278 , mContentOrNormalChannel(false)
279 , mAnyChannel(false)
280 {
281 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
282 if (obs) {
283 obs->AddObserver(this, "xpcom-shutdown", false);
284 obs->AddObserver(this, "outer-window-destroyed", false);
285 if (IsParentProcess()) {
286 obs->AddObserver(this, "ipc:content-shutdown", false);
287
288 #ifdef MOZ_WIDGET_GONK
289 // To monitor the volume settings based on audio channel.
290 obs->AddObserver(this, "mozsettings-changed", false);
291 #endif
292 }
293 }
294
295 Preferences::AddBoolVarCache(&sAudioChannelMutedByDefault,
296 "dom.audiochannel.mutedByDefault");
297 Preferences::AddBoolVarCache(&sAudioChannelCompeting,
298 "dom.audiochannel.audioCompeting");
299 Preferences::AddBoolVarCache(&sAudioChannelCompetingAllAgents,
300 "dom.audiochannel.audioCompeting.allAgents");
301 }
302
~AudioChannelService()303 AudioChannelService::~AudioChannelService()
304 {
305 }
306
307 void
RegisterAudioChannelAgent(AudioChannelAgent * aAgent,AudibleState aAudible)308 AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
309 AudibleState aAudible)
310 {
311 MOZ_ASSERT(aAgent);
312
313 uint64_t windowID = aAgent->WindowID();
314 AudioChannelWindow* winData = GetWindowData(windowID);
315 if (!winData) {
316 winData = new AudioChannelWindow(windowID);
317 mWindows.AppendElement(winData);
318 }
319
320 // To make sure agent would be alive because AppendAgent() would trigger the
321 // callback function of AudioChannelAgentOwner that means the agent might be
322 // released in their callback.
323 RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
324 winData->AppendAgent(aAgent, aAudible);
325
326 MaybeSendStatusUpdate();
327 }
328
329 void
UnregisterAudioChannelAgent(AudioChannelAgent * aAgent)330 AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
331 {
332 MOZ_ASSERT(aAgent);
333
334 AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
335 if (!winData) {
336 return;
337 }
338
339 // To make sure agent would be alive because AppendAgent() would trigger the
340 // callback function of AudioChannelAgentOwner that means the agent might be
341 // released in their callback.
342 RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
343 winData->RemoveAgent(aAgent);
344
345 #ifdef MOZ_WIDGET_GONK
346 bool active = AnyAudioChannelIsActive();
347 for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
348 mSpeakerManager[i]->SetAudioChannelActive(active);
349 }
350 #endif
351
352 MaybeSendStatusUpdate();
353 }
354
355 void
RegisterTabParent(TabParent * aTabParent)356 AudioChannelService::RegisterTabParent(TabParent* aTabParent)
357 {
358 MOZ_ASSERT(aTabParent);
359 MOZ_ASSERT(!mTabParents.Contains(aTabParent));
360 mTabParents.AppendElement(aTabParent);
361 }
362
363 void
UnregisterTabParent(TabParent * aTabParent)364 AudioChannelService::UnregisterTabParent(TabParent* aTabParent)
365 {
366 MOZ_ASSERT(aTabParent);
367 mTabParents.RemoveElement(aTabParent);
368 }
369
370 AudioPlaybackConfig
GetMediaConfig(nsPIDOMWindowOuter * aWindow,uint32_t aAudioChannel) const371 AudioChannelService::GetMediaConfig(nsPIDOMWindowOuter* aWindow,
372 uint32_t aAudioChannel) const
373 {
374 MOZ_ASSERT(!aWindow || aWindow->IsOuterWindow());
375 MOZ_ASSERT(aAudioChannel < NUMBER_OF_AUDIO_CHANNELS);
376
377 AudioPlaybackConfig config(1.0, false,
378 nsISuspendedTypes::NONE_SUSPENDED);
379
380 if (!aWindow || !aWindow->IsOuterWindow()) {
381 config.SetConfig(0.0, true,
382 nsISuspendedTypes::SUSPENDED_BLOCK);
383 return config;
384 }
385
386 AudioChannelWindow* winData = nullptr;
387 nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
388
389 // The volume must be calculated based on the window hierarchy. Here we go up
390 // to the top window and we calculate the volume and the muted flag.
391 do {
392 winData = GetWindowData(window->WindowID());
393 if (winData) {
394 config.mVolume *= winData->mChannels[aAudioChannel].mVolume;
395 config.mMuted = config.mMuted || winData->mChannels[aAudioChannel].mMuted;
396 config.mSuspend = winData->mOwningAudioFocus ?
397 config.mSuspend : nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
398 }
399
400 config.mVolume *= window->GetAudioVolume();
401 config.mMuted = config.mMuted || window->GetAudioMuted();
402 if (window->GetMediaSuspend() != nsISuspendedTypes::NONE_SUSPENDED) {
403 config.mSuspend = window->GetMediaSuspend();
404 }
405
406 nsCOMPtr<nsPIDOMWindowOuter> win = window->GetScriptableParentOrNull();
407 if (!win) {
408 break;
409 }
410
411 window = do_QueryInterface(win);
412
413 // If there is no parent, or we are the toplevel we don't continue.
414 } while (window && window != aWindow);
415
416 return config;
417 }
418
419 void
AudioAudibleChanged(AudioChannelAgent * aAgent,AudibleState aAudible,AudibleChangedReasons aReason)420 AudioChannelService::AudioAudibleChanged(AudioChannelAgent* aAgent,
421 AudibleState aAudible,
422 AudibleChangedReasons aReason)
423 {
424 MOZ_ASSERT(aAgent);
425
426 uint64_t windowID = aAgent->WindowID();
427 AudioChannelWindow* winData = GetWindowData(windowID);
428 if (winData) {
429 winData->AudioAudibleChanged(aAgent, aAudible, aReason);
430 }
431 }
432
433 bool
TelephonyChannelIsActive()434 AudioChannelService::TelephonyChannelIsActive()
435 {
436 nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator windowsIter(mWindows);
437 while (windowsIter.HasMore()) {
438 AudioChannelWindow* next = windowsIter.GetNext();
439 if (next->mChannels[(uint32_t)AudioChannel::Telephony].mNumberOfAgents != 0 &&
440 !next->mChannels[(uint32_t)AudioChannel::Telephony].mMuted) {
441 return true;
442 }
443 }
444
445 if (IsParentProcess()) {
446 nsTObserverArray<nsAutoPtr<AudioChannelChildStatus>>::ForwardIterator
447 childrenIter(mPlayingChildren);
448 while (childrenIter.HasMore()) {
449 AudioChannelChildStatus* child = childrenIter.GetNext();
450 if (child->mActiveTelephonyChannel) {
451 return true;
452 }
453 }
454 }
455
456 return false;
457 }
458
459 bool
ContentOrNormalChannelIsActive()460 AudioChannelService::ContentOrNormalChannelIsActive()
461 {
462 // This method is meant to be used just by the child to send status update.
463 MOZ_ASSERT(!IsParentProcess());
464
465 nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator iter(mWindows);
466 while (iter.HasMore()) {
467 AudioChannelWindow* next = iter.GetNext();
468 if (next->mChannels[(uint32_t)AudioChannel::Content].mNumberOfAgents > 0 ||
469 next->mChannels[(uint32_t)AudioChannel::Normal].mNumberOfAgents > 0) {
470 return true;
471 }
472 }
473 return false;
474 }
475
476 AudioChannelService::AudioChannelChildStatus*
GetChildStatus(uint64_t aChildID) const477 AudioChannelService::GetChildStatus(uint64_t aChildID) const
478 {
479 nsTObserverArray<nsAutoPtr<AudioChannelChildStatus>>::ForwardIterator
480 iter(mPlayingChildren);
481 while (iter.HasMore()) {
482 AudioChannelChildStatus* child = iter.GetNext();
483 if (child->mChildID == aChildID) {
484 return child;
485 }
486 }
487
488 return nullptr;
489 }
490
491 void
RemoveChildStatus(uint64_t aChildID)492 AudioChannelService::RemoveChildStatus(uint64_t aChildID)
493 {
494 nsTObserverArray<nsAutoPtr<AudioChannelChildStatus>>::ForwardIterator
495 iter(mPlayingChildren);
496 while (iter.HasMore()) {
497 nsAutoPtr<AudioChannelChildStatus>& child = iter.GetNext();
498 if (child->mChildID == aChildID) {
499 mPlayingChildren.RemoveElement(child);
500 break;
501 }
502 }
503 }
504
505 bool
ProcessContentOrNormalChannelIsActive(uint64_t aChildID)506 AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID)
507 {
508 AudioChannelChildStatus* child = GetChildStatus(aChildID);
509 if (!child) {
510 return false;
511 }
512
513 return child->mActiveContentOrNormalChannel;
514 }
515
516 bool
AnyAudioChannelIsActive()517 AudioChannelService::AnyAudioChannelIsActive()
518 {
519 nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator iter(mWindows);
520 while (iter.HasMore()) {
521 AudioChannelWindow* next = iter.GetNext();
522 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
523 if (next->mChannels[kMozAudioChannelAttributeTable[i].value].mNumberOfAgents
524 != 0) {
525 return true;
526 }
527 }
528 }
529
530 if (IsParentProcess()) {
531 return !mPlayingChildren.IsEmpty();
532 }
533
534 return false;
535 }
536
537 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)538 AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic,
539 const char16_t* aData)
540 {
541 if (!strcmp(aTopic, "xpcom-shutdown")) {
542 sXPCOMShuttingDown = true;
543 Shutdown();
544 } else if (!strcmp(aTopic, "outer-window-destroyed")) {
545 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
546 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
547
548 uint64_t outerID;
549 nsresult rv = wrapper->GetData(&outerID);
550 if (NS_WARN_IF(NS_FAILED(rv))) {
551 return rv;
552 }
553
554 nsAutoPtr<AudioChannelWindow> winData;
555 {
556 nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
557 iter(mWindows);
558 while (iter.HasMore()) {
559 nsAutoPtr<AudioChannelWindow>& next = iter.GetNext();
560 if (next->mWindowID == outerID) {
561 uint32_t pos = mWindows.IndexOf(next);
562 winData = next.forget();
563 mWindows.RemoveElementAt(pos);
564 break;
565 }
566 }
567 }
568
569 if (winData) {
570 nsTObserverArray<AudioChannelAgent*>::ForwardIterator
571 iter(winData->mAgents);
572 while (iter.HasMore()) {
573 iter.GetNext()->WindowVolumeChanged();
574 }
575 }
576
577 #ifdef MOZ_WIDGET_GONK
578 bool active = AnyAudioChannelIsActive();
579 for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
580 mSpeakerManager[i]->SetAudioChannelActive(active);
581 }
582 #endif
583 } else if (!strcmp(aTopic, "ipc:content-shutdown")) {
584 nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
585 if (!props) {
586 NS_WARNING("ipc:content-shutdown message without property bag as subject");
587 return NS_OK;
588 }
589
590 uint64_t childID = 0;
591 nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
592 &childID);
593 if (NS_WARN_IF(NS_FAILED(rv))) {
594 return rv;
595 }
596
597 if (mDefChannelChildID == childID) {
598 SetDefaultVolumeControlChannelInternal(-1, false, childID);
599 mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN;
600 }
601
602 RemoveChildStatus(childID);
603 }
604
605 return NS_OK;
606 }
607
608 void
RefreshAgentsVolumeAndPropagate(AudioChannel aAudioChannel,nsPIDOMWindowOuter * aWindow)609 AudioChannelService::RefreshAgentsVolumeAndPropagate(AudioChannel aAudioChannel,
610 nsPIDOMWindowOuter* aWindow)
611 {
612 MOZ_ASSERT(aWindow);
613 MOZ_ASSERT(aWindow->IsOuterWindow());
614
615 nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
616 if (!topWindow) {
617 return;
618 }
619
620 AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
621 if (!winData) {
622 return;
623 }
624
625 for (uint32_t i = 0; i < mTabParents.Length(); ++i) {
626 mTabParents[i]->AudioChannelChangeNotification(aWindow, aAudioChannel,
627 winData->mChannels[(uint32_t)aAudioChannel].mVolume,
628 winData->mChannels[(uint32_t)aAudioChannel].mMuted);
629 }
630
631 RefreshAgentsVolume(aWindow);
632 }
633
634 void
RefreshAgents(nsPIDOMWindowOuter * aWindow,mozilla::function<void (AudioChannelAgent *)> aFunc)635 AudioChannelService::RefreshAgents(nsPIDOMWindowOuter* aWindow,
636 mozilla::function<void(AudioChannelAgent*)> aFunc)
637 {
638 MOZ_ASSERT(aWindow);
639 MOZ_ASSERT(aWindow->IsOuterWindow());
640
641 nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
642 if (!topWindow) {
643 return;
644 }
645
646 AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
647 if (!winData) {
648 return;
649 }
650
651 nsTObserverArray<AudioChannelAgent*>::ForwardIterator
652 iter(winData->mAgents);
653 while (iter.HasMore()) {
654 aFunc(iter.GetNext());
655 }
656 }
657
658 void
RefreshAgentsVolume(nsPIDOMWindowOuter * aWindow)659 AudioChannelService::RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow)
660 {
661 RefreshAgents(aWindow, [] (AudioChannelAgent* agent) {
662 agent->WindowVolumeChanged();
663 });
664 }
665
666 void
RefreshAgentsSuspend(nsPIDOMWindowOuter * aWindow,nsSuspendedTypes aSuspend)667 AudioChannelService::RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow,
668 nsSuspendedTypes aSuspend)
669 {
670 RefreshAgents(aWindow, [aSuspend] (AudioChannelAgent* agent) {
671 agent->WindowSuspendChanged(aSuspend);
672 });
673 }
674
675 void
SetWindowAudioCaptured(nsPIDOMWindowOuter * aWindow,uint64_t aInnerWindowID,bool aCapture)676 AudioChannelService::SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
677 uint64_t aInnerWindowID,
678 bool aCapture)
679 {
680 MOZ_ASSERT(NS_IsMainThread());
681 MOZ_ASSERT(aWindow);
682 MOZ_ASSERT(aWindow->IsOuterWindow());
683
684 MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
685 ("AudioChannelService, SetWindowAudioCaptured, window = %p, "
686 "aCapture = %d\n", aWindow, aCapture));
687
688 nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
689 if (!topWindow) {
690 return;
691 }
692
693 AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
694
695 // This can happen, but only during shutdown, because the the outer window
696 // changes ScriptableTop, so that its ID is different.
697 // In this case either we are capturing, and it's too late because the window
698 // has been closed anyways, or we are un-capturing, and everything has already
699 // been cleaned up by the HTMLMediaElements or the AudioContexts.
700 if (!winData) {
701 return;
702 }
703
704 if (aCapture != winData->mIsAudioCaptured) {
705 winData->mIsAudioCaptured = aCapture;
706 nsTObserverArray<AudioChannelAgent*>::ForwardIterator
707 iter(winData->mAgents);
708 while (iter.HasMore()) {
709 iter.GetNext()->WindowAudioCaptureChanged(aInnerWindowID, aCapture);
710 }
711 }
712 }
713
714 /* static */ const nsAttrValue::EnumTable*
GetAudioChannelTable()715 AudioChannelService::GetAudioChannelTable()
716 {
717 return kMozAudioChannelAttributeTable;
718 }
719
720 /* static */ AudioChannel
GetAudioChannel(const nsAString & aChannel)721 AudioChannelService::GetAudioChannel(const nsAString& aChannel)
722 {
723 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
724 if (aChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
725 return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value);
726 }
727 }
728
729 return AudioChannel::Normal;
730 }
731
732 /* static */ AudioChannel
GetDefaultAudioChannel()733 AudioChannelService::GetDefaultAudioChannel()
734 {
735 nsAutoString audioChannel(Preferences::GetString("media.defaultAudioChannel"));
736 if (audioChannel.IsEmpty()) {
737 return AudioChannel::Normal;
738 }
739
740 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
741 if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
742 return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value);
743 }
744 }
745
746 return AudioChannel::Normal;
747 }
748
749 /* static */ void
GetAudioChannelString(AudioChannel aChannel,nsAString & aString)750 AudioChannelService::GetAudioChannelString(AudioChannel aChannel,
751 nsAString& aString)
752 {
753 aString.AssignASCII("normal");
754
755 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
756 if (aChannel ==
757 static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value)) {
758 aString.AssignASCII(kMozAudioChannelAttributeTable[i].tag);
759 break;
760 }
761 }
762 }
763
764 /* static */ void
GetDefaultAudioChannelString(nsAString & aString)765 AudioChannelService::GetDefaultAudioChannelString(nsAString& aString)
766 {
767 aString.AssignASCII("normal");
768
769 nsAutoString audioChannel(Preferences::GetString("media.defaultAudioChannel"));
770 if (!audioChannel.IsEmpty()) {
771 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
772 if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
773 aString = audioChannel;
774 break;
775 }
776 }
777 }
778 }
779
780 AudioChannelService::AudioChannelWindow*
GetOrCreateWindowData(nsPIDOMWindowOuter * aWindow)781 AudioChannelService::GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow)
782 {
783 MOZ_ASSERT(NS_IsMainThread());
784 MOZ_ASSERT(aWindow);
785 MOZ_ASSERT(aWindow->IsOuterWindow());
786
787 AudioChannelWindow* winData = GetWindowData(aWindow->WindowID());
788 if (!winData) {
789 winData = new AudioChannelWindow(aWindow->WindowID());
790 mWindows.AppendElement(winData);
791 }
792
793 return winData;
794 }
795
796 AudioChannelService::AudioChannelWindow*
GetWindowData(uint64_t aWindowID) const797 AudioChannelService::GetWindowData(uint64_t aWindowID) const
798 {
799 nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
800 iter(mWindows);
801 while (iter.HasMore()) {
802 AudioChannelWindow* next = iter.GetNext();
803 if (next->mWindowID == aWindowID) {
804 return next;
805 }
806 }
807
808 return nullptr;
809 }
810
811 float
GetAudioChannelVolume(nsPIDOMWindowOuter * aWindow,AudioChannel aAudioChannel)812 AudioChannelService::GetAudioChannelVolume(nsPIDOMWindowOuter* aWindow,
813 AudioChannel aAudioChannel)
814 {
815 MOZ_ASSERT(NS_IsMainThread());
816 MOZ_ASSERT(aWindow);
817 MOZ_ASSERT(aWindow->IsOuterWindow());
818
819 AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
820 return winData->mChannels[(uint32_t)aAudioChannel].mVolume;
821 }
822
823 NS_IMETHODIMP
GetAudioChannelVolume(mozIDOMWindowProxy * aWindow,unsigned short aAudioChannel,float * aVolume)824 AudioChannelService::GetAudioChannelVolume(mozIDOMWindowProxy* aWindow,
825 unsigned short aAudioChannel,
826 float* aVolume)
827 {
828 MOZ_ASSERT(NS_IsMainThread());
829
830 auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
831 *aVolume = GetAudioChannelVolume(window, (AudioChannel)aAudioChannel);
832 return NS_OK;
833 }
834
835 void
SetAudioChannelVolume(nsPIDOMWindowOuter * aWindow,AudioChannel aAudioChannel,float aVolume)836 AudioChannelService::SetAudioChannelVolume(nsPIDOMWindowOuter* aWindow,
837 AudioChannel aAudioChannel,
838 float aVolume)
839 {
840 MOZ_ASSERT(NS_IsMainThread());
841 MOZ_ASSERT(aWindow);
842 MOZ_ASSERT(aWindow->IsOuterWindow());
843
844 MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
845 ("AudioChannelService, SetAudioChannelVolume, window = %p, type = %d, "
846 "volume = %f\n", aWindow, aAudioChannel, aVolume));
847
848 AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
849 winData->mChannels[(uint32_t)aAudioChannel].mVolume = aVolume;
850 RefreshAgentsVolumeAndPropagate(aAudioChannel, aWindow);
851 }
852
853 NS_IMETHODIMP
SetAudioChannelVolume(mozIDOMWindowProxy * aWindow,unsigned short aAudioChannel,float aVolume)854 AudioChannelService::SetAudioChannelVolume(mozIDOMWindowProxy* aWindow,
855 unsigned short aAudioChannel,
856 float aVolume)
857 {
858 MOZ_ASSERT(NS_IsMainThread());
859
860 auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
861 SetAudioChannelVolume(window, (AudioChannel)aAudioChannel, aVolume);
862 return NS_OK;
863 }
864
865 bool
GetAudioChannelMuted(nsPIDOMWindowOuter * aWindow,AudioChannel aAudioChannel)866 AudioChannelService::GetAudioChannelMuted(nsPIDOMWindowOuter* aWindow,
867 AudioChannel aAudioChannel)
868 {
869 MOZ_ASSERT(NS_IsMainThread());
870 MOZ_ASSERT(aWindow);
871 MOZ_ASSERT(aWindow->IsOuterWindow());
872
873 AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
874 return winData->mChannels[(uint32_t)aAudioChannel].mMuted;
875 }
876
877 NS_IMETHODIMP
GetAudioChannelMuted(mozIDOMWindowProxy * aWindow,unsigned short aAudioChannel,bool * aMuted)878 AudioChannelService::GetAudioChannelMuted(mozIDOMWindowProxy* aWindow,
879 unsigned short aAudioChannel,
880 bool* aMuted)
881 {
882 MOZ_ASSERT(NS_IsMainThread());
883
884 auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
885 *aMuted = GetAudioChannelMuted(window, (AudioChannel)aAudioChannel);
886 return NS_OK;
887 }
888
889 void
SetAudioChannelMuted(nsPIDOMWindowOuter * aWindow,AudioChannel aAudioChannel,bool aMuted)890 AudioChannelService::SetAudioChannelMuted(nsPIDOMWindowOuter* aWindow,
891 AudioChannel aAudioChannel,
892 bool aMuted)
893 {
894 MOZ_ASSERT(NS_IsMainThread());
895 MOZ_ASSERT(aWindow);
896 MOZ_ASSERT(aWindow->IsOuterWindow());
897
898 MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
899 ("AudioChannelService, SetAudioChannelMuted, window = %p, type = %d, "
900 "mute = %d\n", aWindow, aAudioChannel, aMuted));
901
902 if (aAudioChannel == AudioChannel::System) {
903 // Workaround for bug1183033, system channel type can always playback.
904 return;
905 }
906
907 AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
908 winData->mChannels[(uint32_t)aAudioChannel].mMuted = aMuted;
909 RefreshAgentsVolumeAndPropagate(aAudioChannel, aWindow);
910 }
911
912 NS_IMETHODIMP
SetAudioChannelMuted(mozIDOMWindowProxy * aWindow,unsigned short aAudioChannel,bool aMuted)913 AudioChannelService::SetAudioChannelMuted(mozIDOMWindowProxy* aWindow,
914 unsigned short aAudioChannel,
915 bool aMuted)
916 {
917 MOZ_ASSERT(NS_IsMainThread());
918
919 auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
920 SetAudioChannelMuted(window, (AudioChannel)aAudioChannel, aMuted);
921 return NS_OK;
922 }
923
924 bool
IsAudioChannelActive(nsPIDOMWindowOuter * aWindow,AudioChannel aAudioChannel)925 AudioChannelService::IsAudioChannelActive(nsPIDOMWindowOuter* aWindow,
926 AudioChannel aAudioChannel)
927 {
928 MOZ_ASSERT(NS_IsMainThread());
929 MOZ_ASSERT(aWindow);
930 MOZ_ASSERT(aWindow->IsOuterWindow());
931
932 AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
933 return !!winData->mChannels[(uint32_t)aAudioChannel].mNumberOfAgents;
934 }
935
936 NS_IMETHODIMP
IsAudioChannelActive(mozIDOMWindowProxy * aWindow,unsigned short aAudioChannel,bool * aActive)937 AudioChannelService::IsAudioChannelActive(mozIDOMWindowProxy* aWindow,
938 unsigned short aAudioChannel,
939 bool* aActive)
940 {
941 MOZ_ASSERT(NS_IsMainThread());
942
943 auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
944 *aActive = IsAudioChannelActive(window, (AudioChannel)aAudioChannel);
945 return NS_OK;
946 }
947 void
SetDefaultVolumeControlChannel(int32_t aChannel,bool aVisible)948 AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel,
949 bool aVisible)
950 {
951 SetDefaultVolumeControlChannelInternal(aChannel, aVisible,
952 CONTENT_PROCESS_ID_MAIN);
953 }
954
955 void
SetDefaultVolumeControlChannelInternal(int32_t aChannel,bool aVisible,uint64_t aChildID)956 AudioChannelService::SetDefaultVolumeControlChannelInternal(int32_t aChannel,
957 bool aVisible,
958 uint64_t aChildID)
959 {
960 if (!IsParentProcess()) {
961 ContentChild* cc = ContentChild::GetSingleton();
962 if (cc) {
963 cc->SendAudioChannelChangeDefVolChannel(aChannel, aVisible);
964 }
965
966 return;
967 }
968
969 // If this child is in the background and mDefChannelChildID is set to
970 // others then it means other child in the foreground already set it's
971 // own default channel.
972 if (!aVisible && mDefChannelChildID != aChildID) {
973 return;
974 }
975
976 // Workaround for the call screen app. The call screen app is running on the
977 // main process, that will results in wrong visible state. Because we use the
978 // docshell's active state as visible state, the main process is always
979 // active. Therefore, we will see the strange situation that the visible
980 // state of the call screen is always true. If the mDefChannelChildID is set
981 // to others then it means other child in the foreground already set it's
982 // own default channel already.
983 // Summary :
984 // Child process : foreground app always can set type.
985 // Parent process : check the mDefChannelChildID.
986 else if (aChildID == CONTENT_PROCESS_ID_MAIN &&
987 mDefChannelChildID != CONTENT_PROCESS_ID_UNKNOWN) {
988 return;
989 }
990
991 mDefChannelChildID = aVisible ? aChildID : CONTENT_PROCESS_ID_UNKNOWN;
992 nsAutoString channelName;
993
994 if (aChannel == -1) {
995 channelName.AssignASCII("unknown");
996 } else {
997 GetAudioChannelString(static_cast<AudioChannel>(aChannel), channelName);
998 }
999
1000 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1001 if (obs) {
1002 obs->NotifyObservers(nullptr, "default-volume-channel-changed",
1003 channelName.get());
1004 }
1005 }
1006
1007 void
MaybeSendStatusUpdate()1008 AudioChannelService::MaybeSendStatusUpdate()
1009 {
1010 if (IsParentProcess()) {
1011 return;
1012 }
1013
1014 bool telephonyChannel = TelephonyChannelIsActive();
1015 bool contentOrNormalChannel = ContentOrNormalChannelIsActive();
1016 bool anyChannel = AnyAudioChannelIsActive();
1017
1018 if (telephonyChannel == mTelephonyChannel &&
1019 contentOrNormalChannel == mContentOrNormalChannel &&
1020 anyChannel == mAnyChannel) {
1021 return;
1022 }
1023
1024 mTelephonyChannel = telephonyChannel;
1025 mContentOrNormalChannel = contentOrNormalChannel;
1026 mAnyChannel = anyChannel;
1027
1028 ContentChild* cc = ContentChild::GetSingleton();
1029 if (cc) {
1030 cc->SendAudioChannelServiceStatus(telephonyChannel, contentOrNormalChannel,
1031 anyChannel);
1032 }
1033 }
1034
1035 void
ChildStatusReceived(uint64_t aChildID,bool aTelephonyChannel,bool aContentOrNormalChannel,bool aAnyChannel)1036 AudioChannelService::ChildStatusReceived(uint64_t aChildID,
1037 bool aTelephonyChannel,
1038 bool aContentOrNormalChannel,
1039 bool aAnyChannel)
1040 {
1041 if (!aAnyChannel) {
1042 RemoveChildStatus(aChildID);
1043 return;
1044 }
1045
1046 AudioChannelChildStatus* data = GetChildStatus(aChildID);
1047 if (!data) {
1048 data = new AudioChannelChildStatus(aChildID);
1049 mPlayingChildren.AppendElement(data);
1050 }
1051
1052 data->mActiveTelephonyChannel = aTelephonyChannel;
1053 data->mActiveContentOrNormalChannel = aContentOrNormalChannel;
1054 }
1055
1056 void
RefreshAgentsAudioFocusChanged(AudioChannelAgent * aAgent)1057 AudioChannelService::RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent)
1058 {
1059 MOZ_ASSERT(aAgent);
1060
1061 nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
1062 iter(mWindows);
1063 while (iter.HasMore()) {
1064 AudioChannelWindow* winData = iter.GetNext();
1065 if (winData->mOwningAudioFocus) {
1066 winData->AudioFocusChanged(aAgent);
1067 }
1068 }
1069 }
1070
1071 void
RequestAudioFocus(AudioChannelAgent * aAgent)1072 AudioChannelService::AudioChannelWindow::RequestAudioFocus(AudioChannelAgent* aAgent)
1073 {
1074 MOZ_ASSERT(aAgent);
1075
1076 // Don't need to check audio focus for window-less agent.
1077 if (!aAgent->Window()) {
1078 return;
1079 }
1080
1081 // We already have the audio focus. No operation is needed.
1082 if (mOwningAudioFocus) {
1083 return;
1084 }
1085
1086 // Only foreground window can request audio focus, but it would still own the
1087 // audio focus even it goes to background. Audio focus would be abandoned
1088 // only when other foreground window starts audio competing.
1089 // One exception is if the pref "media.block-autoplay-until-in-foreground"
1090 // is on and the background page is the non-visited before. Because the media
1091 // in that page would be blocked until the page is going to foreground.
1092 mOwningAudioFocus = (!(aAgent->Window()->IsBackground()) ||
1093 aAgent->Window()->GetMediaSuspend() == nsISuspendedTypes::SUSPENDED_BLOCK) ;
1094
1095 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1096 ("AudioChannelWindow, RequestAudioFocus, this = %p, "
1097 "agent = %p, owning audio focus = %d\n",
1098 this, aAgent, mOwningAudioFocus));
1099 }
1100
1101 void
NotifyAudioCompetingChanged(AudioChannelAgent * aAgent)1102 AudioChannelService::AudioChannelWindow::NotifyAudioCompetingChanged(AudioChannelAgent* aAgent)
1103 {
1104 // This function may be called after RemoveAgentAndReduceAgentsNum(), so the
1105 // agent may be not contained in mAgent. In addition, the agent would still
1106 // be alive because we have kungFuDeathGrip in UnregisterAudioChannelAgent().
1107 MOZ_ASSERT(aAgent);
1108
1109 RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
1110 MOZ_ASSERT(service);
1111
1112 if (!service->IsEnableAudioCompeting()) {
1113 return;
1114 }
1115
1116 if (!IsAgentInvolvingInAudioCompeting(aAgent)) {
1117 return;
1118 }
1119
1120 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1121 ("AudioChannelWindow, NotifyAudioCompetingChanged, this = %p, "
1122 "agent = %p\n",
1123 this, aAgent));
1124
1125 service->RefreshAgentsAudioFocusChanged(aAgent);
1126 }
1127
1128 bool
IsAgentInvolvingInAudioCompeting(AudioChannelAgent * aAgent) const1129 AudioChannelService::AudioChannelWindow::IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const
1130 {
1131 MOZ_ASSERT(aAgent);
1132
1133 if(!mOwningAudioFocus) {
1134 return false;
1135 }
1136
1137 if (IsAudioCompetingInSameTab()) {
1138 return false;
1139 }
1140
1141 // TODO : add MediaSession::ambient kind, because it doens't interact with
1142 // other kinds.
1143 return true;
1144 }
1145
1146 bool
IsAudioCompetingInSameTab() const1147 AudioChannelService::AudioChannelWindow::IsAudioCompetingInSameTab() const
1148 {
1149 bool hasMultipleActiveAgents = IsEnableAudioCompetingForAllAgents() ?
1150 mAgents.Length() > 1 : mAudibleAgents.Length() > 1;
1151 return mOwningAudioFocus && hasMultipleActiveAgents;
1152 }
1153
1154 void
AudioFocusChanged(AudioChannelAgent * aNewPlayingAgent)1155 AudioChannelService::AudioChannelWindow::AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent)
1156 {
1157 // This agent isn't always known for the current window, because it can comes
1158 // from other window.
1159 MOZ_ASSERT(aNewPlayingAgent);
1160
1161 if (IsInactiveWindow()) {
1162 // These would happen in two situations,
1163 // (1) Audio in page A was ended, and another page B want to play audio.
1164 // Page A should abandon its focus.
1165 // (2) Audio was paused by remote-control, page should still own the focus.
1166 mOwningAudioFocus = IsContainingPlayingAgent(aNewPlayingAgent);
1167 } else {
1168 nsTObserverArray<AudioChannelAgent*>::ForwardIterator
1169 iter(IsEnableAudioCompetingForAllAgents() ? mAgents : mAudibleAgents);
1170 while (iter.HasMore()) {
1171 AudioChannelAgent* agent = iter.GetNext();
1172 MOZ_ASSERT(agent);
1173
1174 // Don't need to update the playing state of new playing agent.
1175 if (agent == aNewPlayingAgent) {
1176 continue;
1177 }
1178
1179 uint32_t type = GetCompetingBehavior(agent,
1180 aNewPlayingAgent->AudioChannelType());
1181
1182 // If window will be suspended, it needs to abandon the audio focus
1183 // because only one window can own audio focus at a time. However, we
1184 // would support multiple audio focus at the same time in the future.
1185 mOwningAudioFocus = (type == nsISuspendedTypes::NONE_SUSPENDED);
1186
1187 // TODO : support other behaviors which are definded in MediaSession API.
1188 switch (type) {
1189 case nsISuspendedTypes::NONE_SUSPENDED:
1190 case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
1191 agent->WindowSuspendChanged(type);
1192 break;
1193 }
1194 }
1195 }
1196
1197 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1198 ("AudioChannelWindow, AudioFocusChanged, this = %p, "
1199 "OwningAudioFocus = %d\n", this, mOwningAudioFocus));
1200 }
1201
1202 bool
IsContainingPlayingAgent(AudioChannelAgent * aAgent) const1203 AudioChannelService::AudioChannelWindow::IsContainingPlayingAgent(AudioChannelAgent* aAgent) const
1204 {
1205 return (aAgent->WindowID() == mWindowID);
1206 }
1207
1208 uint32_t
GetCompetingBehavior(AudioChannelAgent * aAgent,int32_t aIncomingChannelType) const1209 AudioChannelService::AudioChannelWindow::GetCompetingBehavior(AudioChannelAgent* aAgent,
1210 int32_t aIncomingChannelType) const
1211 {
1212 MOZ_ASSERT(aAgent);
1213 MOZ_ASSERT(IsEnableAudioCompetingForAllAgents() ?
1214 mAgents.Contains(aAgent) : mAudibleAgents.Contains(aAgent));
1215
1216 uint32_t competingBehavior = nsISuspendedTypes::NONE_SUSPENDED;
1217 int32_t presentChannelType = aAgent->AudioChannelType();
1218
1219 // TODO : add other competing cases for MediaSession API
1220 if (presentChannelType == int32_t(AudioChannel::Normal) &&
1221 aIncomingChannelType == int32_t(AudioChannel::Normal)) {
1222 competingBehavior = nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
1223 }
1224
1225 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1226 ("AudioChannelWindow, GetCompetingBehavior, this = %p, "
1227 "present type = %d, incoming channel = %d, behavior = %d\n",
1228 this, presentChannelType, aIncomingChannelType, competingBehavior));
1229
1230 return competingBehavior;
1231 }
1232
1233 /* static */ bool
IsAudioChannelMutedByDefault()1234 AudioChannelService::IsAudioChannelMutedByDefault()
1235 {
1236 CreateServiceIfNeeded();
1237 return sAudioChannelMutedByDefault;
1238 }
1239
1240 void
AppendAgent(AudioChannelAgent * aAgent,AudibleState aAudible)1241 AudioChannelService::AudioChannelWindow::AppendAgent(AudioChannelAgent* aAgent,
1242 AudibleState aAudible)
1243 {
1244 MOZ_ASSERT(aAgent);
1245
1246 RequestAudioFocus(aAgent);
1247 AppendAgentAndIncreaseAgentsNum(aAgent);
1248 AudioCapturedChanged(aAgent, AudioCaptureState::eCapturing);
1249 if (aAudible == AudibleState::eAudible) {
1250 AudioAudibleChanged(aAgent,
1251 AudibleState::eAudible,
1252 AudibleChangedReasons::eDataAudibleChanged);
1253 } else if (IsEnableAudioCompetingForAllAgents() &&
1254 aAudible != AudibleState::eAudible) {
1255 NotifyAudioCompetingChanged(aAgent);
1256 }
1257 }
1258
1259 void
RemoveAgent(AudioChannelAgent * aAgent)1260 AudioChannelService::AudioChannelWindow::RemoveAgent(AudioChannelAgent* aAgent)
1261 {
1262 MOZ_ASSERT(aAgent);
1263
1264 RemoveAgentAndReduceAgentsNum(aAgent);
1265 AudioCapturedChanged(aAgent, AudioCaptureState::eNotCapturing);
1266 AudioAudibleChanged(aAgent,
1267 AudibleState::eNotAudible,
1268 AudibleChangedReasons::ePauseStateChanged);
1269 }
1270
1271 void
AppendAgentAndIncreaseAgentsNum(AudioChannelAgent * aAgent)1272 AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent)
1273 {
1274 MOZ_ASSERT(aAgent);
1275 MOZ_ASSERT(!mAgents.Contains(aAgent));
1276
1277 int32_t channel = aAgent->AudioChannelType();
1278 mAgents.AppendElement(aAgent);
1279
1280 ++mChannels[channel].mNumberOfAgents;
1281
1282 // The first one, we must inform the BrowserElementAudioChannel.
1283 if (mChannels[channel].mNumberOfAgents == 1) {
1284 NotifyChannelActive(aAgent->WindowID(),
1285 static_cast<AudioChannel>(channel),
1286 true);
1287 }
1288 }
1289
1290 void
RemoveAgentAndReduceAgentsNum(AudioChannelAgent * aAgent)1291 AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(AudioChannelAgent* aAgent)
1292 {
1293 MOZ_ASSERT(aAgent);
1294 MOZ_ASSERT(mAgents.Contains(aAgent));
1295
1296 int32_t channel = aAgent->AudioChannelType();
1297 mAgents.RemoveElement(aAgent);
1298
1299 MOZ_ASSERT(mChannels[channel].mNumberOfAgents > 0);
1300 --mChannels[channel].mNumberOfAgents;
1301
1302 if (mChannels[channel].mNumberOfAgents == 0) {
1303 NotifyChannelActive(aAgent->WindowID(),
1304 static_cast<AudioChannel>(channel),
1305 false);
1306 }
1307 }
1308
1309 void
AudioCapturedChanged(AudioChannelAgent * aAgent,AudioCaptureState aCapture)1310 AudioChannelService::AudioChannelWindow::AudioCapturedChanged(AudioChannelAgent* aAgent,
1311 AudioCaptureState aCapture)
1312 {
1313 MOZ_ASSERT(aAgent);
1314
1315 if (mIsAudioCaptured) {
1316 aAgent->WindowAudioCaptureChanged(aAgent->InnerWindowID(), aCapture);
1317 }
1318 }
1319
1320 void
AudioAudibleChanged(AudioChannelAgent * aAgent,AudibleState aAudible,AudibleChangedReasons aReason)1321 AudioChannelService::AudioChannelWindow::AudioAudibleChanged(AudioChannelAgent* aAgent,
1322 AudibleState aAudible,
1323 AudibleChangedReasons aReason)
1324 {
1325 MOZ_ASSERT(aAgent);
1326
1327 if (aAudible == AudibleState::eAudible) {
1328 AppendAudibleAgentIfNotContained(aAgent, aReason);
1329 } else {
1330 RemoveAudibleAgentIfContained(aAgent, aReason);
1331 }
1332
1333 if (aAudible == AudibleState::eAudible) {
1334 NotifyAudioCompetingChanged(aAgent);
1335 } else if (aAudible != AudibleState::eNotAudible) {
1336 MaybeNotifyMediaBlocked(aAgent);
1337 }
1338 }
1339
1340 void
AppendAudibleAgentIfNotContained(AudioChannelAgent * aAgent,AudibleChangedReasons aReason)1341 AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent,
1342 AudibleChangedReasons aReason)
1343 {
1344 MOZ_ASSERT(aAgent);
1345 MOZ_ASSERT(mAgents.Contains(aAgent));
1346
1347 if (!mAudibleAgents.Contains(aAgent)) {
1348 mAudibleAgents.AppendElement(aAgent);
1349 if (IsFirstAudibleAgent()) {
1350 NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible, aReason);
1351 }
1352 }
1353 }
1354
1355 void
RemoveAudibleAgentIfContained(AudioChannelAgent * aAgent,AudibleChangedReasons aReason)1356 AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent,
1357 AudibleChangedReasons aReason)
1358 {
1359 MOZ_ASSERT(aAgent);
1360
1361 if (mAudibleAgents.Contains(aAgent)) {
1362 mAudibleAgents.RemoveElement(aAgent);
1363 if (IsLastAudibleAgent()) {
1364 NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible, aReason);
1365 }
1366 }
1367 }
1368
1369 bool
IsFirstAudibleAgent() const1370 AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const
1371 {
1372 return (mAudibleAgents.Length() == 1);
1373 }
1374
1375 bool
IsLastAudibleAgent() const1376 AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const
1377 {
1378 return mAudibleAgents.IsEmpty();
1379 }
1380
1381 bool
IsInactiveWindow() const1382 AudioChannelService::AudioChannelWindow::IsInactiveWindow() const
1383 {
1384 return IsEnableAudioCompetingForAllAgents() ?
1385 mAudibleAgents.IsEmpty() && mAgents.IsEmpty() : mAudibleAgents.IsEmpty();
1386 }
1387
1388 void
NotifyAudioAudibleChanged(nsPIDOMWindowOuter * aWindow,AudibleState aAudible,AudibleChangedReasons aReason)1389 AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow,
1390 AudibleState aAudible,
1391 AudibleChangedReasons aReason)
1392 {
1393 RefPtr<AudioPlaybackRunnable> runnable =
1394 new AudioPlaybackRunnable(aWindow,
1395 aAudible == AudibleState::eAudible,
1396 aReason);
1397 DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
1398 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
1399 }
1400
1401 void
NotifyChannelActive(uint64_t aWindowID,AudioChannel aChannel,bool aActive)1402 AudioChannelService::AudioChannelWindow::NotifyChannelActive(uint64_t aWindowID,
1403 AudioChannel aChannel,
1404 bool aActive)
1405 {
1406 RefPtr<NotifyChannelActiveRunnable> runnable =
1407 new NotifyChannelActiveRunnable(aWindowID, aChannel, aActive);
1408 DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
1409 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
1410 }
1411
1412 void
MaybeNotifyMediaBlocked(AudioChannelAgent * aAgent)1413 AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlocked(AudioChannelAgent* aAgent)
1414 {
1415 nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
1416 if (!window) {
1417 return;
1418 }
1419
1420 MOZ_ASSERT(window->IsOuterWindow());
1421 if (window->GetMediaSuspend() != nsISuspendedTypes::SUSPENDED_BLOCK) {
1422 return;
1423 }
1424
1425 NS_DispatchToCurrentThread(NS_NewRunnableFunction([window] () -> void {
1426 nsCOMPtr<nsIObserverService> observerService =
1427 services::GetObserverService();
1428 if (NS_WARN_IF(!observerService)) {
1429 return;
1430 }
1431
1432 observerService->NotifyObservers(ToSupports(window),
1433 "audio-playback",
1434 u"block");
1435 })
1436 );
1437 }
1438