AudioChannelService.cpp 42 KB

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