Bug 1571493 - part1 : implement 'ContentMediaController' which is used to notify 'MediaControlKeysEvent' for controlled media. r=chunmin

Implement class `ContentMediaController` that is used to dispatch media controls key events to those media which would like to be controlled.

`ContentMediaController` is inherited from two classes, `MediaControlAgent` and `MediaControlKeysEventHandler`. The former one is used for controlled media, the controlled media can register itself to `MediaControlAgent` to receive events and do corresponding operations depending on the event type. The latter one is used to handle events sent from chrome process and dispatch them to the corresponding controlled media.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
alwu 2020-01-03 01:29:38 +00:00
Родитель 62b6b0092c
Коммит 9977c552ff
5 изменённых файлов: 281 добавлений и 11 удалений

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

@ -0,0 +1,129 @@
/* 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 "ContentMediaController.h"
#include "MediaControlUtils.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/StaticPtr.h"
#include "nsDataHashtable.h"
namespace mozilla {
namespace dom {
using ControllerMap =
nsDataHashtable<nsUint64HashKey, RefPtr<ContentMediaController>>;
static StaticAutoPtr<ControllerMap> sControllers;
#undef LOG
#define LOG(msg, ...) \
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
("ContentMediaController=%p, " msg, this, ##__VA_ARGS__))
static already_AddRefed<ContentMediaController>
GetContentMediaControllerFromBrowsingContext(
BrowsingContext* aBrowsingContext) {
MOZ_ASSERT(NS_IsMainThread());
if (!sControllers) {
sControllers = new ControllerMap();
ClearOnShutdown(&sControllers);
}
RefPtr<BrowsingContext> topLevelBC =
GetAliveTopBrowsingContext(aBrowsingContext);
if (!topLevelBC) {
return nullptr;
}
const uint64_t topLevelBCId = topLevelBC->Id();
RefPtr<ContentMediaController> controller;
if (!sControllers->Contains(topLevelBCId)) {
controller = new ContentMediaController(topLevelBCId);
sControllers->Put(topLevelBCId, controller);
} else {
controller = sControllers->Get(topLevelBCId);
}
return controller.forget();
}
/* static */
ContentControlKeyEventReceiver* ContentControlKeyEventReceiver::Get(
BrowsingContext* aBC) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<ContentMediaController> controller =
GetContentMediaControllerFromBrowsingContext(aBC);
return controller
? static_cast<ContentControlKeyEventReceiver*>(controller.get())
: nullptr;
}
/* static */
ContentMediaAgent* ContentMediaAgent::Get(BrowsingContext* aBC) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<ContentMediaController> controller =
GetContentMediaControllerFromBrowsingContext(aBC);
return controller ? static_cast<ContentMediaAgent*>(controller.get())
: nullptr;
}
ContentMediaController::ContentMediaController(uint64_t aId)
: mTopLevelBrowsingContextId(aId) {}
void ContentMediaController::AddListener(
MediaControlKeysEventListener* aListener) {
MOZ_ASSERT(NS_IsMainThread());
ContentMediaAgent::AddListener(aListener);
}
void ContentMediaController::RemoveListener(
MediaControlKeysEventListener* aListener) {
MOZ_ASSERT(NS_IsMainThread());
ContentMediaAgent::RemoveListener(aListener);
// No more media needs to be controlled, so we can release this and recreate
// it when someone needs it.
if (mListeners.IsEmpty()) {
Close();
}
}
void ContentMediaController::NotifyMediaStateChanged(
const MediaControlKeysEventListener* aMedia, ControlledMediaState aState) {
MOZ_ASSERT(NS_IsMainThread());
if (!mListeners.Contains(aMedia)) {
return;
}
// TODO : implement in following patches.
}
void ContentMediaController::NotifyAudibleStateChanged(
const MediaControlKeysEventListener* aMedia, bool aAudible) {
MOZ_ASSERT(NS_IsMainThread());
if (!mListeners.Contains(aMedia)) {
return;
}
// TODO : implement in following patches.
}
void ContentMediaController::OnKeyPressed(MediaControlKeysEvent aEvent) {
MOZ_ASSERT(NS_IsMainThread());
LOG("Handle '%s' event, listener num=%zu", ToMediaControlKeysEventStr(aEvent),
mListeners.Length());
for (auto& listener : mListeners) {
listener->OnKeyPressed(aEvent);
}
}
void ContentMediaController::Close() {
MOZ_ASSERT(NS_IsMainThread());
MediaControlKeysEventSource::Close();
// `sControllers` might be null if ContentMediaController is detroyed after
// freeing `sControllers`.
if (sControllers) {
sControllers->Remove(mTopLevelBrowsingContextId);
}
}
} // namespace dom
} // namespace mozilla

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

