author | Alastor Wu <alwu@mozilla.com> |
Wed, 01 Jun 2016 10:21:13 +0800 | |
changeset 338850 | 15ba5d12b73b122aaad76aeeac2f7648d442a31f |
parent 338849 | 285bc19d2b16254420f595ed9e5de606a57fdc68 |
child 338851 | 476e1a1c0ef24aeae3d9382c3cb8700dade99bc6 |
push id | 6249 |
push user | jlund@mozilla.com |
push date | Mon, 01 Aug 2016 13:59:36 +0000 |
treeherder | mozilla-beta@bad9d4f5bf7e [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
bugs | 1257738 |
milestone | 49.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -1082,13 +1082,15 @@ pref("dom.presentation.device.name", "Fi // Enable notification of performance timing pref("dom.performance.enable_notify_performance_timing", true); // Multi-screen pref("b2g.multiscreen.chrome_remote_url", "chrome://b2g/content/shell_remote.html"); pref("b2g.multiscreen.system_remote_url", "index_remote.html"); +// Audio competing between tabs +pref("dom.audiochannel.audioCompeting", false); // Because we can't have nice things. #ifdef MOZ_GRAPHENE #include ../graphene/graphene.js #endif
--- a/dom/audiochannel/AudioChannelService.cpp +++ b/dom/audiochannel/AudioChannelService.cpp @@ -17,16 +17,17 @@ #include "mozilla/dom/TabParent.h" #include "nsContentUtils.h" #include "nsIScriptSecurityManager.h" #include "nsISupportsPrimitives.h" #include "nsThreadUtils.h" #include "nsHashPropertyBag.h" #include "nsComponentManagerUtils.h" +#include "nsGlobalWindow.h" #include "nsPIDOMWindow.h" #include "nsServiceManagerUtils.h" #include "mozilla/dom/SettingChangeNotificationBinding.h" #ifdef MOZ_WIDGET_GONK #include "nsJSUtils.h" #include "SpeakerManagerService.h" #endif @@ -36,16 +37,17 @@ using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::hal; namespace { // If true, any new AudioChannelAgent will be muted when created. bool sAudioChannelMutedByDefault = false; +bool sAudioChannelCompeting = false; bool sXPCOMShuttingDown = false; class NotifyChannelActiveRunnable final : public Runnable { public: NotifyChannelActiveRunnable(uint64_t aWindowID, AudioChannel aAudioChannel, bool aActive) : mWindowID(aWindowID) @@ -205,16 +207,23 @@ AudioChannelService::Shutdown() #ifdef MOZ_WIDGET_GONK gAudioChannelService->mSpeakerManager.Clear(); #endif gAudioChannelService = nullptr; } } +/* static */ bool +AudioChannelService::IsEnableAudioCompeting() +{ + CreateServiceIfNeeded(); + return sAudioChannelCompeting; +} + NS_INTERFACE_MAP_BEGIN(AudioChannelService) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAudioChannelService) NS_INTERFACE_MAP_ENTRY(nsIAudioChannelService) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_END NS_IMPL_ADDREF(AudioChannelService) NS_IMPL_RELEASE(AudioChannelService) @@ -236,16 +245,18 @@ AudioChannelService::AudioChannelService // To monitor the volume settings based on audio channel. obs->AddObserver(this, "mozsettings-changed", false); #endif } } Preferences::AddBoolVarCache(&sAudioChannelMutedByDefault, "dom.audiochannel.mutedByDefault"); + Preferences::AddBoolVarCache(&sAudioChannelCompeting, + "dom.audiochannel.audioCompeting"); } AudioChannelService::~AudioChannelService() { } void AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent, @@ -331,24 +342,25 @@ AudioChannelService::GetMediaConfig(nsPI // The volume must be calculated based on the window hierarchy. Here we go up // to the top window and we calculate the volume and the muted flag. do { winData = GetWindowData(window->WindowID()); if (winData) { config.mVolume *= winData->mChannels[aAudioChannel].mVolume; config.mMuted = config.mMuted || winData->mChannels[aAudioChannel].mMuted; + config.mSuspend = winData->mOwningAudioFocus ? + config.mSuspend : nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE; } config.mVolume *= window->GetAudioVolume(); config.mMuted = config.mMuted || window->GetAudioMuted(); - - // If the mSuspend is already suspended, we don't need to set it again. - config.mSuspend = (config.mSuspend == nsISuspendedTypes::NONE_SUSPENDED) ? - window->GetMediaSuspend() : config.mSuspend; + if (window->GetMediaSuspend() != nsISuspendedTypes::NONE_SUSPENDED) { + config.mSuspend = window->GetMediaSuspend(); + } nsCOMPtr<nsPIDOMWindowOuter> win = window->GetScriptableParentOrNull(); if (!win) { break; } window = do_QueryInterface(win); @@ -989,32 +1001,205 @@ AudioChannelService::ChildStatusReceived data = new AudioChannelChildStatus(aChildID); mPlayingChildren.AppendElement(data); } data->mActiveTelephonyChannel = aTelephonyChannel; data->mActiveContentOrNormalChannel = aContentOrNormalChannel; } +void +AudioChannelService::RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent, + bool aActive) +{ + MOZ_ASSERT(aAgent); + + nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator + iter(mWindows); + while (iter.HasMore()) { + AudioChannelWindow* winData = iter.GetNext(); + if (winData->mOwningAudioFocus) { + winData->AudioFocusChanged(aAgent, aActive); + } + } +} + +void +AudioChannelService::AudioChannelWindow::RequestAudioFocus(AudioChannelAgent* aAgent) +{ + MOZ_ASSERT(aAgent); + + // We already have the audio focus. No operation is needed. + if (mOwningAudioFocus) { + return; + } + + // Only foreground window can request audio focus, but it would still own the + // audio focus even it goes to background. Audio focus would be abandoned + // only when other foreground window starts audio competing. + mOwningAudioFocus = !(aAgent->Window()->IsBackground()); + + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("AudioChannelWindow, RequestAudioFocus, this = %p, " + "agent = %p, owning audio focus = %d\n", + this, aAgent, mOwningAudioFocus)); +} + +void +AudioChannelService::AudioChannelWindow::NotifyAudioCompetingChanged(AudioChannelAgent* aAgent, + bool aActive) +{ + // This function may be called after RemoveAgentAndReduceAgentsNum(), so the + // agent may be not contained in mAgent. In addition, the agent would still + // be alive because we have kungFuDeathGrip in UnregisterAudioChannelAgent(). + MOZ_ASSERT(aAgent); + + RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate(); + MOZ_ASSERT(service); + + if (!service->IsEnableAudioCompeting()) { + return; + } + + if (!IsAgentInvolvingInAudioCompeting(aAgent)) { + return; + } + + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("AudioChannelWindow, NotifyAudioCompetingChanged, this = %p, " + "agent = %p, active = %d\n", + this, aAgent, aActive)); + + service->RefreshAgentsAudioFocusChanged(aAgent, aActive); +} + +bool +AudioChannelService::AudioChannelWindow::IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const +{ + MOZ_ASSERT(aAgent); + + if(!mOwningAudioFocus) { + return false; + } + + if (IsAudioCompetingInSameTab()) { + return false; + } + + // TODO : add MediaSession::ambient kind, because it doens't interact with + // other kinds. + return true; +} + +bool +AudioChannelService::AudioChannelWindow::IsAudioCompetingInSameTab() const +{ + return (mOwningAudioFocus && mAudibleAgents.Length() > 1); +} + +void +AudioChannelService::AudioChannelWindow::AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent, + bool aActive) +{ + // This agent isn't always known for the current window, because it can comes + // from other window. + MOZ_ASSERT(aNewPlayingAgent); + + if (mAudibleAgents.IsEmpty()) { + // These would happen in two situations, + // (1) Audio in page A was ended, and another page B want to play audio. + // Page A should abandon its focus. + // (2) Audio was paused by remote-control, page should still own the focus. + mOwningAudioFocus = IsContainingPlayingAgent(aNewPlayingAgent); + } else { + nsTObserverArray<AudioChannelAgent*>::ForwardIterator iter(mAudibleAgents); + while (iter.HasMore()) { + AudioChannelAgent* agent = iter.GetNext(); + MOZ_ASSERT(agent); + + // Don't need to update the playing state of new playing agent. + if (agent == aNewPlayingAgent) { + continue; + } + + uint32_t type = GetCompetingBehavior(agent, + aNewPlayingAgent->AudioChannelType(), + aActive); + + // If window will be suspended, it needs to abandon the audio focus + // because only one window can own audio focus at a time. However, we + // would support multiple audio focus at the same time in the future. + mOwningAudioFocus = (type == nsISuspendedTypes::NONE_SUSPENDED); + + // TODO : support other behaviors which are definded in MediaSession API. + switch (type) { + case nsISuspendedTypes::NONE_SUSPENDED: + case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE: + agent->WindowSuspendChanged(type); + break; + } + } + } + + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("AudioChannelWindow, AudioFocusChanged, this = %p, " + "OwningAudioFocus = %d\n", this, mOwningAudioFocus)); +} + +bool +AudioChannelService::AudioChannelWindow::IsContainingPlayingAgent(AudioChannelAgent* aAgent) const +{ + return (aAgent->WindowID() == mWindowID); +} + +uint32_t +AudioChannelService::AudioChannelWindow::GetCompetingBehavior(AudioChannelAgent* aAgent, + int32_t aIncomingChannelType, + bool aIncomingChannelActive) const +{ + MOZ_ASSERT(aAgent); + MOZ_ASSERT(mAudibleAgents.Contains(aAgent)); + + uint32_t competingBehavior = nsISuspendedTypes::NONE_SUSPENDED; + int32_t presentChannelType = aAgent->AudioChannelType(); + + // TODO : add other competing cases for MediaSession API + if (presentChannelType == int32_t(AudioChannel::Normal) && + aIncomingChannelType == int32_t(AudioChannel::Normal) && + aIncomingChannelActive) { + competingBehavior = nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE; + } + + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("AudioChannelWindow, GetCompetingBehavior, this = %p, " + "present type = %d, incoming channel = %d, behavior = %d\n", + this, presentChannelType, aIncomingChannelType, competingBehavior)); + + return competingBehavior; +} + /* static */ bool AudioChannelService::IsAudioChannelMutedByDefault() { CreateServiceIfNeeded(); return sAudioChannelMutedByDefault; } void AudioChannelService::AudioChannelWindow::AppendAgent(AudioChannelAgent* aAgent, AudibleState aAudible) { MOZ_ASSERT(aAgent); + RequestAudioFocus(aAgent); AppendAgentAndIncreaseAgentsNum(aAgent); AudioCapturedChanged(aAgent, AudioCaptureState::eCapturing); - AudioAudibleChanged(aAgent, aAudible); + if (aAudible) { + AudioAudibleChanged(aAgent, AudibleState::eAudible); + } } void AudioChannelService::AudioChannelWindow::RemoveAgent(AudioChannelAgent* aAgent) { MOZ_ASSERT(aAgent); RemoveAgentAndReduceAgentsNum(aAgent); @@ -1077,16 +1262,18 @@ AudioChannelService::AudioChannelWindow: { MOZ_ASSERT(aAgent); if (aAudible) { AppendAudibleAgentIfNotContained(aAgent); } else { RemoveAudibleAgentIfContained(aAgent); } + + NotifyAudioCompetingChanged(aAgent, aAudible); } void AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent) { MOZ_ASSERT(aAgent); MOZ_ASSERT(mAgents.Contains(aAgent));
--- a/dom/audiochannel/AudioChannelService.h +++ b/dom/audiochannel/AudioChannelService.h @@ -84,16 +84,18 @@ public: * Only to be called from main thread. */ static already_AddRefed<AudioChannelService> GetOrCreate(); static bool IsAudioChannelMutedByDefault(); static PRLogModuleInfo* GetAudioChannelLog(); + static bool IsEnableAudioCompeting(); + /** * Any audio channel agent that starts playing should register itself to * this service, sharing the AudioChannel. */ void RegisterAudioChannelAgent(AudioChannelAgent* aAgent, AudibleState aAudible); /** @@ -215,52 +217,61 @@ private: void MaybeSendStatusUpdate(); bool ContentOrNormalChannelIsActive(); /* Send the default-volume-channel-changed notification */ void SetDefaultVolumeControlChannelInternal(int32_t aChannel, bool aVisible, uint64_t aChildID); + void RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent, + bool aActive); + class AudioChannelConfig final : public AudioPlaybackConfig { public: AudioChannelConfig() : AudioPlaybackConfig(1.0, IsAudioChannelMutedByDefault(), nsISuspendedTypes::NONE_SUSPENDED) , mNumberOfAgents(0) {} uint32_t mNumberOfAgents; }; class AudioChannelWindow final { public: explicit AudioChannelWindow(uint64_t aWindowID) - : mWindowID(aWindowID), - mIsAudioCaptured(false) + : mWindowID(aWindowID) + , mIsAudioCaptured(false) + , mOwningAudioFocus(!AudioChannelService::IsEnableAudioCompeting()) { // Workaround for bug1183033, system channel type can always playback. mChannels[(int16_t)AudioChannel::System].mMuted = false; } + void AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent, bool aActive); void AudioAudibleChanged(AudioChannelAgent* aAgent, AudibleState aAudible); void AppendAgent(AudioChannelAgent* aAgent, AudibleState aAudible); void RemoveAgent(AudioChannelAgent* aAgent); uint64_t mWindowID; bool mIsAudioCaptured; AudioChannelConfig mChannels[NUMBER_OF_AUDIO_CHANNELS]; // Raw pointer because the AudioChannelAgent must unregister itself. nsTObserverArray<AudioChannelAgent*> mAgents; nsTObserverArray<AudioChannelAgent*> mAudibleAgents; + // Owning audio focus when the window starts playing audible sound, and + // lose audio focus when other windows starts playing. + bool mOwningAudioFocus; + private: void AudioCapturedChanged(AudioChannelAgent* aAgent, AudioCaptureState aCapture); void AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent); void RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent); void AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent); @@ -268,16 +279,26 @@ private: bool IsFirstAudibleAgent() const; bool IsLastAudibleAgent() const; void NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow, AudibleState aAudible); void NotifyChannelActive(uint64_t aWindowID, AudioChannel aChannel, bool aActive); + + void RequestAudioFocus(AudioChannelAgent* aAgent); + void NotifyAudioCompetingChanged(AudioChannelAgent* aAgent, bool aActive); + + uint32_t GetCompetingBehavior(AudioChannelAgent* aAgent, + int32_t aIncomingChannelType, + bool aIncomingChannelActive) const; + bool IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const; + bool IsAudioCompetingInSameTab() const; + bool IsContainingPlayingAgent(AudioChannelAgent* aAgent) const; }; AudioChannelWindow* GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow); AudioChannelWindow* GetWindowData(uint64_t aWindowID) const;
--- a/dom/audiochannel/moz.build +++ b/dom/audiochannel/moz.build @@ -16,11 +16,15 @@ EXPORTS += [ 'AudioChannelService.h', ] UNIFIED_SOURCES += [ 'AudioChannelAgent.cpp', 'AudioChannelService.cpp', ] +LOCAL_INCLUDES += [ + '/dom/base/', +] + include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'xul'
--- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -918,11 +918,12 @@ pref("identity.fxaccounts.remote.oauth.u // Token server used by Firefox Account-authenticated Sync. pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sync/1.5"); // Enable Presentation API pref("dom.presentation.enabled", true); pref("dom.presentation.discovery.enabled", true); +pref("dom.audiochannel.audioCompeting", true); // TODO : remove it after landing bug1242874 because now it's the only way to // suspend the MediaElement. pref("media.useAudioChannelAPI", true);
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -5399,16 +5399,18 @@ pref("dom.input.fallbackUploadDir", ""); pref("plugins.rewrite_youtube_embeds", true); // Disable browser frames by default pref("dom.mozBrowserFramesEnabled", false); // Is support for 'color-adjust' CSS property enabled? pref("layout.css.color-adjust.enabled", true); +pref("dom.audiochannel.audioCompeting", false); + // Disable Node.rootNode in release builds. #ifdef RELEASE_BUILD pref("dom.node.rootNode.enabled", false); #else pref("dom.node.rootNode.enabled", true); #endif // Once bug 1276272 is resolved, we will trun this preference to default ON in