Bug 1565689 - part1 : implement MediaController and MediaControlService. r=baku

In order to have a centralized audio control in the parent process, we create two new classes here.

* MediaController
MediaController is a class used to control certain amount of media in the content process. Every controller corresponds to a browsing context.
For example, TabMediaController would correspond to the top level browsing context, which mean it can control all media in the specific tab.

* MediaControlService
As there might be multiple tabs playing audio, so there would be multiple controllers. MediaControlService is a place to manage all of them, you can access specific controller through MediaControlService by providing controller ID.
Everytime a controller becomes active, which means there is a media starts in corresponding browsing context, then controller would be added into the list of the MediaControlService. And it would be removed from the list when the media in corresponding browsering context stopped.

Differential Revision: https://phabricator.services.mozilla.com/D38141

--HG--
extra : moz-landing-system : lando
This commit is contained in:
alwu 2019-08-07 01:46:10 +00:00
Родитель e71120d4fe
Коммит a9d4b33f07
7 изменённых файлов: 457 добавлений и 0 удалений

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

@ -0,0 +1,151 @@
/* 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 "MediaControlService.h"
#include "MediaController.h"
#include "mozilla/Assertions.h"
#include "mozilla/Logging.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "nsIObserverService.h"
#include "nsXULAppAPI.h"
extern mozilla::LazyLogModule gMediaControlLog;
#undef LOG
#define LOG(msg, ...) \
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
("MediaControlService=%p, " msg, this, ##__VA_ARGS__))
namespace mozilla {
namespace dom {
StaticRefPtr<MediaControlService> gMediaControlService;
static bool sIsXPCOMShutdown = false;
/* static */
RefPtr<MediaControlService> MediaControlService::GetService() {
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(),
"MediaControlService only runs on Chrome process!");
if (sIsXPCOMShutdown) {
return nullptr;
}
if (!gMediaControlService) {
gMediaControlService = new MediaControlService();
}
RefPtr<MediaControlService> service = gMediaControlService.get();
return service;
}
NS_INTERFACE_MAP_BEGIN(MediaControlService)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(MediaControlService)
NS_IMPL_RELEASE(MediaControlService)
MediaControlService::MediaControlService() {
LOG("create media control service");
RefPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->AddObserver(this, "xpcom-shutdown", false);
}
}
MediaControlService::~MediaControlService() {
LOG("destroy media control service");
ShutdownAllControllers();
}
NS_IMETHODIMP
MediaControlService::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (!strcmp(aTopic, "xpcom-shutdown")) {
LOG("XPCOM shutdown");
MOZ_ASSERT(gMediaControlService);
RefPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, "xpcom-shutdown");
}
ShutdownAllControllers();
mControllers.Clear();
sIsXPCOMShutdown = true;
gMediaControlService = nullptr;
}
return NS_OK;
}
RefPtr<MediaController> MediaControlService::GetOrCreateControllerById(
const uint64_t aId) const {
RefPtr<MediaController> controller = mControllers.Get(aId);
if (!controller) {
controller = new TabMediaController(aId);
}
return controller;
}
RefPtr<MediaController> MediaControlService::GetControllerById(
const uint64_t aId) const {
return mControllers.Get(aId);
}
void MediaControlService::AddMediaController(
const RefPtr<MediaController>& aController) {
MOZ_DIAGNOSTIC_ASSERT(aController);
const uint64_t cId = aController->Id();
MOZ_DIAGNOSTIC_ASSERT(!mControllers.GetValue(cId),
"Controller has been added already!");
mControllers.Put(cId, aController);
LOG("Add media controller %" PRId64 ", currentNum=%" PRId64, cId,
GetControllersNum());
}
void MediaControlService::RemoveMediaController(
const RefPtr<MediaController>& aController) {
MOZ_DIAGNOSTIC_ASSERT(aController);
const uint64_t cId = aController->Id();
MOZ_DIAGNOSTIC_ASSERT(mControllers.GetValue(cId),
"Controller does not exist!");
mControllers.Remove(cId);
LOG("Remove media controller %" PRId64 ", currentNum=%" PRId64, cId,
GetControllersNum());
}
void MediaControlService::PlayAllControllers() const {
for (auto iter = mControllers.ConstIter(); !iter.Done(); iter.Next()) {
const RefPtr<MediaController>& controller = iter.Data();
controller->Play();
}
}
void MediaControlService::PauseAllControllers() const {
for (auto iter = mControllers.ConstIter(); !iter.Done(); iter.Next()) {
const RefPtr<MediaController>& controller = iter.Data();
controller->Pause();
}
}
void MediaControlService::StopAllControllers() const {
for (auto iter = mControllers.ConstIter(); !iter.Done(); iter.Next()) {
const RefPtr<MediaController>& controller = iter.Data();
controller->Stop();
}
}
void MediaControlService::ShutdownAllControllers() const {
for (auto iter = mControllers.ConstIter(); !iter.Done(); iter.Next()) {
const RefPtr<MediaController>& controller = iter.Data();
controller->Shutdown();
}
}
uint64_t MediaControlService::GetControllersNum() const {
return mControllers.Count();
}
} // namespace dom
} // namespace mozilla

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

