Bug 1571493 - part2 : use 'MediaControlAgent' in media element. r=chunmin

We create `MediaControlEventListener` to register itself to the `MediaControlAgent` that is an event source, so that we can receive the media control event from the evnet source and operate media according to different types of event.

 `MediaControlEventListener` is also used to notify controlled media state to the media controller. When media first starts playing, or leaves bfcache and  has created listener before, we would notify `eStarted`. Notify `eStopped` when media destroys, or enter bfcache and has created listener before. When media's playing state changes, we would notifty `ePlayed` or `ePaused` depeding on media's `mPaused`.

Differential Revision: https://phabricator.services.mozilla.com/D57571
This commit is contained in:
alwu
2020-01-03 01:20:01 +00:00
parent a179fd8a09
commit aa180f8e57
2 changed files with 214 additions and 0 deletions

View File

@@ -62,12 +62,14 @@
#include "mozilla/dom/AudioTrack.h"
#include "mozilla/dom/AudioTrackList.h"
#include "mozilla/dom/BlobURLProtocolHandler.h"
#include "mozilla/dom/ContentMediaController.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/HTMLAudioElement.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLMediaElementBinding.h"
#include "mozilla/dom/HTMLSourceElement.h"
#include "mozilla/dom/HTMLVideoElement.h"
#include "mozilla/dom/MediaControlUtils.h"
#include "mozilla/dom/MediaEncryptedEvent.h"
#include "mozilla/dom/MediaErrorBinding.h"
#include "mozilla/dom/MediaSource.h"
@@ -128,6 +130,12 @@ extern mozilla::LazyLogModule gAutoplayPermissionLog;
#define AUTOPLAY_LOG(msg, ...) \
MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
// avoid redefined macro in unified build
#undef MEDIACONTROL_LOG
#define MEDIACONTROL_LOG(msg, ...) \
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
("HTMLMediaElement=%p, " msg, this, ##__VA_ARGS__))
#define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
#define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
@@ -369,6 +377,146 @@ class nsSourceErrorEventRunner : public nsMediaEvent {
}
};
/**
* We use MediaControlEventListener to listen MediaControlKeysEvent in order to
* play and pause media element when user press media control keys and update
* media's playback and audible state to the media controller.
*
* Use `Start()` to start listening event and use `Stop()` to stop listening
* event. In addition, notifying any change to media controller MUST be done
* after successfully calling `Start()`.
*/
class HTMLMediaElement::MediaControlEventListener final
: public MediaControlKeysEventListener {
public:
NS_INLINE_DECL_REFCOUNTING(MediaControlEventListener, override)
MOZ_INIT_OUTSIDE_CTOR explicit MediaControlEventListener(
HTMLMediaElement* aElement)
: mElement(aElement) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aElement);
}
void Start() {
MOZ_ASSERT(NS_IsMainThread());
if (IsStarted()) {
// We have already been started, do not notify start twice.
return;
}
// Fail to init media agent, we are not able to notify the media controller
// any update and also are not able to receive media control key events.
if (!InitMediaAgent()) {
MEDIACONTROL_LOG("Fail to init content media agent!");
return;
}
NotifyMediaStateChanged(ControlledMediaState::eStarted);
}
void Stop() {
MOZ_ASSERT(NS_IsMainThread());
if (!IsStarted()) {
// We have already been stopped, do not notify stop twice.
return;
}
NotifyMediaStateChanged(ControlledMediaState::eStopped);
// Remove ourselves from media agent, which would stop receiving event.
mControlAgent->RemoveListener(this);
mControlAgent = nullptr;
}
void NotifyMediaStartedPlaying() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsStarted());
if (mState == ControlledMediaState::eStarted ||
mState == ControlledMediaState::ePaused) {
NotifyMediaStateChanged(ControlledMediaState::ePlayed);
NotifyAudibleStateChanged();
}
}
void NotifyMediaStoppedPlaying() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsStarted());
if (mState == ControlledMediaState::ePlayed) {
NotifyMediaStateChanged(ControlledMediaState::ePaused);
}
}
void UpdateMediaAudibleState(bool aIsOwnerAudible) {
MOZ_ASSERT(NS_IsMainThread());
if (mIsOwnerAudible == aIsOwnerAudible) {
return;
}
mIsOwnerAudible = aIsOwnerAudible;
// If media hasn't started playing, it doesn't make sense to update media
// audible state. Therefore, in that case we would noitfy the audible state
// when media starts playing.
if (mState == ControlledMediaState::ePlayed) {
NotifyAudibleStateChanged();
}
}
void OnKeyPressed(MediaControlKeysEvent aEvent) override {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsStarted());
MEDIACONTROL_LOG("OnKeyPressed '%s'", ToMediaControlKeysEventStr(aEvent));
if (aEvent == MediaControlKeysEvent::ePlay) {
Owner()->Play();
} else if (aEvent == MediaControlKeysEvent::ePause ||
aEvent == MediaControlKeysEvent::eStop) {
Owner()->Pause();
}
}
bool IsStarted() const { return mState != ControlledMediaState::eStopped; }
private:
~MediaControlEventListener() = default;
bool InitMediaAgent() {
MOZ_ASSERT(NS_IsMainThread());
nsPIDOMWindowInner* window = Owner()->OwnerDoc()->GetInnerWindow();
if (!window) {
return false;
}
mControlAgent = ContentMediaAgent::Get(window->GetBrowsingContext());
if (!mControlAgent) {
return false;
}
mControlAgent->AddListener(this);
return true;
}
HTMLMediaElement* Owner() const { return mElement.get(); }
void NotifyMediaStateChanged(ControlledMediaState aState) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mControlAgent);
MEDIACONTROL_LOG("NotifyMediaState from state='%s' to state='%s'",
ToControlledMediaStateStr(mState),
ToControlledMediaStateStr(aState));
MOZ_ASSERT(mState != aState, "Should not notify same state again!");
mState = aState;
mControlAgent->NotifyMediaStateChanged(this, mState);
}
void NotifyAudibleStateChanged() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsStarted());
mControlAgent->NotifyAudibleStateChanged(this, mIsOwnerAudible);
}
ControlledMediaState mState = ControlledMediaState::eStopped;
WeakPtr<HTMLMediaElement> mElement;
RefPtr<ContentMediaAgent> mControlAgent;
bool mIsOwnerAudible = false;
};
class HTMLMediaElement::MediaStreamTrackListener
: public DOMMediaStream::TrackListener {
public:
@@ -4034,6 +4182,8 @@ void HTMLMediaElement::Init() {
mWatchManager.Watch(mPaused, &HTMLMediaElement::UpdateWakeLock);
mWatchManager.Watch(mPaused, &HTMLMediaElement::UpdateOutputTracksMuting);
mWatchManager.Watch(
mPaused, &HTMLMediaElement::NotifyMediaControlPlaybackStateChanged);
mWatchManager.Watch(mReadyState, &HTMLMediaElement::UpdateOutputTracksMuting);
mWatchManager.Watch(mTracksCaptured,
@@ -4120,6 +4270,9 @@ HTMLMediaElement::~HTMLMediaElement() {
mResumeDelayedPlaybackAgent = nullptr;
}
StopListeningMediaControlEventIfNeeded();
mMediaControlEventListener = nullptr;
WakeLockRelease();
ReportPlayedTimeAfterBlockedTelemetry();
@@ -4318,6 +4471,8 @@ void HTMLMediaElement::PlayInternal(bool aHandlingUserInput) {
UpdatePreloadAction();
UpdateSrcMediaStreamPlaying();
StartListeningMediaControlEventIfNeeded();
// Once play() has been called in a user generated event handler,
// it is allowed to autoplay. Note: we can reach here when not in
// a user generated event handler if our readyState has not yet
@@ -6054,6 +6209,8 @@ void HTMLMediaElement::CheckAutoplayDataReady() {
UpdateSrcMediaStreamPlaying();
UpdateAudioChannelPlayingState();
StartListeningMediaControlEventIfNeeded();
if (mDecoder) {
SetPlayedOrSeeked(true);
if (mCurrentPlayRangeStart == -1.0) {
@@ -6385,6 +6542,7 @@ void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement,
mEventDeliveryPaused = aSuspendEvents;
// We won't want to resume media element from the bfcache.
ClearResumeDelayedMediaPlaybackAgentIfNeeded();
StopListeningMediaControlEventIfNeeded();
} else {
if (!mPaused) {
mCurrentLoadPlayTime.Start();
@@ -6407,6 +6565,16 @@ void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement,
!AutoplayPolicy::IsAllowedToPlay(*this)) {
MaybeNotifyAutoplayBlocked();
}
if (mMediaControlEventListener) {
MOZ_ASSERT(!mMediaControlEventListener->IsStarted(),
"We didn't stop listening event when we were in bfcache?");
mMediaControlEventListener->Start();
// As resuming media from bfcache won't change `mPaused`, so we have to
// update the playback state manually,
if (!mPaused) {
mMediaControlEventListener->NotifyMediaStartedPlaying();
}
}
}
}
}
@@ -7250,6 +7418,9 @@ void HTMLMediaElement::NotifyAudioPlaybackChanged(
if (mAudioChannelWrapper) {
mAudioChannelWrapper->NotifyAudioPlaybackChanged(aReason);
}
if (mMediaControlEventListener) {
mMediaControlEventListener->UpdateMediaAudibleState(IsAudible());
}
// only request wake lock for audible media.
UpdateWakeLock();
}
@@ -7696,6 +7867,33 @@ void HTMLMediaElement::ClearResumeDelayedMediaPlaybackAgentIfNeeded() {
}
}
void HTMLMediaElement::NotifyMediaControlPlaybackStateChanged() {
if (!mMediaControlEventListener || !mMediaControlEventListener->IsStarted()) {
return;
}
if (mPaused) {
mMediaControlEventListener->NotifyMediaStoppedPlaying();
} else {
mMediaControlEventListener->NotifyMediaStartedPlaying();
}
}
void HTMLMediaElement::StartListeningMediaControlEventIfNeeded() {
if (!mMediaControlEventListener) {
mMediaControlEventListener = new MediaControlEventListener(this);
}
if (!mMediaControlEventListener->IsStarted()) {
mMediaControlEventListener->Start();
}
}
void HTMLMediaElement::StopListeningMediaControlEventIfNeeded() {
if (mMediaControlEventListener && mMediaControlEventListener->IsStarted()) {
mMediaControlEventListener->NotifyMediaStoppedPlaying();
mMediaControlEventListener->Stop();
}
}
} // namespace dom
} // namespace mozilla