Bug 1235612 - part1 : Implement notify media-playback.
MozReview-Commit-ID: HAd9FKWcHtl
--- a/dom/audiochannel/AudioChannelAgent.cpp
+++ b/dom/audiochannel/AudioChannelAgent.cpp
@@ -209,18 +209,17 @@ AudioChannelAgent::NotifyStartedPlaying(
}
RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
service == nullptr || mIsRegToService) {
return NS_ERROR_FAILURE;
}
- service->RegisterAudioChannelAgent(this,
- static_cast<AudioChannel>(mAudioChannelType));
+ service->RegisterAudioChannelAgent(this);
AudioPlaybackConfig config = service->GetMediaConfig(mWindow,
mAudioChannelType);
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
("AudioChannelAgent, NotifyStartedPlaying, this = %p, "
"mute = %d, volume = %f, suspend = %d\n", this,
config.mMuted, config.mVolume, config.mSuspend));
@@ -245,16 +244,33 @@ AudioChannelAgent::NotifyStoppedPlaying(
if (service) {
service->UnregisterAudioChannelAgent(this);
}
mIsRegToService = false;
return NS_OK;
}
+NS_IMETHODIMP
+AudioChannelAgent::NotifyStartedAudible(bool aAudible)
+{
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelAgent, NotifyStartedAudible, this = %p, "
+ "audible = %d\n", this, aAudible));
+
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (NS_WARN_IF(!service)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ service->AudioAudibleChanged(
+ this, static_cast<AudioChannelService::AudibleState>(aAudible));
+ return NS_OK;
+}
+
already_AddRefed<nsIAudioChannelAgentCallback>
AudioChannelAgent::GetCallback()
{
nsCOMPtr<nsIAudioChannelAgentCallback> callback = mCallback;
if (!callback) {
callback = do_QueryReferent(mWeakCallback);
}
return callback.forget();
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -89,51 +89,45 @@ public:
}
private:
const uint64_t mWindowID;
const AudioChannel mAudioChannel;
const bool mActive;
};
-void
-NotifyChannelActive(uint64_t aWindowID, AudioChannel aAudioChannel,
- bool aActive)
-{
- RefPtr<Runnable> runnable =
- new NotifyChannelActiveRunnable(aWindowID, aAudioChannel, aActive);
- NS_DispatchToCurrentThread(runnable);
-}
-
bool
IsParentProcess()
{
return XRE_GetProcessType() == GeckoProcessType_Default;
}
-class MediaPlaybackRunnable : public Runnable
+class AudioPlaybackRunnable final : public Runnable
{
public:
- MediaPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive)
+ AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive)
: mWindow(aWindow)
, mActive(aActive)
{}
NS_IMETHOD Run()
{
nsCOMPtr<nsIObserverService> observerService =
services::GetObserverService();
- if (observerService) {
- observerService->NotifyObservers(
- ToSupports(mWindow),
- "audio-playback",
- mActive ? MOZ_UTF16("active")
- : MOZ_UTF16("inactive"));
+ if (NS_WARN_IF(!observerService)) {
+ return NS_ERROR_FAILURE;
}
+ observerService->NotifyObservers(ToSupports(mWindow),
+ "audio-playback",
+ mActive ? MOZ_UTF16("active")
+ : MOZ_UTF16("inactive"));
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioPlaybackRunnable, active = %d\n", mActive));
return NS_OK;
}
private:
nsCOMPtr<nsPIDOMWindowOuter> mWindow;
bool mActive;
};
@@ -249,97 +243,59 @@ AudioChannelService::AudioChannelService
"dom.audiochannel.mutedByDefault");
}
AudioChannelService::~AudioChannelService()
{
}
void
-AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
- AudioChannel aChannel)
+AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent)
{
+ MOZ_ASSERT(aAgent);
+
uint64_t windowID = aAgent->WindowID();
AudioChannelWindow* winData = GetWindowData(windowID);
if (!winData) {
winData = new AudioChannelWindow(windowID);
mWindows.AppendElement(winData);
}
- MOZ_ASSERT(!winData->mAgents.Contains(aAgent));
- winData->mAgents.AppendElement(aAgent);
-
- ++winData->mChannels[(uint32_t)aChannel].mNumberOfAgents;
-
- // The first one, we must inform the BrowserElementAudioChannel.
- if (winData->mChannels[(uint32_t)aChannel].mNumberOfAgents == 1) {
- NotifyChannelActive(aAgent->WindowID(), aChannel, true);
- }
-
- // If this is the first agent for this window, we must notify the observers.
- if (winData->mAgents.Length() == 1) {
- RefPtr<MediaPlaybackRunnable> runnable =
- new MediaPlaybackRunnable(aAgent->Window(), true /* active */);
- NS_DispatchToCurrentThread(runnable);
- }
-
- // If the window has already been captured, the agent of that window should
- // also be captured.
- if (winData->mIsAudioCaptured) {
- aAgent->WindowAudioCaptureChanged(aAgent->InnerWindowID(),
- winData->mIsAudioCaptured);
- }
+ // To make sure agent would be alive because AppendAgent() would trigger the
+ // callback function of AudioChannelAgentOwner that means the agent might be
+ // released in their callback.
+ RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
+ winData->AppendAgent(aAgent);
MaybeSendStatusUpdate();
}
void
AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
{
+ MOZ_ASSERT(aAgent);
+
AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
if (!winData) {
return;
}
- if (winData->mAgents.Contains(aAgent)) {
- int32_t channel = aAgent->AudioChannelType();
- uint64_t windowID = aAgent->WindowID();
-
- // aAgent can be freed after this call.
- winData->mAgents.RemoveElement(aAgent);
-
- MOZ_ASSERT(winData->mChannels[channel].mNumberOfAgents > 0);
-
- --winData->mChannels[channel].mNumberOfAgents;
-
- // The last one, we must inform the BrowserElementAudioChannel.
- if (winData->mChannels[channel].mNumberOfAgents == 0) {
- NotifyChannelActive(windowID, static_cast<AudioChannel>(channel), false);
- }
- }
+ // To make sure agent would be alive because AppendAgent() would trigger the
+ // callback function of AudioChannelAgentOwner that means the agent might be
+ // released in their callback.
+ RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
+ winData->RemoveAgent(aAgent);
#ifdef MOZ_WIDGET_GONK
bool active = AnyAudioChannelIsActive();
for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
mSpeakerManager[i]->SetAudioChannelActive(active);
}
#endif
- // If this is the last agent for this window, we must notify the observers.
- if (winData->mAgents.IsEmpty()) {
- RefPtr<MediaPlaybackRunnable> runnable =
- new MediaPlaybackRunnable(aAgent->Window(), false /* active */);
- NS_DispatchToCurrentThread(runnable);
- }
-
- // No need to capture non-audible object.
- if (winData->mIsAudioCaptured) {
- aAgent->WindowAudioCaptureChanged(aAgent->InnerWindowID(), false);
- }
-
MaybeSendStatusUpdate();
}
void
AudioChannelService::RegisterTabParent(TabParent* aTabParent)
{
MOZ_ASSERT(aTabParent);
MOZ_ASSERT(!mTabParents.Contains(aTabParent));
@@ -396,16 +352,29 @@ AudioChannelService::GetMediaConfig(nsPI
window = do_QueryInterface(win);
// If there is no parent, or we are the toplevel we don't continue.
} while (window && window != aWindow);
return config;
}
+void
+AudioChannelService::AudioAudibleChanged(AudioChannelAgent* aAgent,
+ AudibleState aAudible)
+{
+ MOZ_ASSERT(aAgent);
+
+ uint64_t windowID = aAgent->WindowID();
+ AudioChannelWindow* winData = GetWindowData(windowID);
+ if (winData) {
+ winData->AudioAudibleChanged(aAgent, aAudible);
+ }
+}
+
bool
AudioChannelService::TelephonyChannelIsActive()
{
nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator windowsIter(mWindows);
while (windowsIter.HasMore()) {
AudioChannelWindow* next = windowsIter.GetNext();
if (next->mChannels[(uint32_t)AudioChannel::Telephony].mNumberOfAgents != 0 &&
!next->mChannels[(uint32_t)AudioChannel::Telephony].mMuted) {
@@ -1025,8 +994,150 @@ AudioChannelService::ChildStatusReceived
}
/* static */ bool
AudioChannelService::IsAudioChannelMutedByDefault()
{
CreateServiceIfNeeded();
return sAudioChannelMutedByDefault;
}
+
+void
+AudioChannelService::AudioChannelWindow::AppendAgent(AudioChannelAgent* aAgent)
+{
+ MOZ_ASSERT(aAgent);
+
+ AppendAgentAndIncreaseAgentsNum(aAgent);
+ AudioCapturedChanged(aAgent, AudioCaptureState::eCapturing);
+ // Audio-playback would be notified when the agent owner starts audible.
+}
+
+void
+AudioChannelService::AudioChannelWindow::RemoveAgent(AudioChannelAgent* aAgent)
+{
+ MOZ_ASSERT(aAgent);
+
+ RemoveAgentAndReduceAgentsNum(aAgent);
+ AudioCapturedChanged(aAgent, AudioCaptureState::eNotCapturing);
+ AudioAudibleChanged(aAgent, AudibleState::eNotAudible);
+}
+
+void
+AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent)
+{
+ MOZ_ASSERT(aAgent);
+ MOZ_ASSERT(!mAgents.Contains(aAgent));
+
+ int32_t channel = aAgent->AudioChannelType();
+ mAgents.AppendElement(aAgent);
+
+ ++mChannels[channel].mNumberOfAgents;
+
+ // The first one, we must inform the BrowserElementAudioChannel.
+ if (mChannels[channel].mNumberOfAgents == 1) {
+ NotifyChannelActive(aAgent->WindowID(),
+ static_cast<AudioChannel>(channel),
+ true);
+ }
+}
+
+void
+AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(AudioChannelAgent* aAgent)
+{
+ MOZ_ASSERT(aAgent);
+ MOZ_ASSERT(mAgents.Contains(aAgent));
+
+ int32_t channel = aAgent->AudioChannelType();
+ mAgents.RemoveElement(aAgent);
+
+ MOZ_ASSERT(mChannels[channel].mNumberOfAgents > 0);
+ --mChannels[channel].mNumberOfAgents;
+
+ if (mChannels[channel].mNumberOfAgents == 0) {
+ NotifyChannelActive(aAgent->WindowID(),
+ static_cast<AudioChannel>(channel),
+ false);
+ }
+}
+
+void
+AudioChannelService::AudioChannelWindow::AudioCapturedChanged(AudioChannelAgent* aAgent,
+ AudioCaptureState aCapture)
+{
+ MOZ_ASSERT(aAgent);
+
+ if (mIsAudioCaptured) {
+ aAgent->WindowAudioCaptureChanged(aAgent->InnerWindowID(), aCapture);
+ }
+}
+
+void
+AudioChannelService::AudioChannelWindow::AudioAudibleChanged(AudioChannelAgent* aAgent,
+ AudibleState aAudible)
+{
+ MOZ_ASSERT(aAgent);
+
+ if (aAudible) {
+ AppendAudibleAgentIfNotContained(aAgent);
+ } else {
+ RemoveAudibleAgentIfContained(aAgent);
+ }
+}
+
+void
+AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent)
+{
+ MOZ_ASSERT(aAgent);
+ MOZ_ASSERT(mAgents.Contains(aAgent));
+
+ if (!mAudibleAgents.Contains(aAgent)) {
+ mAudibleAgents.AppendElement(aAgent);
+ if (IsFirstAudibleAgent()) {
+ NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible);
+ }
+ }
+}
+
+void
+AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent)
+{
+ MOZ_ASSERT(aAgent);
+
+ if (mAudibleAgents.Contains(aAgent)) {
+ mAudibleAgents.RemoveElement(aAgent);
+ if (IsLastAudibleAgent()) {
+ NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible);
+ }
+ }
+}
+
+bool
+AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const
+{
+ return (mAudibleAgents.Length() == 1);
+}
+
+bool
+AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const
+{
+ return mAudibleAgents.IsEmpty();
+}
+
+void
+AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow,
+ AudibleState aAudible)
+{
+ RefPtr<AudioPlaybackRunnable> runnable =
+ new AudioPlaybackRunnable(aWindow, aAudible);
+ nsresult rv = NS_DispatchToCurrentThread(runnable);
+ NS_WARN_IF(NS_FAILED(rv));
+}
+
+void
+AudioChannelService::AudioChannelWindow::NotifyChannelActive(uint64_t aWindowID,
+ AudioChannel aChannel,
+ bool aActive)
+{
+ RefPtr<NotifyChannelActiveRunnable> runnable =
+ new NotifyChannelActiveRunnable(aWindowID, aChannel, aActive);
+ nsresult rv = NS_DispatchToCurrentThread(runnable);
+ NS_WARN_IF(NS_FAILED(rv));
+}
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -63,33 +63,42 @@ public:
class AudioChannelService final : public nsIAudioChannelService
, public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
NS_DECL_NSIAUDIOCHANNELSERVICE
+ enum AudibleState : bool {
+ eAudible = true,
+ eNotAudible = false
+ };
+
+ enum AudioCaptureState : bool {
+ eCapturing = true,
+ eNotCapturing = false
+ };
+
/**
* Returns the AudioChannelServce singleton.
* If AudioChannelServce is not exist, create and return new one.
* Only to be called from main thread.
*/
static already_AddRefed<AudioChannelService> GetOrCreate();
static bool IsAudioChannelMutedByDefault();
static PRLogModuleInfo* GetAudioChannelLog();
/**
* Any audio channel agent that starts playing should register itself to
* this service, sharing the AudioChannel.
*/
- void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
- AudioChannel aChannel);
+ void RegisterAudioChannelAgent(AudioChannelAgent* aAgent);
/**
* Any audio channel agent that stops playing should unregister itself to
* this service.
*/
void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
/**
@@ -100,16 +109,23 @@ public:
/**
* Return the state to indicate this audioChannel for his window should keep
* playing/muted/suspended.
*/
AudioPlaybackConfig GetMediaConfig(nsPIDOMWindowOuter* aWindow,
uint32_t aAudioChannel) const;
+ /**
+ * Called this method when the audible state of the audio playback changed,
+ * it would dispatch the playback event to observers which want to know the
+ * actual audible state of the window.
+ */
+ void AudioAudibleChanged(AudioChannelAgent* aAgent, AudibleState aAudible);
+
/* Methods for the BrowserElementAudioChannel */
float GetAudioChannelVolume(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel);
void SetAudioChannelVolume(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel,
float aVolume);
bool GetAudioChannelMuted(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel);
@@ -210,32 +226,57 @@ private:
: AudioPlaybackConfig(1.0, IsAudioChannelMutedByDefault(),
nsISuspendedTypes::NONE_SUSPENDED)
, mNumberOfAgents(0)
{}
uint32_t mNumberOfAgents;
};
- struct AudioChannelWindow final
+ class AudioChannelWindow final
{
+ public:
explicit AudioChannelWindow(uint64_t aWindowID)
: mWindowID(aWindowID),
mIsAudioCaptured(false)
{
// Workaround for bug1183033, system channel type can always playback.
mChannels[(int16_t)AudioChannel::System].mMuted = false;
}
+ void AudioAudibleChanged(AudioChannelAgent* aAgent, AudibleState aAudible);
+
+ void AppendAgent(AudioChannelAgent* aAgent);
+ 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;
+
+ private:
+ void AudioCapturedChanged(AudioChannelAgent* aAgent,
+ AudioCaptureState aCapture);
+
+ void AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent);
+ void RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent);
+
+ void AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent);
+ void RemoveAgentAndReduceAgentsNum(AudioChannelAgent* aAgent);
+
+ bool IsFirstAudibleAgent() const;
+ bool IsLastAudibleAgent() const;
+
+ void NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow,
+ AudibleState aAudible);
+ void NotifyChannelActive(uint64_t aWindowID, AudioChannel aChannel,
+ bool aActive);
};
AudioChannelWindow*
GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow);
AudioChannelWindow*
GetWindowData(uint64_t aWindowID) const;
--- a/dom/audiochannel/nsIAudioChannelAgent.idl
+++ b/dom/audiochannel/nsIAudioChannelAgent.idl
@@ -84,20 +84,18 @@ interface nsIAudioChannelAgentCallback :
void windowAudioCaptureChanged(in bool aCapture);
};
/**
* This interface provides an agent for gecko components to participate
* in the audio channel service. Gecko components are responsible for
* 1. Indicating what channel type they are using (via the init() member
* function).
- * 2. Before playing, checking the playable status of the channel.
- * 3. Notifying the agent when they start/stop using this channel.
- * 4. Notifying the agent of changes to the visibility of the component using
- * this channel.
+ * 2. Notifying the agent when they start/stop using this channel.
+ * 3. Notifying the agent when they are audible.
*
* The agent will invoke a callback to notify Gecko components of
* 1. Changes to the playable status of this channel.
*/
[uuid(ab7e21c0-970c-11e5-a837-0800200c9a66)]
interface nsIAudioChannelAgent : nsISupports
{
@@ -171,9 +169,19 @@ interface nsIAudioChannelAgent : nsISupp
* Notify the agent we no longer want to play.
*
* Note : even if notifyStartedPlaying() returned false, the agent would
* still be registered with the audio channel service and receive callbacks
* for status changes. So notifyStoppedPlaying must still eventually be
* called to unregister the agent with the channel service.
*/
void notifyStoppedPlaying();
+
+
+ /**
+ * Notify agent that we already start producing audible data.
+ *
+ * Note : sometime audio might become silent during playing, this method is used to
+ * notify the actually audible state to other services which want to know
+ * about that, ex. tab sound indicator.
+ */
+ void notifyStartedAudible(in bool audible);
};