@ -0,0 +1,58 @@
/* 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 mozilla_dom_mediacontrolservice_h__
#define mozilla_dom_mediacontrolservice_h__
#include "mozilla/AlreadyAddRefed.h"
#include "MediaController.h"
#include "nsDataHashtable.h"
#include "nsIObserver.h"
#include "nsTArray.h"
namespace mozilla {
namespace dom {
/**
* MediaControlService is an interface to access controllers by providing
* controller Id. Everytime when controller becomes active, which means there is
* one or more media started in the corresponding browsing context, so now the
* controller is actually controlling something in the content process, so it
* would be added into the list of the MediaControlService. The controller would
* be removed from the list of the MediaControlService when it becomes inactive,
* which means no media is playing in the corresponding browsing context. Note
* that, a controller can't be added to or remove from the list twice. It should
* should have a responsibility to add and remove itself in the proper time.
*/
class MediaControlService final : public nsIObserver {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
static RefPtr<MediaControlService> GetService();
RefPtr<MediaController> GetOrCreateControllerById(const uint64_t aId) const;
RefPtr<MediaController> GetControllerById(const uint64_t aId) const;
void AddMediaController(const RefPtr<MediaController>& aController);
void RemoveMediaController(const RefPtr<MediaController>& aController);
uint64_t GetControllersNum() const;
private:
MediaControlService();
~MediaControlService();
void PlayAllControllers() const;
void PauseAllControllers() const;
void StopAllControllers() const;
void ShutdownAllControllers() const;
nsDataHashtable<nsUint64HashKey, RefPtr<MediaController>> mControllers;
};
} // namespace dom
} // namespace mozilla
#endif

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

