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',