Bug 1633010 - part2 : create `MediaPlaybackStatus` to handle the tasks of determining playback related status. r=chunmin

This patch will do :
- implement a new class `MediaPlaybackStatus` to handle the detail of modifying different media status counter

The advantage of doing so :
- encapsulate the low level details to the delegate and simplify the code in `MediaController`
- help us maintain separated media status for each different context within a tab
- using `MediaPlaybackStatus` can fix the problem of not `IsAudible()` being able to represent the actual audible state of media controller

Differential Revision: https://phabricator.services.mozilla.com/D73486
This commit is contained in:
alwu 2020-05-13 17:05:31 +00:00
Родитель 32695cee12
Коммит 36c6a1d4e9
7 изменённых файлов: 263 добавлений и 71 удалений

Просмотреть файл

@ -114,6 +114,18 @@ inline const char* ToMediaPlaybackStateStr(MediaPlaybackState aState) {
}
}
inline const char* ToMediaAudibleStateStr(MediaAudibleState aState) {
switch (aState) {
case MediaAudibleState::eInaudible:
return "inaudible";
case MediaAudibleState::eAudible:
return "audible";
default:
MOZ_ASSERT_UNREACHABLE("Invalid audible state.");
return "Unknown";
}
}
BrowsingContext* GetAliveTopBrowsingContext(BrowsingContext* aBC);
} // namespace dom

Просмотреть файл

