Bug 1588840 - Make ended return true after reaching the event loop's step 1 when playing a MediaStream. r=jib
authorAndreas Pehrson <apehrson@mozilla.com>
Wed, 06 Nov 2019 13:06:44 +0000
changeset 500844 0efd3fc716c415d777c2f6e91b936295ca2b3dcd
parent 500843 28ed0836d698a6168ca2a4b5b5ec09009b2e719e
child 500845 3eb9521efab18a64b5205bed524c9966fdc7f9fc
push id114166
push userapavel@mozilla.com
push dateThu, 07 Nov 2019 10:04:01 +0000
treeherdermozilla-inbound@d271c572a9bc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjib
bugs1588840
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1588840 - Make ended return true after reaching the event loop's step 1 when playing a MediaStream. r=jib This is especially important for media elements playing MediaStreams, since the streams can be manipulated to end playback of the elements synchronously by either stopping or removing their tracks. Differential Revision: https://phabricator.services.mozilla.com/D49385
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1805,17 +1805,17 @@ void HTMLMediaElement::SetSrcObject(DOMM
 void HTMLMediaElement::SetSrcObject(DOMMediaStream* aValue) {
   mSrcAttrStream = aValue;
   UpdateAudioChannelPlayingState();
   DoLoad();
 }
 
 bool HTMLMediaElement::Ended() {
   return (mDecoder && mDecoder->IsEnded()) ||
-         (mSrcStream && mSrcStreamPlaybackEnded);
+         (mSrcStream && mSrcStreamReportPlaybackEnded);
 }
 
 void HTMLMediaElement::GetCurrentSrc(nsAString& aCurrentSrc) {
   nsAutoCString src;
   GetCurrentSpec(src);
   CopyUTF8toUTF16(src, aCurrentSrc);
 }
 
@@ -4782,16 +4782,17 @@ class HTMLMediaElement::MediaStreamTrack
   void OnInactive() {
     MOZ_ASSERT(mElement);
 
     if (mElement->IsPlaybackEnded()) {
       return;
     }
     LOG(LogLevel::Debug, ("%p, mSrcStream %p became inactive", mElement.get(),
                           mElement->mSrcStream.get()));
+
     mElement->PlaybackEnded();
     mElement->UpdateReadyStateInternal();
   }
 
   void NotifyInactive() override {
     if (!mElement) {
       return;
     }
@@ -4834,16 +4835,17 @@ void HTMLMediaElement::UpdateSrcMediaStr
   mSrcStreamIsPlaying = shouldPlay;
 
   LOG(LogLevel::Debug,
       ("MediaElement %p %s playback of DOMMediaStream %p", this,
        shouldPlay ? "Setting up" : "Removing", mSrcStream.get()));
 
   if (shouldPlay) {
     mSrcStreamPlaybackEnded = false;
+    mSrcStreamReportPlaybackEnded = false;
 
     if (mMediaStreamRenderer) {
       mMediaStreamRenderer->Start();
     }
 
     if (mSink.second()) {
       NS_WARNING(
           "setSinkId() when playing a MediaStream is not supported yet and "
@@ -4910,16 +4912,18 @@ void HTMLMediaElement::SetupSrcMediaStre
   mMediaStreamRenderer = MakeAndAddRef<MediaStreamRenderer>(
       mAbstractMainThread, GetVideoFrameContainer(), this);
   mWatchManager.Watch(mPaused,
                       &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
   mWatchManager.Watch(mReadyState,
                       &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
   mWatchManager.Watch(mSrcStreamPlaybackEnded,
                       &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
+  mWatchManager.Watch(mSrcStreamPlaybackEnded,
+                      &HTMLMediaElement::UpdateSrcStreamReportPlaybackEnded);
   mWatchManager.Watch(mMediaStreamRenderer->CurrentGraphTime(),
                       &HTMLMediaElement::UpdateSrcStreamTime);
   SetVolumeInternal();
 
   UpdateSrcMediaStreamPlaying();
   UpdateSrcStreamPotentiallyPlaying();
   mSrcStreamVideoPrincipal = NodePrincipal();
 
@@ -4957,26 +4961,30 @@ void HTMLMediaElement::EndSrcMediaStream
 
   if (mMediaStreamRenderer) {
     mWatchManager.Unwatch(mPaused,
                           &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
     mWatchManager.Unwatch(mReadyState,
                           &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
     mWatchManager.Unwatch(mSrcStreamPlaybackEnded,
                           &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
+    mWatchManager.Unwatch(
+        mSrcStreamPlaybackEnded,
+        &HTMLMediaElement::UpdateSrcStreamReportPlaybackEnded);
     mWatchManager.Unwatch(mMediaStreamRenderer->CurrentGraphTime(),
                           &HTMLMediaElement::UpdateSrcStreamTime);
     mMediaStreamRenderer->Shutdown();
     mMediaStreamRenderer = nullptr;
   }
 
   mSrcStream->UnregisterTrackListener(mMediaStreamTrackListener.get());
   mMediaStreamTrackListener = nullptr;
   mSrcStreamTracksAvailable = false;
   mSrcStreamPlaybackEnded = false;
+  mSrcStreamReportPlaybackEnded = false;
   mSrcStreamVideoPrincipal = nullptr;
 
 #ifdef DEBUG
   for (OutputMediaStream& ms : mOutputStreams) {
     // These tracks were removed by clearing AudioTracks() and VideoTracks().
     MOZ_ASSERT(ms.mTracks.IsEmpty());
   }
 #endif
@@ -5299,16 +5307,20 @@ void HTMLMediaElement::PlaybackEnded() {
     // A MediaStream that goes from inactive to active shall be eligible for
     // autoplay again according to the mediacapture-main spec.
     mAutoplaying = true;
   }
 
   DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
 }
 
+void HTMLMediaElement::UpdateSrcStreamReportPlaybackEnded() {
+  mSrcStreamReportPlaybackEnded = mSrcStreamPlaybackEnded;
+}
+
 void HTMLMediaElement::SeekStarted() {
   DispatchAsyncEvent(NS_LITERAL_STRING("seeking"));
 }
 
 void HTMLMediaElement::SeekCompleted() {
   mPlayingBeforeSeek = false;
   SetPlayedOrSeeked(true);
   if (mTextTrackManager) {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -830,16 +830,24 @@ class HTMLMediaElement : public nsGeneri
 
   /**
    * mSrcStream's graph's CurrentTime() has been updated. It might be time to
    * fire "timeupdate".
    */
   void UpdateSrcStreamTime();
 
   /**
+   * Called after a tail dispatch when playback of mSrcStream ended, to comply
+   * with the spec where we must start reporting true for the ended attribute
+   * after the event loop returns to step 1. A MediaStream could otherwise be
+   * manipulated to end a HTMLMediaElement synchronously.
+   */
+  void UpdateSrcStreamReportPlaybackEnded();
+
+  /**
    * Called by our DOMMediaStream::TrackListener when a new MediaStreamTrack has
    * been added to the playback stream of |mSrcStream|.
    */
   void NotifyMediaStreamTrackAdded(const RefPtr<MediaStreamTrack>& aTrack);
 
   /**
    * Called by our DOMMediaStream::TrackListener when a MediaStreamTrack in
    * |mSrcStream|'s playback stream has ended.
@@ -1325,16 +1333,22 @@ class HTMLMediaElement : public nsGeneri
   // True once mSrcStream's initial set of tracks are known.
   bool mSrcStreamTracksAvailable = false;
 
   // True once PlaybackEnded() is called and we're playing a MediaStream.
   // Reset to false if we start playing mSrcStream again.
   Watchable<bool> mSrcStreamPlaybackEnded = {
       false, "HTMLMediaElement::mSrcStreamPlaybackEnded"};
 
+  // Mirrors mSrcStreamPlaybackEnded after a tail dispatch when set to true,
+  // but may be be forced to false directly. To accomodate when an application
+  // ends playback synchronously by manipulating mSrcStream or its tracks,
+  // e.g., through MediaStream.removeTrack(), or MediaStreamTrack.stop().
+  bool mSrcStreamReportPlaybackEnded = false;
+
   // Holds a reference to the stream connecting this stream to the window
   // capture sink.
   UniquePtr<MediaStreamWindowCapturer> mStreamWindowCapturer;
 
   // Holds references to the DOM wrappers for the MediaStreams that we're
   // writing to.
   nsTArray<OutputMediaStream> mOutputStreams;