|
1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ |
|
2 /* vim: set ts=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 #include "AudioChannelServiceChild.h" |
|
9 |
|
10 #include "base/basictypes.h" |
|
11 |
|
12 #include "mozilla/Services.h" |
|
13 #include "mozilla/StaticPtr.h" |
|
14 #include "mozilla/unused.h" |
|
15 |
|
16 #include "mozilla/dom/ContentParent.h" |
|
17 |
|
18 #include "nsThreadUtils.h" |
|
19 #include "nsHashPropertyBag.h" |
|
20 #include "nsComponentManagerUtils.h" |
|
21 #include "nsPIDOMWindow.h" |
|
22 #include "nsServiceManagerUtils.h" |
|
23 |
|
24 #ifdef MOZ_WIDGET_GONK |
|
25 #include "nsJSUtils.h" |
|
26 #include "nsCxPusher.h" |
|
27 #include "nsIAudioManager.h" |
|
28 #include "SpeakerManagerService.h" |
|
29 #define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1" |
|
30 #endif |
|
31 |
|
32 #include "mozilla/Preferences.h" |
|
33 |
|
34 using namespace mozilla; |
|
35 using namespace mozilla::dom; |
|
36 using namespace mozilla::hal; |
|
37 |
|
38 StaticRefPtr<AudioChannelService> gAudioChannelService; |
|
39 |
|
40 // Mappings from 'mozaudiochannel' attribute strings to an enumeration. |
|
41 static const nsAttrValue::EnumTable kMozAudioChannelAttributeTable[] = { |
|
42 { "normal", (int16_t)AudioChannel::Normal }, |
|
43 { "content", (int16_t)AudioChannel::Content }, |
|
44 { "notification", (int16_t)AudioChannel::Notification }, |
|
45 { "alarm", (int16_t)AudioChannel::Alarm }, |
|
46 { "telephony", (int16_t)AudioChannel::Telephony }, |
|
47 { "ringer", (int16_t)AudioChannel::Ringer }, |
|
48 { "publicnotification", (int16_t)AudioChannel::Publicnotification }, |
|
49 { nullptr } |
|
50 }; |
|
51 |
|
52 // static |
|
53 AudioChannelService* |
|
54 AudioChannelService::GetAudioChannelService() |
|
55 { |
|
56 MOZ_ASSERT(NS_IsMainThread()); |
|
57 |
|
58 if (XRE_GetProcessType() != GeckoProcessType_Default) { |
|
59 return AudioChannelServiceChild::GetAudioChannelService(); |
|
60 } |
|
61 |
|
62 // If we already exist, exit early |
|
63 if (gAudioChannelService) { |
|
64 return gAudioChannelService; |
|
65 } |
|
66 |
|
67 // Create new instance, register, return |
|
68 nsRefPtr<AudioChannelService> service = new AudioChannelService(); |
|
69 NS_ENSURE_TRUE(service, nullptr); |
|
70 |
|
71 gAudioChannelService = service; |
|
72 return gAudioChannelService; |
|
73 } |
|
74 |
|
75 void |
|
76 AudioChannelService::Shutdown() |
|
77 { |
|
78 if (XRE_GetProcessType() != GeckoProcessType_Default) { |
|
79 return AudioChannelServiceChild::Shutdown(); |
|
80 } |
|
81 |
|
82 if (gAudioChannelService) { |
|
83 gAudioChannelService = nullptr; |
|
84 } |
|
85 } |
|
86 |
|
87 NS_IMPL_ISUPPORTS(AudioChannelService, nsIObserver, nsITimerCallback) |
|
88 |
|
89 AudioChannelService::AudioChannelService() |
|
90 : mCurrentHigherChannel(-1) |
|
91 , mCurrentVisibleHigherChannel(-1) |
|
92 , mPlayableHiddenContentChildID(CONTENT_PROCESS_ID_UNKNOWN) |
|
93 , mDisabled(false) |
|
94 , mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN) |
|
95 { |
|
96 if (XRE_GetProcessType() == GeckoProcessType_Default) { |
|
97 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
|
98 if (obs) { |
|
99 obs->AddObserver(this, "ipc:content-shutdown", false); |
|
100 obs->AddObserver(this, "xpcom-shutdown", false); |
|
101 #ifdef MOZ_WIDGET_GONK |
|
102 // To monitor the volume settings based on audio channel. |
|
103 obs->AddObserver(this, "mozsettings-changed", false); |
|
104 #endif |
|
105 } |
|
106 } |
|
107 } |
|
108 |
|
109 AudioChannelService::~AudioChannelService() |
|
110 { |
|
111 } |
|
112 |
|
113 void |
|
114 AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent, |
|
115 AudioChannel aChannel, |
|
116 bool aWithVideo) |
|
117 { |
|
118 if (mDisabled) { |
|
119 return; |
|
120 } |
|
121 |
|
122 AudioChannelAgentData* data = new AudioChannelAgentData(aChannel, |
|
123 true /* aElementHidden */, |
|
124 AUDIO_CHANNEL_STATE_MUTED /* aState */, |
|
125 aWithVideo); |
|
126 mAgents.Put(aAgent, data); |
|
127 RegisterType(aChannel, CONTENT_PROCESS_ID_MAIN, aWithVideo); |
|
128 |
|
129 // If this is the first agent for this window, we must notify the observers. |
|
130 uint32_t count = CountWindow(aAgent->Window()); |
|
131 if (count == 1) { |
|
132 nsCOMPtr<nsIObserverService> observerService = |
|
133 services::GetObserverService(); |
|
134 if (observerService) { |
|
135 observerService->NotifyObservers(ToSupports(aAgent->Window()), |
|
136 "media-playback", |
|
137 NS_LITERAL_STRING("active").get()); |
|
138 } |
|
139 } |
|
140 } |
|
141 |
|
142 void |
|
143 AudioChannelService::RegisterType(AudioChannel aChannel, uint64_t aChildID, |
|
144 bool aWithVideo) |
|
145 { |
|
146 if (mDisabled) { |
|
147 return; |
|
148 } |
|
149 |
|
150 AudioChannelInternalType type = GetInternalType(aChannel, true); |
|
151 mChannelCounters[type].AppendElement(aChildID); |
|
152 |
|
153 if (XRE_GetProcessType() == GeckoProcessType_Default) { |
|
154 // Since there is another telephony registered, we can unregister old one |
|
155 // immediately. |
|
156 if (mDeferTelChannelTimer && aChannel == AudioChannel::Telephony) { |
|
157 mDeferTelChannelTimer->Cancel(); |
|
158 mDeferTelChannelTimer = nullptr; |
|
159 UnregisterTypeInternal(aChannel, mTimerElementHidden, mTimerChildID, |
|
160 false); |
|
161 } |
|
162 |
|
163 if (aWithVideo) { |
|
164 mWithVideoChildIDs.AppendElement(aChildID); |
|
165 } |
|
166 |
|
167 // No hidden content channel can be playable if there is a content channel |
|
168 // in foreground (bug 855208), nor if there is a normal channel with video |
|
169 // in foreground (bug 894249). |
|
170 if (type == AUDIO_CHANNEL_INT_CONTENT || |
|
171 (type == AUDIO_CHANNEL_INT_NORMAL && |
|
172 mWithVideoChildIDs.Contains(aChildID))) { |
|
173 mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; |
|
174 } |
|
175 // One hidden content channel can be playable only when there is no any |
|
176 // content channel in the foreground, and no normal channel with video in |
|
177 // foreground. |
|
178 else if (type == AUDIO_CHANNEL_INT_CONTENT_HIDDEN && |
|
179 mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) { |
|
180 mPlayableHiddenContentChildID = aChildID; |
|
181 } |
|
182 |
|
183 // In order to avoid race conditions, it's safer to notify any existing |
|
184 // agent any time a new one is registered. |
|
185 SendAudioChannelChangedNotification(aChildID); |
|
186 Notify(); |
|
187 } |
|
188 } |
|
189 |
|
190 void |
|
191 AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent) |
|
192 { |
|
193 if (mDisabled) { |
|
194 return; |
|
195 } |
|
196 |
|
197 nsAutoPtr<AudioChannelAgentData> data; |
|
198 mAgents.RemoveAndForget(aAgent, data); |
|
199 |
|
200 if (data) { |
|
201 UnregisterType(data->mChannel, data->mElementHidden, |
|
202 CONTENT_PROCESS_ID_MAIN, data->mWithVideo); |
|
203 } |
|
204 #ifdef MOZ_WIDGET_GONK |
|
205 bool active = AnyAudioChannelIsActive(); |
|
206 for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) { |
|
207 mSpeakerManager[i]->SetAudioChannelActive(active); |
|
208 } |
|
209 #endif |
|
210 |
|
211 // If this is the last agent for this window, we must notify the observers. |
|
212 uint32_t count = CountWindow(aAgent->Window()); |
|
213 if (count == 0) { |
|
214 nsCOMPtr<nsIObserverService> observerService = |
|
215 services::GetObserverService(); |
|
216 if (observerService) { |
|
217 observerService->NotifyObservers(ToSupports(aAgent->Window()), |
|
218 "media-playback", |
|
219 NS_LITERAL_STRING("inactive").get()); |
|
220 } |
|
221 } |
|
222 } |
|
223 |
|
224 void |
|
225 AudioChannelService::UnregisterType(AudioChannel aChannel, |
|
226 bool aElementHidden, |
|
227 uint64_t aChildID, |
|
228 bool aWithVideo) |
|
229 { |
|
230 if (mDisabled) { |
|
231 return; |
|
232 } |
|
233 |
|
234 // There are two reasons to defer the decrease of telephony channel. |
|
235 // 1. User can have time to remove device from his ear before music resuming. |
|
236 // 2. Give BT SCO to be disconnected before starting to connect A2DP. |
|
237 if (XRE_GetProcessType() == GeckoProcessType_Default && |
|
238 aChannel == AudioChannel::Telephony && |
|
239 (mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].Length() + |
|
240 mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].Length()) == 1) { |
|
241 mTimerElementHidden = aElementHidden; |
|
242 mTimerChildID = aChildID; |
|
243 mDeferTelChannelTimer = do_CreateInstance("@mozilla.org/timer;1"); |
|
244 mDeferTelChannelTimer->InitWithCallback(this, 1500, nsITimer::TYPE_ONE_SHOT); |
|
245 return; |
|
246 } |
|
247 |
|
248 UnregisterTypeInternal(aChannel, aElementHidden, aChildID, aWithVideo); |
|
249 } |
|
250 |
|
251 void |
|
252 AudioChannelService::UnregisterTypeInternal(AudioChannel aChannel, |
|
253 bool aElementHidden, |
|
254 uint64_t aChildID, |
|
255 bool aWithVideo) |
|
256 { |
|
257 // The array may contain multiple occurrence of this appId but |
|
258 // this should remove only the first one. |
|
259 AudioChannelInternalType type = GetInternalType(aChannel, aElementHidden); |
|
260 MOZ_ASSERT(mChannelCounters[type].Contains(aChildID)); |
|
261 mChannelCounters[type].RemoveElement(aChildID); |
|
262 |
|
263 // In order to avoid race conditions, it's safer to notify any existing |
|
264 // agent any time a new one is registered. |
|
265 if (XRE_GetProcessType() == GeckoProcessType_Default) { |
|
266 // No hidden content channel is playable if the original playable hidden |
|
267 // process does not need to play audio from background anymore. |
|
268 if (aChannel == AudioChannel::Content && |
|
269 mPlayableHiddenContentChildID == aChildID && |
|
270 !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID)) { |
|
271 mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; |
|
272 } |
|
273 |
|
274 if (aWithVideo) { |
|
275 MOZ_ASSERT(mWithVideoChildIDs.Contains(aChildID)); |
|
276 mWithVideoChildIDs.RemoveElement(aChildID); |
|
277 } |
|
278 |
|
279 SendAudioChannelChangedNotification(aChildID); |
|
280 Notify(); |
|
281 } |
|
282 } |
|
283 |
|
284 void |
|
285 AudioChannelService::UpdateChannelType(AudioChannel aChannel, |
|
286 uint64_t aChildID, |
|
287 bool aElementHidden, |
|
288 bool aElementWasHidden) |
|
289 { |
|
290 // Calculate the new and old internal type and update the hashtable if needed. |
|
291 AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden); |
|
292 AudioChannelInternalType oldType = GetInternalType(aChannel, aElementWasHidden); |
|
293 |
|
294 if (newType != oldType) { |
|
295 mChannelCounters[newType].AppendElement(aChildID); |
|
296 MOZ_ASSERT(mChannelCounters[oldType].Contains(aChildID)); |
|
297 mChannelCounters[oldType].RemoveElement(aChildID); |
|
298 } |
|
299 |
|
300 // No hidden content channel can be playable if there is a content channel |
|
301 // in foreground (bug 855208), nor if there is a normal channel with video |
|
302 // in foreground (bug 894249). |
|
303 if (newType == AUDIO_CHANNEL_INT_CONTENT || |
|
304 (newType == AUDIO_CHANNEL_INT_NORMAL && |
|
305 mWithVideoChildIDs.Contains(aChildID))) { |
|
306 mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; |
|
307 } |
|
308 // If there is no content channel in foreground and no normal channel with |
|
309 // video in foreground, the last content channel which goes from foreground |
|
310 // to background can be playable. |
|
311 else if (oldType == AUDIO_CHANNEL_INT_CONTENT && |
|
312 newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN && |
|
313 mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) { |
|
314 mPlayableHiddenContentChildID = aChildID; |
|
315 } |
|
316 } |
|
317 |
|
318 AudioChannelState |
|
319 AudioChannelService::GetState(AudioChannelAgent* aAgent, bool aElementHidden) |
|
320 { |
|
321 AudioChannelAgentData* data; |
|
322 if (!mAgents.Get(aAgent, &data)) { |
|
323 return AUDIO_CHANNEL_STATE_MUTED; |
|
324 } |
|
325 |
|
326 bool oldElementHidden = data->mElementHidden; |
|
327 // Update visibility. |
|
328 data->mElementHidden = aElementHidden; |
|
329 |
|
330 data->mState = GetStateInternal(data->mChannel, CONTENT_PROCESS_ID_MAIN, |
|
331 aElementHidden, oldElementHidden); |
|
332 return data->mState; |
|
333 } |
|
334 |
|
335 AudioChannelState |
|
336 AudioChannelService::GetStateInternal(AudioChannel aChannel, uint64_t aChildID, |
|
337 bool aElementHidden, |
|
338 bool aElementWasHidden) |
|
339 { |
|
340 UpdateChannelType(aChannel, aChildID, aElementHidden, aElementWasHidden); |
|
341 |
|
342 // Calculating the new and old type and update the hashtable if needed. |
|
343 AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden); |
|
344 AudioChannelInternalType oldType = GetInternalType(aChannel, |
|
345 aElementWasHidden); |
|
346 |
|
347 if (newType != oldType && |
|
348 (aChannel == AudioChannel::Content || |
|
349 (aChannel == AudioChannel::Normal && |
|
350 mWithVideoChildIDs.Contains(aChildID)))) { |
|
351 Notify(); |
|
352 } |
|
353 |
|
354 SendAudioChannelChangedNotification(aChildID); |
|
355 |
|
356 // Let play any visible audio channel. |
|
357 if (!aElementHidden) { |
|
358 if (CheckVolumeFadedCondition(newType, aElementHidden)) { |
|
359 return AUDIO_CHANNEL_STATE_FADED; |
|
360 } |
|
361 return AUDIO_CHANNEL_STATE_NORMAL; |
|
362 } |
|
363 |
|
364 // We are not visible, maybe we have to mute. |
|
365 if (newType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN || |
|
366 (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN && |
|
367 // One process can have multiple content channels; and during the |
|
368 // transition from foreground to background, its content channels will be |
|
369 // updated with correct visibility status one by one. All its content |
|
370 // channels should remain playable until all of their visibility statuses |
|
371 // have been updated as hidden. After all its content channels have been |
|
372 // updated properly as hidden, mPlayableHiddenContentChildID is used to |
|
373 // check whether this background process is playable or not. |
|
374 !(mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) || |
|
375 (mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() && |
|
376 mPlayableHiddenContentChildID == aChildID)))) { |
|
377 return AUDIO_CHANNEL_STATE_MUTED; |
|
378 } |
|
379 |
|
380 // After checking the condition on normal & content channel, if the state |
|
381 // is not on muted then checking other higher channels type here. |
|
382 if (ChannelsActiveWithHigherPriorityThan(newType)) { |
|
383 MOZ_ASSERT(newType != AUDIO_CHANNEL_INT_NORMAL_HIDDEN); |
|
384 if (CheckVolumeFadedCondition(newType, aElementHidden)) { |
|
385 return AUDIO_CHANNEL_STATE_FADED; |
|
386 } |
|
387 return AUDIO_CHANNEL_STATE_MUTED; |
|
388 } |
|
389 |
|
390 return AUDIO_CHANNEL_STATE_NORMAL; |
|
391 } |
|
392 |
|
393 bool |
|
394 AudioChannelService::CheckVolumeFadedCondition(AudioChannelInternalType aType, |
|
395 bool aElementHidden) |
|
396 { |
|
397 // Only normal & content channels are considered |
|
398 if (aType > AUDIO_CHANNEL_INT_CONTENT_HIDDEN) { |
|
399 return false; |
|
400 } |
|
401 |
|
402 // Consider that audio from notification is with short duration |
|
403 // so just fade the volume not pause it |
|
404 if (mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty() && |
|
405 mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN].IsEmpty()) { |
|
406 return false; |
|
407 } |
|
408 |
|
409 // Since this element is on the foreground, it can be allowed to play always. |
|
410 // So return true directly when there is any notification channel alive. |
|
411 if (aElementHidden == false) { |
|
412 return true; |
|
413 } |
|
414 |
|
415 // If element is on the background, it is possible paused by channels higher |
|
416 // then notification. |
|
417 for (int i = AUDIO_CHANNEL_INT_LAST - 1; |
|
418 i != AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN; --i) { |
|
419 if (!mChannelCounters[i].IsEmpty()) { |
|
420 return false; |
|
421 } |
|
422 } |
|
423 |
|
424 return true; |
|
425 } |
|
426 |
|
427 bool |
|
428 AudioChannelService::ContentOrNormalChannelIsActive() |
|
429 { |
|
430 return !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() || |
|
431 !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].IsEmpty() || |
|
432 !mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty(); |
|
433 } |
|
434 |
|
435 bool |
|
436 AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID) |
|
437 { |
|
438 return mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) || |
|
439 mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID) || |
|
440 mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].Contains(aChildID); |
|
441 } |
|
442 |
|
443 void |
|
444 AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel, |
|
445 bool aHidden) |
|
446 { |
|
447 SetDefaultVolumeControlChannelInternal(aChannel, aHidden, |
|
448 CONTENT_PROCESS_ID_MAIN); |
|
449 } |
|
450 |
|
451 void |
|
452 AudioChannelService::SetDefaultVolumeControlChannelInternal(int32_t aChannel, |
|
453 bool aHidden, |
|
454 uint64_t aChildID) |
|
455 { |
|
456 if (XRE_GetProcessType() != GeckoProcessType_Default) { |
|
457 return; |
|
458 } |
|
459 |
|
460 // If this child is in the background and mDefChannelChildID is set to |
|
461 // others then it means other child in the foreground already set it's |
|
462 // own default channel already. |
|
463 if (!aHidden && mDefChannelChildID != aChildID) { |
|
464 return; |
|
465 } |
|
466 |
|
467 mDefChannelChildID = aChildID; |
|
468 nsString channelName; |
|
469 |
|
470 if (aChannel == -1) { |
|
471 channelName.AssignASCII("unknown"); |
|
472 } else { |
|
473 GetAudioChannelString(static_cast<AudioChannel>(aChannel), channelName); |
|
474 } |
|
475 |
|
476 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
|
477 if (obs) { |
|
478 obs->NotifyObservers(nullptr, "default-volume-channel-changed", |
|
479 channelName.get()); |
|
480 } |
|
481 } |
|
482 |
|
483 void |
|
484 AudioChannelService::SendAudioChannelChangedNotification(uint64_t aChildID) |
|
485 { |
|
486 if (XRE_GetProcessType() != GeckoProcessType_Default) { |
|
487 return; |
|
488 } |
|
489 |
|
490 nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag(); |
|
491 props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), aChildID); |
|
492 |
|
493 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
|
494 if (obs) { |
|
495 obs->NotifyObservers(static_cast<nsIWritablePropertyBag*>(props), |
|
496 "audio-channel-process-changed", nullptr); |
|
497 } |
|
498 |
|
499 // Calculating the most important active channel. |
|
500 int32_t higher = -1; |
|
501 |
|
502 // Top-Down in the hierarchy for visible elements |
|
503 if (!mChannelCounters[AUDIO_CHANNEL_INT_PUBLICNOTIFICATION].IsEmpty()) { |
|
504 higher = static_cast<int32_t>(AudioChannel::Publicnotification); |
|
505 } |
|
506 |
|
507 else if (!mChannelCounters[AUDIO_CHANNEL_INT_RINGER].IsEmpty()) { |
|
508 higher = static_cast<int32_t>(AudioChannel::Ringer); |
|
509 } |
|
510 |
|
511 else if (!mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].IsEmpty()) { |
|
512 higher = static_cast<int32_t>(AudioChannel::Telephony); |
|
513 } |
|
514 |
|
515 else if (!mChannelCounters[AUDIO_CHANNEL_INT_ALARM].IsEmpty()) { |
|
516 higher = static_cast<int32_t>(AudioChannel::Alarm); |
|
517 } |
|
518 |
|
519 else if (!mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty()) { |
|
520 higher = static_cast<int32_t>(AudioChannel::Notification); |
|
521 } |
|
522 |
|
523 else if (!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) { |
|
524 higher = static_cast<int32_t>(AudioChannel::Content); |
|
525 } |
|
526 |
|
527 else if (!mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty()) { |
|
528 higher = static_cast<int32_t>(AudioChannel::Normal); |
|
529 } |
|
530 |
|
531 int32_t visibleHigher = higher; |
|
532 |
|
533 // Top-Down in the hierarchy for non-visible elements |
|
534 // And we can ignore normal channel because it can't play in the background. |
|
535 int32_t index; |
|
536 for (index = 0; kMozAudioChannelAttributeTable[index].tag; ++index); |
|
537 |
|
538 for (--index; |
|
539 kMozAudioChannelAttributeTable[index].value > higher && |
|
540 kMozAudioChannelAttributeTable[index].value > (int16_t)AudioChannel::Normal; |
|
541 --index) { |
|
542 if (kMozAudioChannelAttributeTable[index].value == (int16_t)AudioChannel::Content && |
|
543 mPlayableHiddenContentChildID != CONTENT_PROCESS_ID_UNKNOWN) { |
|
544 higher = kMozAudioChannelAttributeTable[index].value; |
|
545 } |
|
546 |
|
547 // Each channel type will be split to fg and bg for recording the state, |
|
548 // so here need to do a translation. |
|
549 if (!mChannelCounters[index * 2 + 1].IsEmpty()) { |
|
550 higher = kMozAudioChannelAttributeTable[index].value; |
|
551 break; |
|
552 } |
|
553 } |
|
554 |
|
555 if (higher != mCurrentHigherChannel) { |
|
556 mCurrentHigherChannel = higher; |
|
557 |
|
558 nsString channelName; |
|
559 if (mCurrentHigherChannel != -1) { |
|
560 GetAudioChannelString(static_cast<AudioChannel>(mCurrentHigherChannel), |
|
561 channelName); |
|
562 } else { |
|
563 channelName.AssignLiteral("none"); |
|
564 } |
|
565 |
|
566 if (obs) { |
|
567 obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get()); |
|
568 } |
|
569 } |
|
570 |
|
571 if (visibleHigher != mCurrentVisibleHigherChannel) { |
|
572 mCurrentVisibleHigherChannel = visibleHigher; |
|
573 |
|
574 nsString channelName; |
|
575 if (mCurrentVisibleHigherChannel != -1) { |
|
576 GetAudioChannelString(static_cast<AudioChannel>(mCurrentVisibleHigherChannel), |
|
577 channelName); |
|
578 } else { |
|
579 channelName.AssignLiteral("none"); |
|
580 } |
|
581 |
|
582 if (obs) { |
|
583 obs->NotifyObservers(nullptr, "visible-audio-channel-changed", channelName.get()); |
|
584 } |
|
585 } |
|
586 } |
|
587 |
|
588 PLDHashOperator |
|
589 AudioChannelService::NotifyEnumerator(AudioChannelAgent* aAgent, |
|
590 AudioChannelAgentData* aData, void* aUnused) |
|
591 { |
|
592 MOZ_ASSERT(aAgent); |
|
593 aAgent->NotifyAudioChannelStateChanged(); |
|
594 return PL_DHASH_NEXT; |
|
595 } |
|
596 |
|
597 void |
|
598 AudioChannelService::Notify() |
|
599 { |
|
600 MOZ_ASSERT(NS_IsMainThread()); |
|
601 |
|
602 // Notify any agent for the main process. |
|
603 mAgents.EnumerateRead(NotifyEnumerator, nullptr); |
|
604 |
|
605 // Notify for the child processes. |
|
606 nsTArray<ContentParent*> children; |
|
607 ContentParent::GetAll(children); |
|
608 for (uint32_t i = 0; i < children.Length(); i++) { |
|
609 unused << children[i]->SendAudioChannelNotify(); |
|
610 } |
|
611 } |
|
612 |
|
613 NS_IMETHODIMP |
|
614 AudioChannelService::Notify(nsITimer* aTimer) |
|
615 { |
|
616 UnregisterTypeInternal(AudioChannel::Telephony, mTimerElementHidden, |
|
617 mTimerChildID, false); |
|
618 mDeferTelChannelTimer = nullptr; |
|
619 return NS_OK; |
|
620 } |
|
621 |
|
622 bool |
|
623 AudioChannelService::AnyAudioChannelIsActive() |
|
624 { |
|
625 for (int i = AUDIO_CHANNEL_INT_LAST - 1; |
|
626 i >= AUDIO_CHANNEL_INT_NORMAL; --i) { |
|
627 if (!mChannelCounters[i].IsEmpty()) { |
|
628 return true; |
|
629 } |
|
630 } |
|
631 |
|
632 return false; |
|
633 } |
|
634 |
|
635 bool |
|
636 AudioChannelService::ChannelsActiveWithHigherPriorityThan( |
|
637 AudioChannelInternalType aType) |
|
638 { |
|
639 for (int i = AUDIO_CHANNEL_INT_LAST - 1; |
|
640 i != AUDIO_CHANNEL_INT_CONTENT_HIDDEN; --i) { |
|
641 if (i == aType) { |
|
642 return false; |
|
643 } |
|
644 |
|
645 if (!mChannelCounters[i].IsEmpty()) { |
|
646 return true; |
|
647 } |
|
648 } |
|
649 |
|
650 return false; |
|
651 } |
|
652 |
|
653 NS_IMETHODIMP |
|
654 AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) |
|
655 { |
|
656 if (!strcmp(aTopic, "xpcom-shutdown")) { |
|
657 mDisabled = true; |
|
658 } |
|
659 |
|
660 if (!strcmp(aTopic, "ipc:content-shutdown")) { |
|
661 nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject); |
|
662 if (!props) { |
|
663 NS_WARNING("ipc:content-shutdown message without property bag as subject"); |
|
664 return NS_OK; |
|
665 } |
|
666 |
|
667 int32_t index; |
|
668 uint64_t childID = 0; |
|
669 nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), |
|
670 &childID); |
|
671 if (NS_SUCCEEDED(rv)) { |
|
672 for (int32_t type = AUDIO_CHANNEL_INT_NORMAL; |
|
673 type < AUDIO_CHANNEL_INT_LAST; |
|
674 ++type) { |
|
675 |
|
676 while ((index = mChannelCounters[type].IndexOf(childID)) != -1) { |
|
677 mChannelCounters[type].RemoveElementAt(index); |
|
678 } |
|
679 } |
|
680 |
|
681 // No hidden content channel is playable if the original playable hidden |
|
682 // process shuts down. |
|
683 if (mPlayableHiddenContentChildID == childID) { |
|
684 mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; |
|
685 } |
|
686 |
|
687 while ((index = mWithVideoChildIDs.IndexOf(childID)) != -1) { |
|
688 mWithVideoChildIDs.RemoveElementAt(index); |
|
689 } |
|
690 |
|
691 // We don't have to remove the agents from the mAgents hashtable because if |
|
692 // that table contains only agents running on the same process. |
|
693 |
|
694 SendAudioChannelChangedNotification(childID); |
|
695 Notify(); |
|
696 |
|
697 if (mDefChannelChildID == childID) { |
|
698 SetDefaultVolumeControlChannelInternal(-1, false, childID); |
|
699 mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN; |
|
700 } |
|
701 } else { |
|
702 NS_WARNING("ipc:content-shutdown message without childID property"); |
|
703 } |
|
704 } |
|
705 #ifdef MOZ_WIDGET_GONK |
|
706 // To process the volume control on each audio channel according to |
|
707 // change of settings |
|
708 else if (!strcmp(aTopic, "mozsettings-changed")) { |
|
709 AutoSafeJSContext cx; |
|
710 nsDependentString dataStr(aData); |
|
711 JS::Rooted<JS::Value> val(cx); |
|
712 if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) || |
|
713 !val.isObject()) { |
|
714 return NS_OK; |
|
715 } |
|
716 |
|
717 JS::Rooted<JSObject*> obj(cx, &val.toObject()); |
|
718 JS::Rooted<JS::Value> key(cx); |
|
719 if (!JS_GetProperty(cx, obj, "key", &key) || |
|
720 !key.isString()) { |
|
721 return NS_OK; |
|
722 } |
|
723 |
|
724 JS::Rooted<JSString*> jsKey(cx, JS::ToString(cx, key)); |
|
725 if (!jsKey) { |
|
726 return NS_OK; |
|
727 } |
|
728 nsDependentJSString keyStr; |
|
729 if (!keyStr.init(cx, jsKey) || keyStr.Find("audio.volume.", 0, false)) { |
|
730 return NS_OK; |
|
731 } |
|
732 |
|
733 JS::Rooted<JS::Value> value(cx); |
|
734 if (!JS_GetProperty(cx, obj, "value", &value) || !value.isInt32()) { |
|
735 return NS_OK; |
|
736 } |
|
737 |
|
738 nsCOMPtr<nsIAudioManager> audioManager = do_GetService(NS_AUDIOMANAGER_CONTRACTID); |
|
739 NS_ENSURE_TRUE(audioManager, NS_OK); |
|
740 |
|
741 int32_t index = value.toInt32(); |
|
742 if (keyStr.EqualsLiteral("audio.volume.content")) { |
|
743 audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Content, index); |
|
744 } else if (keyStr.EqualsLiteral("audio.volume.notification")) { |
|
745 audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Notification, index); |
|
746 } else if (keyStr.EqualsLiteral("audio.volume.alarm")) { |
|
747 audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Alarm, index); |
|
748 } else if (keyStr.EqualsLiteral("audio.volume.telephony")) { |
|
749 audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Telephony, index); |
|
750 } else if (!keyStr.EqualsLiteral("audio.volume.bt_sco")) { |
|
751 // bt_sco is not a valid audio channel so we manipulate it in |
|
752 // AudioManager.cpp. And the others should not be used. |
|
753 // We didn't use MOZ_ASSUME_UNREACHABLE here because any web content who |
|
754 // has permission of mozSettings can set any names then it can be easy to |
|
755 // crash the B2G. |
|
756 NS_WARNING("unexpected audio channel for volume control"); |
|
757 } |
|
758 } |
|
759 #endif |
|
760 |
|
761 return NS_OK; |
|
762 } |
|
763 |
|
764 AudioChannelService::AudioChannelInternalType |
|
765 AudioChannelService::GetInternalType(AudioChannel aChannel, |
|
766 bool aElementHidden) |
|
767 { |
|
768 switch (aChannel) { |
|
769 case AudioChannel::Normal: |
|
770 return aElementHidden |
|
771 ? AUDIO_CHANNEL_INT_NORMAL_HIDDEN |
|
772 : AUDIO_CHANNEL_INT_NORMAL; |
|
773 |
|
774 case AudioChannel::Content: |
|
775 return aElementHidden |
|
776 ? AUDIO_CHANNEL_INT_CONTENT_HIDDEN |
|
777 : AUDIO_CHANNEL_INT_CONTENT; |
|
778 |
|
779 case AudioChannel::Notification: |
|
780 return aElementHidden |
|
781 ? AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN |
|
782 : AUDIO_CHANNEL_INT_NOTIFICATION; |
|
783 |
|
784 case AudioChannel::Alarm: |
|
785 return aElementHidden |
|
786 ? AUDIO_CHANNEL_INT_ALARM_HIDDEN |
|
787 : AUDIO_CHANNEL_INT_ALARM; |
|
788 |
|
789 case AudioChannel::Telephony: |
|
790 return aElementHidden |
|
791 ? AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN |
|
792 : AUDIO_CHANNEL_INT_TELEPHONY; |
|
793 |
|
794 case AudioChannel::Ringer: |
|
795 return aElementHidden |
|
796 ? AUDIO_CHANNEL_INT_RINGER_HIDDEN |
|
797 : AUDIO_CHANNEL_INT_RINGER; |
|
798 |
|
799 case AudioChannel::Publicnotification: |
|
800 return aElementHidden |
|
801 ? AUDIO_CHANNEL_INT_PUBLICNOTIFICATION_HIDDEN |
|
802 : AUDIO_CHANNEL_INT_PUBLICNOTIFICATION; |
|
803 |
|
804 default: |
|
805 break; |
|
806 } |
|
807 |
|
808 MOZ_CRASH("unexpected audio channel"); |
|
809 } |
|
810 |
|
811 struct RefreshAgentsVolumeData |
|
812 { |
|
813 RefreshAgentsVolumeData(nsPIDOMWindow* aWindow) |
|
814 : mWindow(aWindow) |
|
815 {} |
|
816 |
|
817 nsPIDOMWindow* mWindow; |
|
818 nsTArray<nsRefPtr<AudioChannelAgent>> mAgents; |
|
819 }; |
|
820 |
|
821 PLDHashOperator |
|
822 AudioChannelService::RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent, |
|
823 AudioChannelAgentData* aUnused, |
|
824 void* aPtr) |
|
825 { |
|
826 MOZ_ASSERT(aAgent); |
|
827 RefreshAgentsVolumeData* data = static_cast<RefreshAgentsVolumeData*>(aPtr); |
|
828 MOZ_ASSERT(data); |
|
829 |
|
830 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aAgent->Window()); |
|
831 if (window && !window->IsInnerWindow()) { |
|
832 window = window->GetCurrentInnerWindow(); |
|
833 } |
|
834 |
|
835 if (window == data->mWindow) { |
|
836 data->mAgents.AppendElement(aAgent); |
|
837 } |
|
838 |
|
839 return PL_DHASH_NEXT; |
|
840 } |
|
841 void |
|
842 AudioChannelService::RefreshAgentsVolume(nsPIDOMWindow* aWindow) |
|
843 { |
|
844 RefreshAgentsVolumeData data(aWindow); |
|
845 mAgents.EnumerateRead(RefreshAgentsVolumeEnumerator, &data); |
|
846 |
|
847 for (uint32_t i = 0; i < data.mAgents.Length(); ++i) { |
|
848 data.mAgents[i]->WindowVolumeChanged(); |
|
849 } |
|
850 } |
|
851 |
|
852 struct CountWindowData |
|
853 { |
|
854 CountWindowData(nsIDOMWindow* aWindow) |
|
855 : mWindow(aWindow) |
|
856 , mCount(0) |
|
857 {} |
|
858 |
|
859 nsIDOMWindow* mWindow; |
|
860 uint32_t mCount; |
|
861 }; |
|
862 |
|
863 PLDHashOperator |
|
864 AudioChannelService::CountWindowEnumerator(AudioChannelAgent* aAgent, |
|
865 AudioChannelAgentData* aUnused, |
|
866 void* aPtr) |
|
867 { |
|
868 CountWindowData* data = static_cast<CountWindowData*>(aPtr); |
|
869 MOZ_ASSERT(aAgent); |
|
870 |
|
871 if (aAgent->Window() == data->mWindow) { |
|
872 ++data->mCount; |
|
873 } |
|
874 |
|
875 return PL_DHASH_NEXT; |
|
876 } |
|
877 |
|
878 uint32_t |
|
879 AudioChannelService::CountWindow(nsIDOMWindow* aWindow) |
|
880 { |
|
881 CountWindowData data(aWindow); |
|
882 mAgents.EnumerateRead(CountWindowEnumerator, &data); |
|
883 return data.mCount; |
|
884 } |
|
885 |
|
886 /* static */ const nsAttrValue::EnumTable* |
|
887 AudioChannelService::GetAudioChannelTable() |
|
888 { |
|
889 return kMozAudioChannelAttributeTable; |
|
890 } |
|
891 |
|
892 /* static */ AudioChannel |
|
893 AudioChannelService::GetAudioChannel(const nsAString& aChannel) |
|
894 { |
|
895 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { |
|
896 if (aChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) { |
|
897 return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value); |
|
898 } |
|
899 } |
|
900 |
|
901 return AudioChannel::Normal; |
|
902 } |
|
903 |
|
904 /* static */ AudioChannel |
|
905 AudioChannelService::GetDefaultAudioChannel() |
|
906 { |
|
907 nsString audioChannel = Preferences::GetString("media.defaultAudioChannel"); |
|
908 if (audioChannel.IsEmpty()) { |
|
909 return AudioChannel::Normal; |
|
910 } |
|
911 |
|
912 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { |
|
913 if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) { |
|
914 return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value); |
|
915 } |
|
916 } |
|
917 |
|
918 return AudioChannel::Normal; |
|
919 } |
|
920 |
|
921 /* static */ void |
|
922 AudioChannelService::GetAudioChannelString(AudioChannel aChannel, |
|
923 nsAString& aString) |
|
924 { |
|
925 aString.AssignASCII("normal"); |
|
926 |
|
927 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { |
|
928 if (aChannel == |
|
929 static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value)) { |
|
930 aString.AssignASCII(kMozAudioChannelAttributeTable[i].tag); |
|
931 break; |
|
932 } |
|
933 } |
|
934 } |
|
935 |
|
936 /* static */ void |
|
937 AudioChannelService::GetDefaultAudioChannelString(nsAString& aString) |
|
938 { |
|
939 aString.AssignASCII("normal"); |
|
940 |
|
941 nsString audioChannel = Preferences::GetString("media.defaultAudioChannel"); |
|
942 if (!audioChannel.IsEmpty()) { |
|
943 for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { |
|
944 if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) { |
|
945 aString = audioChannel; |
|
946 break; |
|
947 } |
|
948 } |
|
949 } |
|
950 } |