/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AudioChannelAgent.h" #include "AudioChannelService.h" #include "mozilla/Preferences.h" #include "nsContentUtils.h" #include "nsIDocument.h" #include "nsIDOMWindow.h" #include "nsPIDOMWindow.h" #include "nsIURI.h" using namespace mozilla::dom; NS_IMPL_CYCLE_COLLECTION_CLASS(AudioChannelAgent) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioChannelAgent) tmp->Shutdown(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioChannelAgent) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioChannelAgent) NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgent) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioChannelAgent) NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioChannelAgent) AudioChannelAgent::AudioChannelAgent() : mInnerWindowID(0) , mIsRegToService(false) { // Init service in the begining, it can help us to know whether there is any // created media component via AudioChannelService::IsServiceStarted(). RefPtr service = AudioChannelService::GetOrCreate(); } AudioChannelAgent::~AudioChannelAgent() { Shutdown(); } void AudioChannelAgent::Shutdown() { if (mIsRegToService) { NotifyStoppedPlaying(); } } NS_IMETHODIMP AudioChannelAgent::Init(mozIDOMWindow* aWindow, nsIAudioChannelAgentCallback *aCallback) { return InitInternal(nsPIDOMWindowInner::From(aWindow), aCallback, /* useWeakRef = */ false); } NS_IMETHODIMP AudioChannelAgent::InitWithWeakCallback(mozIDOMWindow* aWindow, nsIAudioChannelAgentCallback *aCallback) { return InitInternal(nsPIDOMWindowInner::From(aWindow), aCallback, /* useWeakRef = */ true); } nsresult AudioChannelAgent::FindCorrectWindow(nsPIDOMWindowInner* aWindow) { MOZ_ASSERT(aWindow->IsInnerWindow()); mWindow = aWindow->GetScriptableTop(); if (NS_WARN_IF(!mWindow)) { return NS_OK; } // From here we do an hack for nested iframes. // The system app doesn't have access to the nested iframe objects so it // cannot control the volume of the agents running in nested apps. What we do // here is to assign those Agents to the top scriptable window of the parent // iframe (what is controlled by the system app). // For doing this we go recursively back into the chain of windows until we // find apps that are not the system one. nsCOMPtr outerParent = mWindow->GetParent(); if (!outerParent || outerParent == mWindow) { return NS_OK; } nsCOMPtr parent = outerParent->GetCurrentInnerWindow(); if (!parent) { return NS_OK; } nsCOMPtr doc = parent->GetExtantDoc(); if (!doc) { return NS_OK; } if (nsContentUtils::IsChromeDoc(doc)) { return NS_OK; } nsAutoCString systemAppUrl; nsresult rv = mozilla::Preferences::GetCString("b2g.system_startup_url", systemAppUrl); if (NS_FAILED(rv)) { return NS_OK; } nsCOMPtr principal = doc->NodePrincipal(); nsCOMPtr uri; principal->GetURI(getter_AddRefs(uri)); if (uri) { nsAutoCString spec; uri->GetSpec(spec); if (spec.Equals(systemAppUrl)) { return NS_OK; } } return FindCorrectWindow(parent); } nsresult AudioChannelAgent::InitInternal(nsPIDOMWindowInner* aWindow, nsIAudioChannelAgentCallback *aCallback, bool aUseWeakRef) { if (NS_WARN_IF(!aWindow)) { return NS_ERROR_FAILURE; } MOZ_ASSERT(aWindow->IsInnerWindow()); mInnerWindowID = aWindow->WindowID(); nsresult rv = FindCorrectWindow(aWindow); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (aUseWeakRef) { mWeakCallback = do_GetWeakReference(aCallback); } else { mCallback = aCallback; } MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("AudioChannelAgent, InitInternal, this = %p, " "owner = %p, hasCallback = %d\n", this, mWindow.get(), (!!mCallback || !!mWeakCallback))); return NS_OK; } NS_IMETHODIMP AudioChannelAgent::NotifyStartedPlaying(AudioPlaybackConfig* aConfig, uint8_t aAudible) { if (NS_WARN_IF(!aConfig)) { return NS_ERROR_FAILURE; } RefPtr service = AudioChannelService::GetOrCreate(); if (service == nullptr || mIsRegToService) { return NS_ERROR_FAILURE; } MOZ_ASSERT(AudioChannelService::AudibleState::eNotAudible == 0 && AudioChannelService::AudibleState::eMaybeAudible == 1 && AudioChannelService::AudibleState::eAudible == 2); service->RegisterAudioChannelAgent(this, static_cast(aAudible)); AudioPlaybackConfig config = service->GetMediaConfig(mWindow); MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("AudioChannelAgent, NotifyStartedPlaying, this = %p, " "audible = %s, mute = %s, volume = %f, suspend = %s\n", this, AudibleStateToStr(static_cast(aAudible)), config.mMuted ? "true" : "false", config.mVolume, SuspendTypeToStr(config.mSuspend))); aConfig->SetConfig(config.mVolume, config.mMuted, config.mSuspend); mIsRegToService = true; return NS_OK; } NS_IMETHODIMP AudioChannelAgent::NotifyStoppedPlaying() { if (!mIsRegToService) { return NS_ERROR_FAILURE; } MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("AudioChannelAgent, NotifyStoppedPlaying, this = %p\n", this)); RefPtr service = AudioChannelService::GetOrCreate(); if (service) { service->UnregisterAudioChannelAgent(this); } mIsRegToService = false; return NS_OK; } NS_IMETHODIMP AudioChannelAgent::NotifyStartedAudible(uint8_t aAudible, uint32_t aReason) { MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("AudioChannelAgent, NotifyStartedAudible, this = %p, " "audible = %s, reason = %s\n", this, AudibleStateToStr(static_cast(aAudible)), AudibleChangedReasonToStr(static_cast(aReason)))); RefPtr service = AudioChannelService::GetOrCreate(); if (NS_WARN_IF(!service)) { return NS_ERROR_FAILURE; } service->AudioAudibleChanged( this, static_cast(aAudible), static_cast(aReason)); return NS_OK; } already_AddRefed AudioChannelAgent::GetCallback() { nsCOMPtr callback = mCallback; if (!callback) { callback = do_QueryReferent(mWeakCallback); } return callback.forget(); } void AudioChannelAgent::WindowVolumeChanged() { nsCOMPtr callback = GetCallback(); if (!callback) { return; } AudioPlaybackConfig config = GetMediaConfig(); MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("AudioChannelAgent, WindowVolumeChanged, this = %p, mute = %s, " "volume = %f\n", this, config.mMuted ? "true" : "false", config.mVolume)); callback->WindowVolumeChanged(config.mVolume, config.mMuted); } void AudioChannelAgent::WindowSuspendChanged(nsSuspendedTypes aSuspend) { nsCOMPtr callback = GetCallback(); if (!callback) { return; } if (!IsDisposableSuspend(aSuspend)) { aSuspend = GetMediaConfig().mSuspend; } MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("AudioChannelAgent, WindowSuspendChanged, this = %p, " "suspended = %s\n", this, SuspendTypeToStr(aSuspend))); callback->WindowSuspendChanged(aSuspend); } AudioPlaybackConfig AudioChannelAgent::GetMediaConfig() { RefPtr service = AudioChannelService::GetOrCreate(); AudioPlaybackConfig config(1.0, false, nsISuspendedTypes::NONE_SUSPENDED); if (service) { config = service->GetMediaConfig(mWindow); } return config; } bool AudioChannelAgent::IsDisposableSuspend(nsSuspendedTypes aSuspend) const { return (aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE || aSuspend == nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE); } uint64_t AudioChannelAgent::WindowID() const { return mWindow ? mWindow->WindowID() : 0; } uint64_t AudioChannelAgent::InnerWindowID() const { return mInnerWindowID; } void AudioChannelAgent::WindowAudioCaptureChanged(uint64_t aInnerWindowID, bool aCapture) { if (aInnerWindowID != mInnerWindowID) { return; } nsCOMPtr callback = GetCallback(); if (!callback) { return; } MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("AudioChannelAgent, WindowAudioCaptureChanged, this = %p, " "capture = %d\n", this, aCapture)); callback->WindowAudioCaptureChanged(aCapture); } bool AudioChannelAgent::IsPlayingStarted() const { return mIsRegToService; } bool AudioChannelAgent::ShouldBlockMedia() const { return mWindow ? mWindow->GetMediaSuspend() == nsISuspendedTypes::SUSPENDED_BLOCK : false; }