Bug 1302350 - part4 : refactor the media-blocking mechanism.
The old way is to start playing first, and then block the media element. This
way is too complicated because it involves lots of interal state and isn't intuitive.
The new way is to ignore the play if the media element should be blocked. It's
easy to know and we doesn't need to keep any internal states because we don't play
the media element.
MozReview-Commit-ID: B20e0pvXES4
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2996,29 +2996,29 @@ void
HTMLMediaElement::NotifyXPCOMShutdown()
{
ShutdownDecoder();
}
void
HTMLMediaElement::Play(ErrorResult& aRv)
{
+ if (!IsAllowedToPlay()) {
+ return;
+ }
+
nsresult rv = PlayInternal();
if (NS_FAILED(rv)) {
aRv.Throw(rv);
}
}
nsresult
HTMLMediaElement::PlayInternal()
{
- if (!IsAllowedToPlay()) {
- return NS_OK;
- }
-
// Play was not blocked so assume user interacted with the element.
mHasUserInteraction = true;
StopSuspendingAfterFirstFrame();
SetPlayedOrSeeked(true);
if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
DoLoad();
@@ -3085,16 +3085,20 @@ HTMLMediaElement::PlayInternal()
}
}
return NS_OK;
}
NS_IMETHODIMP HTMLMediaElement::Play()
{
+ if (!IsAllowedToPlay()) {
+ return NS_OK;
+ }
+
return PlayInternal();
}
HTMLMediaElement::WakeLockBoolWrapper&
HTMLMediaElement::WakeLockBoolWrapper::operator=(bool val)
{
if (mValue == val) {
return *this;
@@ -4941,16 +4945,20 @@ bool HTMLMediaElement::CanActivateAutopl
if (!mPaused) {
return false;
}
if (mPausedForInactiveDocumentOrChannel) {
return false;
}
+ if (!IsAllowedToPlay()) {
+ return false;
+ }
+
bool hasData =
(mDecoder && mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) ||
mSrcStream || mMediaSource;
return hasData;
}
void HTMLMediaElement::CheckAutoplayDataReady()
@@ -4965,19 +4973,17 @@ void HTMLMediaElement::CheckAutoplayData
UpdateSrcMediaStreamPlaying();
UpdateAudioChannelPlayingState();
if (mDecoder) {
SetPlayedOrSeeked(true);
if (mCurrentPlayRangeStart == -1.0) {
mCurrentPlayRangeStart = CurrentTime();
}
- if (!ShouldElementBePaused()) {
- mDecoder->Play();
- }
+ mDecoder->Play();
} else if (mSrcStream) {
SetPlayedOrSeeked(true);
}
// For blocked media, the event would be pending until it is resumed.
DispatchAsyncEvent(NS_LITERAL_STRING("play"));
DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
@@ -5769,19 +5775,20 @@ HTMLMediaElement::IsPlayingThroughTheAud
if (mSrcAttrStream) {
return true;
}
return false;
}
void
-HTMLMediaElement::UpdateAudioChannelPlayingState()
-{
- bool playingThroughTheAudioChannel = IsPlayingThroughTheAudioChannel();
+HTMLMediaElement::UpdateAudioChannelPlayingState(bool aForcePlaying)
+{
+ bool playingThroughTheAudioChannel =
+ aForcePlaying || IsPlayingThroughTheAudioChannel();
if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
}
}
void
@@ -5889,31 +5896,33 @@ HTMLMediaElement::ResumeFromAudioChannel
void
HTMLMediaElement::ResumeFromAudioChannelPaused(SuspendTypes aSuspend)
{
MOZ_ASSERT(mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE);
SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
- nsresult rv = PlayInternal();
+ nsresult rv = Play();
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptend"));
}
void
HTMLMediaElement::ResumeFromAudioChannelBlocked()
{
MOZ_ASSERT(mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_BLOCK);
SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
- mPaused = false;
- SuspendOrResumeElement(false /* resume */, false);
+ nsresult rv = Play();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
}
void
HTMLMediaElement::PauseByAudioChannel(SuspendTypes aSuspend)
{
if (IsSuspendedByAudioChannel()) {
return;
}
@@ -5926,18 +5935,16 @@ HTMLMediaElement::PauseByAudioChannel(Su
void
HTMLMediaElement::BlockByAudioChannel()
{
if (IsSuspendedByAudioChannel()) {
return;
}
SetAudioChannelSuspended(nsISuspendedTypes::SUSPENDED_BLOCK);
- mPaused = true;
- SuspendOrResumeElement(true /* suspend */, true /* pending event */);
}
void
HTMLMediaElement::SetAudioChannelSuspended(SuspendTypes aSuspend)
{
if (mAudioChannelSuspended == aSuspend) {
return;
}
@@ -5974,25 +5981,42 @@ HTMLMediaElement::IsAllowedToPlay()
static_cast<nsIContent*>(this),
NS_LITERAL_STRING("MozAutoplayMediaBlocked"),
false,
false);
#endif
return false;
}
- // The MediaElement can't start playback until it's resumed by audio channel.
+ // The media element has already been paused or blocked, so it can't start
+ // playback again by script or user's intend until resuming by audio channel.
if (mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_BLOCK) {
return false;
}
+ // If the tab hasn't been activated yet, the media element in that tab can't
+ // be playback now until the tab goes to foreground first time or user clicks
+ // the unblocking tab icon.
+ if (!IsTabActivated()) {
+ // Even we haven't start playing yet, we still need to notify the audio
+ // channe system because we need to receive the resume notification later.
+ UpdateAudioChannelPlayingState(true /* force to start */);
+ return false;
+ }
+
return true;
}
+bool
+HTMLMediaElement::IsTabActivated() const
+{
+ return !mAudioChannelAgent->ShouldBlockMedia();
+}
+
static const char* VisibilityString(Visibility aVisibility) {
switch(aVisibility) {
case Visibility::UNTRACKED: {
return "UNTRACKED";
}
case Visibility::APPROXIMATELY_NONVISIBLE: {
return "APPROXIMATELY_NONVISIBLE";
}
@@ -6494,21 +6518,16 @@ HTMLMediaElement::OpenUnsupportedMediaWi
NS_LITERAL_STRING("OpenMediaWithExternalApp"),
true,
true);
}
bool
HTMLMediaElement::ShouldElementBePaused()
{
- // The media in the non-visited page would be blocked.
- if (mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_BLOCK) {
- return true;
- }
-
// Bfcached page or inactive document.
if (!IsActive()) {
return true;
}
return false;
}
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1200,17 +1200,17 @@ protected:
// desired, and we'll seek to the sync point (keyframe and/or start of the
// next block of audio samples) preceeding seek target.
already_AddRefed<Promise> Seek(double aTime, SeekTarget::Type aSeekType, ErrorResult& aRv);
// A method to check if we are playing through the AudioChannel.
bool IsPlayingThroughTheAudioChannel() const;
// Update the audio channel playing state
- void UpdateAudioChannelPlayingState();
+ void UpdateAudioChannelPlayingState(bool aForcePlaying = false);
// Adds to the element's list of pending text tracks each text track
// in the element's list of text tracks whose text track mode is not disabled
// and whose text track readiness state is loading.
void PopulatePendingTextTrackList();
// Gets a reference to the MediaElement's TextTrackManager. If the
// MediaElement doesn't yet have one then it will create it.
@@ -1257,18 +1257,23 @@ protected:
void ResumeFromAudioChannel();
void ResumeFromAudioChannelPaused(SuspendTypes aSuspend);
void ResumeFromAudioChannelBlocked();
bool IsSuspendedByAudioChannel() const;
void SetAudioChannelSuspended(SuspendTypes aSuspend);
+ // A method to check whether the media element is allowed to start playback.
bool IsAllowedToPlay();
+ // True if the tab which media element belongs to has been to foreground at
+ // least once or activated by manually clicking the unblocking tab icon.
+ bool IsTabActivated() const;
+
bool IsAudible() const;
bool HaveFailedWithSourceNotSupportedError() const;
void OpenUnsupportedMediaWithExtenalAppIfNeeded();
// It's used for fennec only, send the notification when the user resumes the
// media which was paused by media control.
void MaybeNotifyMediaResumed(SuspendTypes aSuspend);