@ -88,7 +88,7 @@ void MediaController::UpdateMediaControlKeysEventToContentMediaIfNeeded(
MediaControlKeysEvent aEvent) {
// There is no controlled media existing or controller has been shutdown, we
// have no need to update media action to the content process.
if (!ControlledMediaNum() || mShutdown) {
if (!IsAnyMediaBeingControlled() || mShutdown) {
return;
}
// If we have an active media session, then we should directly notify the
@ -114,8 +114,6 @@ void MediaController::Shutdown() {
// the corresponding controller. Therefore, we should manually remove the
// controller from the service.
Deactivate();
mControlledMediaNum = 0;
mPlayingControlledMediaNum = 0;
mShutdown = true;
}
@ -124,14 +122,17 @@ void MediaController::NotifyMediaPlaybackChanged(uint64_t aBrowsingContextId,
if (mShutdown) {
return;
}
if (aState == MediaPlaybackState::eStarted) {
IncreaseControlledMediaNum();
} else if (aState == MediaPlaybackState::eStopped) {
DecreaseControlledMediaNum();
} else if (aState == MediaPlaybackState::ePlayed) {
IncreasePlayingControlledMediaNum();
} else if (aState == MediaPlaybackState::ePaused) {
DecreasePlayingControlledMediaNum();
mMediaStatus.UpdateMediaPlaybackState(aBrowsingContextId, aState);
// Update controller's status according to the media status.
if (ShouldActivateController()) {
Activate();
} else if (ShouldDeactivateController()) {
Deactivate();
} else if (mMediaStatus.IsPlaying()) {
SetGuessedPlayState(MediaSessionPlaybackState::Playing);
} else if (!mMediaStatus.IsPlaying()) {
SetGuessedPlayState(MediaSessionPlaybackState::Paused);
}
}
@ -140,63 +141,36 @@ void MediaController::NotifyMediaAudibleChanged(uint64_t aBrowsingContextId,
if (mShutdown) {
return;
}
mAudibleState = aState;
bool oldAudible = IsAudible();
mMediaStatus.UpdateMediaAudibleState(aBrowsingContextId, aState);
if (IsAudible() == oldAudible) {
return;
}
// Request the audio focus amongs different controllers that could cause
// pausing other audible controllers if we enable the audio focus management.
RefPtr<MediaControlService> service = MediaControlService::GetService();
MOZ_ASSERT(service);
if (mAudibleState == MediaAudibleState::eAudible) {
if (IsAudible()) {
service->GetAudioFocusManager().RequestAudioFocus(this);
} else {
service->GetAudioFocusManager().RevokeAudioFocus(this);
}
}
void MediaController::IncreaseControlledMediaNum() {
bool MediaController::ShouldActivateController() const {
MOZ_ASSERT(!mShutdown);
MOZ_DIAGNOSTIC_ASSERT(mControlledMediaNum >= 0);
mControlledMediaNum++;
LOG("Increase controlled media num to %" PRId64, mControlledMediaNum);
if (mControlledMediaNum == 1) {
Activate();
}
return IsAnyMediaBeingControlled() && !mIsRegisteredToService;
}
void MediaController::DecreaseControlledMediaNum() {
bool MediaController::ShouldDeactivateController() const {
MOZ_ASSERT(!mShutdown);
MOZ_DIAGNOSTIC_ASSERT(mControlledMediaNum >= 1);
mControlledMediaNum--;
LOG("Decrease controlled media num to %" PRId64, mControlledMediaNum);
if (mControlledMediaNum == 0) {
Deactivate();
}
return !IsAnyMediaBeingControlled() && mIsRegisteredToService;
}
void MediaController::IncreasePlayingControlledMediaNum() {
MOZ_ASSERT(!mShutdown);
MOZ_ASSERT(mPlayingControlledMediaNum >= 0);
mPlayingControlledMediaNum++;
LOG("Increase playing controlled media num to %" PRId64,
mPlayingControlledMediaNum);
MOZ_ASSERT(mPlayingControlledMediaNum <= mControlledMediaNum,
"The number of playing media should not exceed the number of "
"controlled media!");
if (mPlayingControlledMediaNum == 1) {
SetGuessedPlayState(MediaSessionPlaybackState::Playing);
}
}
void MediaController::DecreasePlayingControlledMediaNum() {
MOZ_ASSERT(!mShutdown);
mPlayingControlledMediaNum--;
LOG("Decrease playing controlled media num to %" PRId64,
mPlayingControlledMediaNum);
MOZ_ASSERT(mPlayingControlledMediaNum >= 0);
if (mPlayingControlledMediaNum == 0) {
SetGuessedPlayState(MediaSessionPlaybackState::Paused);
}
}
// TODO : Use watchable to moniter mControlledMediaNum
void MediaController::Activate() {
LOG("Activate");
MOZ_ASSERT(!mShutdown);
RefPtr<MediaControlService> service = MediaControlService::GetService();
if (service && !mIsRegisteredToService) {
@ -206,6 +180,7 @@ void MediaController::Activate() {
}
void MediaController::Deactivate() {
LOG("Deactivate");
MOZ_ASSERT(!mShutdown);
RefPtr<MediaControlService> service = MediaControlService::GetService();
if (service) {
@ -274,11 +249,11 @@ MediaSessionPlaybackState MediaController::GetState() const {
bool MediaController::IsAudible() const {
return mGuessedPlaybackState == MediaSessionPlaybackState::Playing &&
mAudibleState == MediaAudibleState::eAudible;
mMediaStatus.IsAudible();
}
uint64_t MediaController::ControlledMediaNum() const {
return mControlledMediaNum;
bool MediaController::IsAnyMediaBeingControlled() const {
return mMediaStatus.IsAnyMediaBeingControlled();
}
} // namespace dom

Просмотреть файл

@ -9,6 +9,7 @@
#include "ContentMediaController.h"
#include "MediaEventSource.h"
#include "MediaPlaybackStatus.h"
#include "mozilla/dom/MediaSessionController.h"
#include "mozilla/LinkedList.h"
#include "nsDataHashtable.h"
@ -75,7 +76,7 @@ class MediaController final
void Shutdown();
bool IsAudible() const;
uint64_t ControlledMediaNum() const;
bool IsAnyMediaBeingControlled() const;
MediaSessionPlaybackState GetState() const;
void SetDeclaredPlaybackState(uint64_t aSessionContextId,
@ -93,14 +94,13 @@ class MediaController final
void UpdateMediaControlKeysEventToContentMediaIfNeeded(
MediaControlKeysEvent aEvent);
void IncreaseControlledMediaNum();
void DecreaseControlledMediaNum();
void IncreasePlayingControlledMediaNum();
void DecreasePlayingControlledMediaNum();
void Activate();
void Deactivate();
bool ShouldActivateController() const;
bool ShouldDeactivateController() const;
void SetGuessedPlayState(MediaSessionPlaybackState aState);
// Whenever the declared playback state (from media session controller) or the
@ -108,12 +108,10 @@ class MediaController final
// to know if we need to update the virtual control interface.
void UpdateActualPlaybackState();
MediaAudibleState mAudibleState = MediaAudibleState::eInaudible;
bool mIsRegisteredToService = false;
int64_t mControlledMediaNum = 0;
int64_t mPlayingControlledMediaNum = 0;
bool mShutdown = false;
bool mIsInPictureInPictureMode = false;
MediaPlaybackStatus mMediaStatus;
// This state can match to the `guessed playback state` in the spec [1], it
// indicates if we have any media element playing within the tab which this

Просмотреть файл

@ -0,0 +1,105 @@
/* 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 "MediaPlaybackStatus.h"
#include "MediaControlUtils.h"
namespace mozilla {
namespace dom {
#undef LOG
#define LOG(msg, ...) \
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
("MediaPlaybackStatus=%p, " msg, this, ##__VA_ARGS__))
void MediaPlaybackStatus::UpdateMediaPlaybackState(uint64_t aContextId,
MediaPlaybackState aState) {
LOG("Update playback state '%s' for context %" PRIu64,
ToMediaPlaybackStateStr(aState), aContextId);
MOZ_ASSERT(NS_IsMainThread());
ContextMediaInfo& info = GetNotNullContextInfo(aContextId);
if (aState == MediaPlaybackState::eStarted) {
info.IncreaseControlledMediaNum();
} else if (aState == MediaPlaybackState::eStopped) {
info.DecreaseControlledMediaNum();
} else if (aState == MediaPlaybackState::ePlayed) {
info.IncreasePlayingMediaNum();
} else {
MOZ_ASSERT(aState == MediaPlaybackState::ePaused);
info.DecreasePlayingMediaNum();
}
// The context still has controlled media, we should keep its alive.
if (info.IsAnyMediaBeingControlled()) {
return;
}
MOZ_ASSERT(!info.IsPlaying());
MOZ_ASSERT(!info.IsAudible());
// DO NOT access `info` after this line.
DestroyContextInfo(aContextId);
}
void MediaPlaybackStatus::DestroyContextInfo(uint64_t aContextId) {
MOZ_ASSERT(NS_IsMainThread());
LOG("Remove context %" PRIu64, aContextId);
mContextInfoMap.Remove(aContextId);
}
void MediaPlaybackStatus::UpdateMediaAudibleState(uint64_t aContextId,
MediaAudibleState aState) {
LOG("Update audible state '%s' for context %" PRIu64,
ToMediaAudibleStateStr(aState), aContextId);
MOZ_ASSERT(NS_IsMainThread());
ContextMediaInfo& info = GetNotNullContextInfo(aContextId);
if (aState == MediaAudibleState::eAudible) {
info.IncreaseAudibleMediaNum();
} else {
MOZ_ASSERT(aState == MediaAudibleState::eInaudible);
info.DecreaseAudibleMediaNum();
}
}
bool MediaPlaybackStatus::IsPlaying() const {
MOZ_ASSERT(NS_IsMainThread());
for (auto iter = mContextInfoMap.ConstIter(); !iter.Done(); iter.Next()) {
if (iter.Data()->IsPlaying()) {
return true;
}
}
return false;
}
bool MediaPlaybackStatus::IsAudible() const {
MOZ_ASSERT(NS_IsMainThread());
for (auto iter = mContextInfoMap.ConstIter(); !iter.Done(); iter.Next()) {
if (iter.Data()->IsAudible()) {
return true;
}
}
return false;
}
bool MediaPlaybackStatus::IsAnyMediaBeingControlled() const {
MOZ_ASSERT(NS_IsMainThread());
for (auto iter = mContextInfoMap.ConstIter(); !iter.Done(); iter.Next()) {
if (iter.Data()->IsAnyMediaBeingControlled()) {
return true;
}
}
return false;
}
MediaPlaybackStatus::ContextMediaInfo&
MediaPlaybackStatus::GetNotNullContextInfo(uint64_t aContextId) {
MOZ_ASSERT(NS_IsMainThread());
if (!mContextInfoMap.Contains(aContextId)) {
mContextInfoMap.Put(aContextId, MakeUnique<ContextMediaInfo>(aContextId));
}
return *(mContextInfoMap.GetValue(aContextId)->get());
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,100 @@
/* 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/. */
#ifndef DOM_MEDIA_MEDIACONTROL_MEDIAPLAYBACKSTATUS_H_
#define DOM_MEDIA_MEDIACONTROL_MEDIAPLAYBACKSTATUS_H_
#include "ContentMediaController.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "nsDataHashtable.h"
#include "nsISupportsImpl.h"
#include "nsTArray.h"
namespace mozilla {
namespace dom {
/**
* MediaPlaybackStatus is an internal module for the media controller, it
* represents a tab's media related status, such like "does the tab contain any
* controlled media? is the tab playing? is the tab audible?".
*
* The reason we need this class is that we would like to encapsulate the
* details of determining the tab's media status. A tab can contains multiple
* browsing contexts, and each browsing context can have different media status.
* The final media status would be decided by checking all those context status.
*
* Use `UpdateMediaXXXState()` to update controlled media status, and use
* `IsXXX()` methods to acquire the playback status of the tab.
*/
class MediaPlaybackStatus final {
public:
void UpdateMediaPlaybackState(uint64_t aContextId, MediaPlaybackState aState);
void UpdateMediaAudibleState(uint64_t aContextId, MediaAudibleState aState);
bool IsPlaying() const;
bool IsAudible() const;
bool IsAnyMediaBeingControlled() const;
private:
/**
* This internal class stores detailed media status of controlled media for
* a browsing context.
*/
class ContextMediaInfo final {
public:
explicit ContextMediaInfo(uint64_t aContextId) : mContextId(aContextId) {}
~ContextMediaInfo() = default;
void IncreaseControlledMediaNum() {
MOZ_DIAGNOSTIC_ASSERT(mControlledMediaNum < UINT_MAX);
mControlledMediaNum++;
}
void DecreaseControlledMediaNum() {
MOZ_DIAGNOSTIC_ASSERT(mControlledMediaNum > 0);
mControlledMediaNum--;
}
void IncreasePlayingMediaNum() {
MOZ_DIAGNOSTIC_ASSERT(mPlayingMediaNum < mControlledMediaNum);
mPlayingMediaNum++;
}
void DecreasePlayingMediaNum() {
MOZ_DIAGNOSTIC_ASSERT(mPlayingMediaNum > 0);
mPlayingMediaNum--;
}
void IncreaseAudibleMediaNum() {
MOZ_DIAGNOSTIC_ASSERT(mAudibleMediaNum < mPlayingMediaNum);
mAudibleMediaNum++;
}
void DecreaseAudibleMediaNum() {
MOZ_DIAGNOSTIC_ASSERT(mAudibleMediaNum > 0);
mAudibleMediaNum--;
}
bool IsPlaying() const { return mPlayingMediaNum > 0; }
bool IsAudible() const { return mAudibleMediaNum > 0; }
bool IsAnyMediaBeingControlled() const { return mControlledMediaNum > 0; }
uint64_t Id() const { return mContextId; }
private:
/**
* The possible value for those three numbers should follow this rule,
* mControlledMediaNum >= mPlayingMediaNum >= mAudibleMediaNum
*/
uint32_t mControlledMediaNum = 0;
uint32_t mAudibleMediaNum = 0;
uint32_t mPlayingMediaNum = 0;
uint64_t mContextId = 0;
};
ContextMediaInfo& GetNotNullContextInfo(uint64_t aContextId);
void DestroyContextInfo(uint64_t aContextId);
// This contains all the media status of browsing contexts within a tab.
nsDataHashtable<nsUint64HashKey, UniquePtr<ContextMediaInfo>> mContextInfoMap;
};
} // namespace dom
} // namespace mozilla
#endif // DOM_MEDIA_MEDIACONTROL_MEDIAPLAYBACKSTATUS_H_

Просмотреть файл

@ -13,6 +13,7 @@ EXPORTS.mozilla.dom += [
'MediaController.h',
'MediaControlService.h',
'MediaControlUtils.h',
'MediaPlaybackStatus.h',
]
EXPORTS.ipc += [
@ -29,6 +30,7 @@ UNIFIED_SOURCES += [
'MediaController.cpp',
'MediaControlService.cpp',
'MediaControlUtils.cpp',
'MediaPlaybackStatus.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')

Просмотреть файл

@ -15,32 +15,32 @@ using namespace mozilla::dom;
TEST(MediaController, DefaultValueCheck)
{
RefPtr<MediaController> controller = new MediaController(CONTROLLER_ID);
ASSERT_TRUE(controller->ControlledMediaNum() == 0);
ASSERT_TRUE(!controller->IsAnyMediaBeingControlled());
ASSERT_TRUE(controller->Id() == CONTROLLER_ID);
ASSERT_TRUE(controller->GetState() == MediaSessionPlaybackState::None);
ASSERT_TRUE(!controller->IsAudible());
}
TEST(MediaController, NotifyMediaPlaybackChanged)
TEST(MediaController, IsAnyMediaBeingControlled)
{
RefPtr<MediaController> controller = new MediaController(CONTROLLER_ID);
ASSERT_TRUE(controller->ControlledMediaNum() == 0);
ASSERT_TRUE(!controller->IsAnyMediaBeingControlled());
controller->NotifyMediaPlaybackChanged(FAKE_CONTEXT_ID,
MediaPlaybackState::eStarted);
ASSERT_TRUE(controller->ControlledMediaNum() == 1);
ASSERT_TRUE(controller->IsAnyMediaBeingControlled());
controller->NotifyMediaPlaybackChanged(FAKE_CONTEXT_ID,
MediaPlaybackState::eStarted);
ASSERT_TRUE(controller->ControlledMediaNum() == 2);
ASSERT_TRUE(controller->IsAnyMediaBeingControlled());
controller->NotifyMediaPlaybackChanged(FAKE_CONTEXT_ID,
MediaPlaybackState::eStopped);
ASSERT_TRUE(controller->ControlledMediaNum() == 1);
ASSERT_TRUE(controller->IsAnyMediaBeingControlled());
controller->NotifyMediaPlaybackChanged(FAKE_CONTEXT_ID,
MediaPlaybackState::eStopped);
ASSERT_TRUE(controller->ControlledMediaNum() == 0);
ASSERT_TRUE(!controller->IsAnyMediaBeingControlled());
}
TEST(MediaController, ActiveAndDeactiveController)