@ -0,0 +1,134 @@
/* 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_CONTENTMEDIACONTROLLER_H_
#define DOM_MEDIA_MEDIACONTROL_CONTENTMEDIACONTROLLER_H_
#include "MediaControlKeysEvent.h"
namespace mozilla {
namespace dom {
class BrowsingContext;
/**
* This enum is used to update controlled media state to the media controller in
* the chrome process.
* `eStarted`: media has successfully registered to the content media controller
* `ePlayed` : media has started playing
* `ePaused` : media has paused playing, but still can be resumed by content
* media controller
* `eStopped`: media has unregistered from the content media controller, we can
* not control it anymore
* When using these states to notify media controller, there are some rules we
* MUST follow (1) `eStart` MUST be the first state and `eStop` MUST be the last
* state (2) Do not notify same state again (3) `ePaused` can only be used after
* notifying `ePlayed`.
*/
enum class ControlledMediaState : uint32_t {
eStarted,
ePlayed,
ePaused,
eStopped,
};
/**
* ContentMediaAgent is an interface which we use to (1) update controlled media
* status to the media controller in the chrome process and (2) act an event
* source to dispatch control key events to its listeners.
*
* If the media would like to know the media control key events, then media
* MUST inherit from MediaControlKeysEventListener, and register themselves to
* ContentMediaAgent. Whenever media key events occur, ContentMediaAgent would
* notify all its listeners. In addition, whenever controlled media changes its
* playback status or audible state, they should update their status update via
* ContentMediaAgent.
*
* Each browsing context tree would only have one ContentMediaAgent that is used
* to update controlled media status existing in that browsing context tree.
*/
class ContentMediaAgent : public MediaControlKeysEventSource {
public:
// Return nullptr if the top level browsing context is no longer alive.
static ContentMediaAgent* Get(BrowsingContext* aBC);
// Use this method to update the media playback state of controlled media, and
// MUST follow the rule of ControlledMediaState.
virtual void NotifyMediaStateChanged(
const MediaControlKeysEventListener* aMedia,
ControlledMediaState aState) = 0;
// Use this method to update the audible state of controlled media, and it's
// safe to notify same audible state again.
virtual void NotifyAudibleStateChanged(
const MediaControlKeysEventListener* aMedia, bool aAudible) = 0;
private:
// We don't need them in our case, so make them private to prevent usage.
bool Open() override { return true; }
bool IsOpened() const override { return true; }
};
/**
* ContentControlKeyEventReceiver is an interface which is used to receive media
* control key events sent from the chrome process, this class MUST only be used
* in PlaybackController.
*
* Each browsing context tree would only have one ContentControlKeyEventReceiver
* that is used to handle media control key events for that browsing context
* tree.
*/
class ContentControlKeyEventReceiver : public MediaControlKeysEventListener {
public:
// Return nullptr if the top level browsing context is no longer alive.
static ContentControlKeyEventReceiver* Get(BrowsingContext* aBC);
void OnKeyPressed(MediaControlKeysEvent aKeyEvent) override = 0;
};
/**
* ContentMediaController has a responsibility to update the content media state
* to MediaController that exists in the chrome process and control all media
* within a tab. It also delivers control commands from MediaController in order
* to control media in the content page.
*
* Each ContentMediaController has its own ID that is corresponding to the top
* level browsing context ID. That means we share same the
* ContentMediaController for those media existing in the same browsing context
* tree and same process.
*
* So if the content of a page are all in the same process, then we would only
* create one ContentMediaController. However, if the content of a page are
* running in different processes because a page has cross-origin iframes, then
* we would have multiple ContentMediaController at the same time, creating one
* ContentMediaController in each process to manage media.
*/
class ContentMediaController final : public ContentMediaAgent,
public ContentControlKeyEventReceiver {
public:
NS_INLINE_DECL_REFCOUNTING(ContentMediaController, override)
explicit ContentMediaController(uint64_t aId);
// ContentMediaAgent methods
void AddListener(MediaControlKeysEventListener* aListener) override;
void RemoveListener(MediaControlKeysEventListener* aListener) override;
void NotifyMediaStateChanged(const MediaControlKeysEventListener* aMedia,
ControlledMediaState aState) override;
void NotifyAudibleStateChanged(const MediaControlKeysEventListener* aMedia,
bool aAudible) override;
// ContentControlKeyEventReceiver method
void OnKeyPressed(MediaControlKeysEvent aEvent) override;
private:
~ContentMediaController() = default;
void Close() override;
uint64_t mTopLevelBrowsingContextId;
};
} // namespace dom
} // namespace mozilla
#endif // DOM_MEDIA_MEDIACONTROL_CONTENTMEDIACONTROLLER_H_

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

