From a9d4b33f079c560d7efa9221a96edccf0ecb89e5 Mon Sep 17 00:00:00 2001 From: alwu Date: Wed, 7 Aug 2019 01:46:10 +0000 Subject: [PATCH] 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 --- .../mediacontrol/MediaControlService.cpp | 151 ++++++++++++++++++ dom/media/mediacontrol/MediaControlService.h | 58 +++++++ dom/media/mediacontrol/MediaControlUtils.h | 20 +++ dom/media/mediacontrol/MediaController.cpp | 112 +++++++++++++ dom/media/mediacontrol/MediaController.h | 96 +++++++++++ dom/media/mediacontrol/moz.build | 19 +++ dom/media/moz.build | 1 + 7 files changed, 457 insertions(+) create mode 100644 dom/media/mediacontrol/MediaControlService.cpp create mode 100644 dom/media/mediacontrol/MediaControlService.h create mode 100644 dom/media/mediacontrol/MediaControlUtils.h create mode 100644 dom/media/mediacontrol/MediaController.cpp create mode 100644 dom/media/mediacontrol/MediaController.h create mode 100644 dom/media/mediacontrol/moz.build diff --git a/dom/media/mediacontrol/MediaControlService.cpp b/dom/media/mediacontrol/MediaControlService.cpp new file mode 100644 index 000000000000..235cbe471cc1 --- /dev/null +++ b/dom/media/mediacontrol/MediaControlService.cpp @@ -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 gMediaControlService; +static bool sIsXPCOMShutdown = false; + +/* static */ +RefPtr MediaControlService::GetService() { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(), + "MediaControlService only runs on Chrome process!"); + if (sIsXPCOMShutdown) { + return nullptr; + } + if (!gMediaControlService) { + gMediaControlService = new MediaControlService(); + } + RefPtr 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 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 obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "xpcom-shutdown"); + } + ShutdownAllControllers(); + mControllers.Clear(); + sIsXPCOMShutdown = true; + gMediaControlService = nullptr; + } + return NS_OK; +} + +RefPtr MediaControlService::GetOrCreateControllerById( + const uint64_t aId) const { + RefPtr controller = mControllers.Get(aId); + if (!controller) { + controller = new TabMediaController(aId); + } + return controller; +} + +RefPtr MediaControlService::GetControllerById( + const uint64_t aId) const { + return mControllers.Get(aId); +} + +void MediaControlService::AddMediaController( + const RefPtr& 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& 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& controller = iter.Data(); + controller->Play(); + } +} + +void MediaControlService::PauseAllControllers() const { + for (auto iter = mControllers.ConstIter(); !iter.Done(); iter.Next()) { + const RefPtr& controller = iter.Data(); + controller->Pause(); + } +} + +void MediaControlService::StopAllControllers() const { + for (auto iter = mControllers.ConstIter(); !iter.Done(); iter.Next()) { + const RefPtr& controller = iter.Data(); + controller->Stop(); + } +} + +void MediaControlService::ShutdownAllControllers() const { + for (auto iter = mControllers.ConstIter(); !iter.Done(); iter.Next()) { + const RefPtr& controller = iter.Data(); + controller->Shutdown(); + } +} + +uint64_t MediaControlService::GetControllersNum() const { + return mControllers.Count(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/media/mediacontrol/MediaControlService.h b/dom/media/mediacontrol/MediaControlService.h new file mode 100644 index 000000000000..6b1670763d53 --- /dev/null +++ b/dom/media/mediacontrol/MediaControlService.h @@ -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 GetService(); + + RefPtr GetOrCreateControllerById(const uint64_t aId) const; + RefPtr GetControllerById(const uint64_t aId) const; + + void AddMediaController(const RefPtr& aController); + void RemoveMediaController(const RefPtr& aController); + uint64_t GetControllersNum() const; + + private: + MediaControlService(); + ~MediaControlService(); + + void PlayAllControllers() const; + void PauseAllControllers() const; + void StopAllControllers() const; + void ShutdownAllControllers() const; + + nsDataHashtable> mControllers; +}; + +} // namespace dom +} // namespace mozilla + +#endif diff --git a/dom/media/mediacontrol/MediaControlUtils.h b/dom/media/mediacontrol/MediaControlUtils.h new file mode 100644 index 000000000000..fe0c73ccf26a --- /dev/null +++ b/dom/media/mediacontrol/MediaControlUtils.h @@ -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 diff --git a/dom/media/mediacontrol/MediaController.cpp b/dom/media/mediacontrol/MediaController.cpp new file mode 100644 index 000000000000..d5db140ff8eb --- /dev/null +++ b/dom/media/mediacontrol/MediaController.cpp @@ -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 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 service = MediaControlService::GetService(); + MOZ_ASSERT(service); + service->AddMediaController(this); +} + +void TabMediaController::Deactivate() { + mIsPlaying = false; + RefPtr service = MediaControlService::GetService(); + MOZ_ASSERT(service); + service->RemoveMediaController(this); +} + +uint64_t TabMediaController::ControlledMediaNum() const { + return mControlledMediaNum; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/media/mediacontrol/MediaController.h b/dom/media/mediacontrol/MediaController.h new file mode 100644 index 000000000000..eee311f83f33 --- /dev/null +++ b/dom/media/mediacontrol/MediaController.h @@ -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 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 diff --git a/dom/media/mediacontrol/moz.build b/dom/media/mediacontrol/moz.build new file mode 100644 index 000000000000..54f08d584bb6 --- /dev/null +++ b/dom/media/mediacontrol/moz.build @@ -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' diff --git a/dom/media/moz.build b/dom/media/moz.build index 65e00e344c1b..343f035bef70 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -42,6 +42,7 @@ DIRS += [ 'imagecapture', 'ipc', 'mediacapabilities', + 'mediacontrol', 'mediasink', 'mediasource', 'mp3',