Bug 855208: Allow one newly created content channel to play in the background. r=mchen

This commit is contained in:
Bruce Sun 2013-09-17 15:46:06 +08:00
parent a0515d82c2
commit 1a1ac42c57
3 changed files with 74 additions and 62 deletions

View File

@ -74,7 +74,7 @@ NS_IMPL_ISUPPORTS2(AudioChannelService, nsIObserver, nsITimerCallback)
AudioChannelService::AudioChannelService()
: mCurrentHigherChannel(AUDIO_CHANNEL_LAST)
, mCurrentVisibleHigherChannel(AUDIO_CHANNEL_LAST)
, mActiveContentChildIDsFrozen(false)
, mPlayableHiddenContentChildID(CONTENT_PROCESS_ID_UNKNOWN)
, mDisabled(false)
, mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN)
{
@ -137,6 +137,18 @@ AudioChannelService::RegisterType(AudioChannelType aType, uint64_t aChildID, boo
mWithVideoChildIDs.AppendElement(aChildID);
}
// One hidden content channel can be playable only when there is no any
// content channel in the foreground.
if (type == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
mPlayableHiddenContentChildID = aChildID;
}
// No hidden content channel can be playable if there is an content channel
// in foreground.
else if (type == AUDIO_CHANNEL_INT_CONTENT) {
mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
}
// In order to avoid race conditions, it's safer to notify any existing
// agent any time a new one is registered.
SendAudioChannelChangedNotification(aChildID);
@ -202,13 +214,12 @@ AudioChannelService::UnregisterTypeInternal(AudioChannelType aType,
// In order to avoid race conditions, it's safer to notify any existing
// agent any time a new one is registered.
if (XRE_GetProcessType() == GeckoProcessType_Default) {
// We only remove ChildID when it is in the foreground.
// If in the background, we kept ChildID for allowing it to play next song.
// No hidden content channel is playable if the original playable hidden
// process does not need to play audio from background anymore.
if (aType == AUDIO_CHANNEL_CONTENT &&
mActiveContentChildIDs.Contains(aChildID) &&
!aElementHidden &&
!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID)) {
mActiveContentChildIDs.RemoveElement(aChildID);
mPlayableHiddenContentChildID == aChildID &&
!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID)) {
mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
}
if (aWithVideo) {
@ -236,6 +247,19 @@ AudioChannelService::UpdateChannelType(AudioChannelType aType,
MOZ_ASSERT(mChannelCounters[oldType].Contains(aChildID));
mChannelCounters[oldType].RemoveElement(aChildID);
}
// The last content channel which goes from foreground to background can also
// be playable.
if (oldType == AUDIO_CHANNEL_INT_CONTENT &&
newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
mPlayableHiddenContentChildID = aChildID;
}
// No hidden content channel can be playable if there is an content channel
// in foreground.
else if (newType == AUDIO_CHANNEL_INT_CONTENT) {
mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
}
}
AudioChannelState
@ -265,43 +289,6 @@ AudioChannelService::GetStateInternal(AudioChannelType aType, uint64_t aChildID,
AudioChannelInternalType newType = GetInternalType(aType, aElementHidden);
AudioChannelInternalType oldType = GetInternalType(aType, aElementWasHidden);
// If the audio content channel is visible, let's remember this ChildID.
if (newType == AUDIO_CHANNEL_INT_CONTENT &&
oldType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN) {
if (mActiveContentChildIDsFrozen) {
mActiveContentChildIDsFrozen = false;
mActiveContentChildIDs.Clear();
}
if (!mActiveContentChildIDs.Contains(aChildID)) {
mActiveContentChildIDs.AppendElement(aChildID);
}
}
else if (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
oldType == AUDIO_CHANNEL_INT_CONTENT &&
!mActiveContentChildIDsFrozen) {
// If nothing is visible, the list has to been frozen.
// Or if there is still any one with other ChildID in foreground then
// it should be removed from list and left other ChildIDs in the foreground
// to keep playing. Finally only last one childID which go to background
// will be in list.
if (mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
mActiveContentChildIDsFrozen = true;
} else if (!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID)) {
MOZ_ASSERT(mActiveContentChildIDs.Contains(aChildID));
mActiveContentChildIDs.RemoveElement(aChildID);
}
}
else if (newType == AUDIO_CHANNEL_INT_NORMAL &&
oldType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN &&
mWithVideoChildIDs.Contains(aChildID)) {
if (mActiveContentChildIDsFrozen) {
mActiveContentChildIDsFrozen = false;
mActiveContentChildIDs.Clear();
}
}
if (newType != oldType &&
(aType == AUDIO_CHANNEL_CONTENT ||
(aType == AUDIO_CHANNEL_NORMAL &&
@ -322,7 +309,16 @@ AudioChannelService::GetStateInternal(AudioChannelType aType, uint64_t aChildID,
// We are not visible, maybe we have to mute.
if (newType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN ||
(newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
!mActiveContentChildIDs.Contains(aChildID))) {
// One process can have multiple content channels; and during the
// transition from foreground to background, its content channels will be
// updated with correct visibility status one by one. All its content
// channels should remain playable until all of their visibility statuses
// have been updated as hidden. After all its content channels have been
// updated properly as hidden, mPlayableHiddenContentChildID is used to
// check whether this background process is playable or not.
!(mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) ||
(mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() &&
mPlayableHiddenContentChildID == aChildID)))) {
return AUDIO_CHANNEL_STATE_MUTED;
}
@ -493,12 +489,8 @@ AudioChannelService::SendAudioChannelChangedNotification(uint64_t aChildID)
higher = AUDIO_CHANNEL_NOTIFICATION;
}
// There is only one Child can play content channel in the background.
// And need to check whether there is any content channels under playing
// now.
else if (!mActiveContentChildIDs.IsEmpty() &&
mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(
mActiveContentChildIDs[0])) {
// Check whether there is any playable hidden content channel or not.
else if (mPlayableHiddenContentChildID != CONTENT_PROCESS_ID_UNKNOWN) {
higher = AUDIO_CHANNEL_CONTENT;
}
}
@ -641,9 +633,12 @@ AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, const PR
}
}
while ((index = mActiveContentChildIDs.IndexOf(childID)) != -1) {
mActiveContentChildIDs.RemoveElementAt(index);
// No hidden content channel is playable if the original playable hidden
// process shuts down.
if (mPlayableHiddenContentChildID == childID) {
mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
}
while ((index = mWithVideoChildIDs.IndexOf(childID)) != -1) {
mWithVideoChildIDs.RemoveElementAt(index);
}

View File

@ -169,9 +169,26 @@ protected:
AudioChannelType mCurrentHigherChannel;
AudioChannelType mCurrentVisibleHigherChannel;
nsTArray<uint64_t> mActiveContentChildIDs;
nsTArray<uint64_t> mWithVideoChildIDs;
bool mActiveContentChildIDsFrozen;
// mPlayableHiddenContentChildID stores the ChildID of the process which can
// play content channel(s) in the background.
// A background process contained content channel(s) will become playable:
// 1. When this background process registers its content channel(s) in
// AudioChannelService and there is no foreground process with registered
// content channel(s).
// 2. When this process goes from foreground into background and there is
// no foreground process with registered content channel(s).
// A background process contained content channel(s) will become non-playable:
// 1. When there is a foreground process registering its content channel(s)
// in AudioChannelService.
// ps. Currently this condition is never satisfied because the default value
// of visibility status of each channel during registering is hidden = true.
// 2. When there is a process with registered content channel(s) goes from
// background into foreground.
// 3. When this process unregisters all hidden content channels.
// 4. When this process shuts down.
uint64_t mPlayableHiddenContentChildID;
bool mDisabled;

View File

@ -272,7 +272,7 @@ TestContentChannels()
rv = agent2->StopPlaying();
NS_ENSURE_SUCCESS(rv, rv);
// Test that content channels can't be allow to play when they starts from
// Test that content channels can be allow to play when they starts from
// the background state
rv = agent1->SetVisibilityState(false);
NS_ENSURE_SUCCESS(rv, rv);
@ -281,14 +281,14 @@ TestContentChannels()
rv = agent1->StartPlaying(&playable);
NS_ENSURE_SUCCESS(rv, rv);
TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
"Test3: A content channel unvisible agent1 must be muted while playing "
TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
"Test3: A content channel unvisible agent1 must be playable "
"from background state");
rv = agent2->StartPlaying(&playable);
NS_ENSURE_SUCCESS(rv, rv);
TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
"Test3: A content channel unvisible agent2 must be muted while playing "
TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
"Test3: A content channel unvisible agent2 must be playable "
"from background state");
agent1->StopPlaying();
@ -418,8 +418,8 @@ TestPriorities()
rv = contentAgent->StartPlaying(&playable);
NS_ENSURE_SUCCESS(rv, rv);
TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED,
"Test5: A content channel unvisible agent agent must be muted while "
TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL,
"Test5: A content channel unvisible agent must be playable while "
"playing from background state");
rv = notificationAgent->StartPlaying(&playable);