@ -21,22 +21,25 @@ mozilla::LazyLogModule gMediaControlLog("MediaControl");
namespace mozilla {
namespace dom {
static RefPtr<BrowsingContext> GetTopBrowsingContextByWindowID(
uint64_t aWindowID) {
BrowsingContext* GetAliveTopBrowsingContext(BrowsingContext* aBC) {
if (!aBC || aBC->IsDiscarded()) {
return nullptr;
}
aBC = aBC->Top();
if (!aBC || aBC->IsDiscarded()) {
return nullptr;
}
return aBC;
}
static BrowsingContext* GetTopBrowsingContextByWindowID(uint64_t aWindowID) {
RefPtr<nsGlobalWindowOuter> window =
nsGlobalWindowOuter::GetOuterWindowWithId(aWindowID);
if (!window) {
return nullptr;
}
RefPtr<BrowsingContext> bc = window->GetBrowsingContext();
if (!bc || bc->IsDiscarded()) {
return nullptr;
}
bc = bc->Top();
if (!bc || bc->IsDiscarded()) {
return nullptr;
}
return bc;
return GetAliveTopBrowsingContext(window->GetBrowsingContext());
}
static void NotifyMediaActiveChanged(const RefPtr<BrowsingContext>& aBc,

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

@ -78,6 +78,8 @@ inline const char* ToPlaybackStateEventStr(
}
}
BrowsingContext* GetAliveTopBrowsingContext(BrowsingContext* aBC);
void NotifyMediaStarted(uint64_t aWindowID);
void NotifyMediaStopped(uint64_t aWindowID);
void NotifyMediaAudibleChanged(uint64_t aWindowID, bool aAudible);

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

@ -5,6 +5,7 @@
EXPORTS.mozilla.dom += [
'AudioFocusManager.h',
'ContentMediaController.h',
'MediaControlKeysEvent.h',
'MediaControlKeysManager.h',
'MediaController.h',
@ -19,6 +20,7 @@ EXPORTS.ipc += [
UNIFIED_SOURCES += [
'AudioFocusManager.cpp',
'ContentMediaController.cpp',
'MediaControlKeysEvent.cpp',
'MediaControlKeysManager.cpp',
'MediaController.cpp',