From 1a1ac42c570fd1e20777a1d8014b368ef96d46d4 Mon Sep 17 00:00:00 2001 From: Bruce Sun Date: Tue, 17 Sep 2013 15:46:06 +0800 Subject: [PATCH] Bug 855208: Allow one newly created content channel to play in the background. r=mchen --- dom/audiochannel/AudioChannelService.cpp | 101 +++++++++--------- dom/audiochannel/AudioChannelService.h | 21 +++- .../tests/TestAudioChannelService.cpp | 14 +-- 3 files changed, 74 insertions(+), 62 deletions(-) diff --git a/dom/audiochannel/AudioChannelService.cpp b/dom/audiochannel/AudioChannelService.cpp index 30e5299c7562..adb0b2545387 100644 --- a/dom/audiochannel/AudioChannelService.cpp +++ b/dom/audiochannel/AudioChannelService.cpp @@ -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); } diff --git a/dom/audiochannel/AudioChannelService.h b/dom/audiochannel/AudioChannelService.h index c62c80da3f51..f68aaed403fd 100644 --- a/dom/audiochannel/AudioChannelService.h +++ b/dom/audiochannel/AudioChannelService.h @@ -169,9 +169,26 @@ protected: AudioChannelType mCurrentHigherChannel; AudioChannelType mCurrentVisibleHigherChannel; - nsTArray mActiveContentChildIDs; nsTArray 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; diff --git a/dom/audiochannel/tests/TestAudioChannelService.cpp b/dom/audiochannel/tests/TestAudioChannelService.cpp index 367dbf6ebc7d..f960e8d191df 100644 --- a/dom/audiochannel/tests/TestAudioChannelService.cpp +++ b/dom/audiochannel/tests/TestAudioChannelService.cpp @@ -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);