@ -0,0 +1,20 @@
/* -*- 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/. */
#ifndef mozilla_dom_MediaControlUtils_h
#define mozilla_dom_MediaControlUtils_h
#include "MediaController.h"
#include "MediaControlService.h"
mozilla::LazyLogModule gMediaControlLog("MediaControl");
#undef LOG
#define LOG(msg, ...) \
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
("MediaControlUtils, " msg, ##__VA_ARGS__))
#endif // mozilla_dom_MediaControlUtils_h

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

@ -0,0 +1,112 @@
/* -*- 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 "MediaController.h"
#include "MediaControlService.h"
#include "mozilla/dom/BrowsingContext.h"
extern mozilla::LazyLogModule gMediaControlLog;
// avoid redefined macro in unified build
#undef LOG
#define LOG(msg, ...) \
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
("TabMediaController=%p, Id=%" PRId64 ", " msg, this, this->Id(), \
##__VA_ARGS__))
namespace mozilla {
namespace dom {
already_AddRefed<BrowsingContext> MediaController::GetContext() const {
return BrowsingContext::Get(mBrowsingContextId);
}
TabMediaController::TabMediaController(uint64_t aContextId)
: MediaController(aContextId) {
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(),
"MediaController only runs on Chrome process!");
LOG("Create controller %" PRId64, Id());
}
TabMediaController::~TabMediaController() {
LOG("Destroy controller %" PRId64, Id());
MOZ_DIAGNOSTIC_ASSERT(!mControlledMediaNum);
};
void TabMediaController::Play() {
LOG("Play");
mIsPlaying = true;
}
void TabMediaController::Pause() {
LOG("Pause");
mIsPlaying = false;
}
void TabMediaController::Stop() {
LOG("Stop");
mIsPlaying = false;
}
void TabMediaController::Shutdown() {
mIsPlaying = false;
mControlledMediaNum = 0;
}
bool TabMediaController::IsAudible() const { return mIsPlaying && mAudible; }
void TabMediaController::NotifyMediaActiveChanged(bool aActive) {
if (aActive) {
IncreaseControlledMediaNum();
} else {
DecreaseControlledMediaNum();
}
}
void TabMediaController::NotifyMediaAudibleChanged(bool aAudible) {
mAudible = aAudible;
}
void TabMediaController::IncreaseControlledMediaNum() {
MOZ_DIAGNOSTIC_ASSERT(mControlledMediaNum >= 0);
mControlledMediaNum++;
LOG("Increase controlled media num to %" PRId64, mControlledMediaNum);
if (mControlledMediaNum == 1) {
Activate();
}
}
void TabMediaController::DecreaseControlledMediaNum() {
MOZ_DIAGNOSTIC_ASSERT(mControlledMediaNum >= 1);
mControlledMediaNum--;
LOG("Decrease controlled media num to %" PRId64, mControlledMediaNum);
if (mControlledMediaNum == 0) {
Deactivate();
}
}
// TODO : Use watchable to moniter mControlledMediaNum
void TabMediaController::Activate() {
mIsPlaying = true;
RefPtr<MediaControlService> service = MediaControlService::GetService();
MOZ_ASSERT(service);
service->AddMediaController(this);
}
void TabMediaController::Deactivate() {
mIsPlaying = false;
RefPtr<MediaControlService> service = MediaControlService::GetService();
MOZ_ASSERT(service);
service->RemoveMediaController(this);
}
uint64_t TabMediaController::ControlledMediaNum() const {
return mControlledMediaNum;
}
} // namespace dom
} // namespace mozilla

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

@ -0,0 +1,96 @@
/* -*- 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/. */
#ifndef mozilla_dom_mediacontroller_h__
#define mozilla_dom_mediacontroller_h__
#include "nsDataHashtable.h"
#include "nsISupportsImpl.h"
namespace mozilla {
namespace dom {
class BrowsingContext;
/**
* MediaController is a class which is used to control media in the content
* process. It's a basic interface class and you should implement you own class
* to inherit this class. Every controller would correspond to a browsing
* context. For example, TabMediaController corresponds to the top level
* browsing context. In the future, we might implement MediaSessionController
* which could correspond to any browsing context, depending on which browsing
* context has active media session.
*/
class MediaController {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaController);
explicit MediaController(uint64_t aContextId)
: mBrowsingContextId(aContextId) {}
virtual void Play() = 0;
virtual void Pause() = 0;
virtual void Stop() = 0;
virtual void Shutdown() = 0;
virtual void NotifyMediaActiveChanged(bool aActive) = 0;
virtual void NotifyMediaAudibleChanged(bool aAudible) = 0;
bool IsPlaying() const { return mIsPlaying; }
uint64_t Id() const { return mBrowsingContextId; }
virtual uint64_t ControlledMediaNum() const { return 0; }
virtual bool IsAudible() const { return false; }
protected:
virtual ~MediaController() = default;
already_AddRefed<BrowsingContext> GetContext() const;
uint64_t mBrowsingContextId;
bool mIsPlaying = false;
};
/**
* TabMediaController is used to control all media in a tab. It can only be used
* in Chrome process. Everytime media starts in the tab, it would increase the
* number of controlled media, and also would decrease the number when media
* stops. The media it controls might be in different content processes, so we
* keep tracking the top level browsing context in the tab, which can be used to
* propagate conmmands to remote content processes.
*/
class TabMediaController final : public MediaController {
public:
explicit TabMediaController(uint64_t aContextId);
void Play() override;
void Pause() override;
void Stop() override;
void Shutdown() override;
uint64_t ControlledMediaNum() const override;
bool IsAudible() const override;
void NotifyMediaActiveChanged(bool aActive) override;
void NotifyMediaAudibleChanged(bool aAudible) override;
protected:
~TabMediaController();
private:
void IncreaseControlledMediaNum();
void DecreaseControlledMediaNum();
void Activate();
void Deactivate();
int64_t mControlledMediaNum = 0;
bool mAudible = false;
};
} // namespace dom
} // namespace mozilla
#endif

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

@ -0,0 +1,19 @@
# vim: set filetype=python:
# 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/.
EXPORTS.mozilla.dom += [
'MediaController.h',
'MediaControlService.h',
'MediaControlUtils.h',
]
UNIFIED_SOURCES += [
'MediaController.cpp',
'MediaControlService.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'

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

@ -42,6 +42,7 @@ DIRS += [
'imagecapture',
'ipc',
'mediacapabilities',
'mediacontrol',
'mediasink',
'mediasource',
'mp3',