Bug 1423241 - Move special media element captureStream handling of inactive stream out of MediaStreamGraph. r=jib
authorAndreas Pehrson <apehrson@mozilla.com>
Fri, 23 Nov 2018 14:59:56 +0000
changeset 447849 6eda96338365abb919e615df7fb8a20d5261efaa
parent 447848 f85343663811990b19cecb27b7c0beba0448309a
child 447850 24d6a327d6a2c8badd1082ab6791b217e7343ba4
push id110089
push userbtara@mozilla.com
push dateFri, 23 Nov 2018 21:42:24 +0000
treeherdermozilla-inbound@7e7bf210330b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjib
bugs1423241
milestone65.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 1423241 - Move special media element captureStream handling of inactive stream out of MediaStreamGraph. r=jib Differential Revision: https://phabricator.services.mozilla.com/D12264
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/DOMMediaStream.cpp
dom/media/DOMMediaStream.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -464,16 +464,270 @@ private:
   nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
 
   // These fields may only be accessed on the MSG's appending thread.
   // (this is a direct listener so we get called by whoever is producing
   // this track's data)
   bool mInitialSizeFound;
 };
 
+class HTMLMediaElement::StreamCaptureTrackSource
+  : public MediaStreamTrackSource
+  , public MediaStreamTrackSource::Sink
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StreamCaptureTrackSource,
+                                           MediaStreamTrackSource)
+
+  StreamCaptureTrackSource(HTMLMediaElement* aElement,
+                           MediaStreamTrackSource* aCapturedTrackSource,
+                           DOMMediaStream* aOwningStream,
+                           TrackID aDestinationTrackID)
+    : MediaStreamTrackSource(aCapturedTrackSource->GetPrincipal(), nsString())
+    , mElement(aElement)
+    , mCapturedTrackSource(aCapturedTrackSource)
+    , mOwningStream(aOwningStream)
+    , mDestinationTrackID(aDestinationTrackID)
+  {
+    MOZ_ASSERT(mElement);
+    MOZ_ASSERT(mCapturedTrackSource);
+    MOZ_ASSERT(mOwningStream);
+    MOZ_ASSERT(IsTrackIDExplicit(mDestinationTrackID));
+
+    mCapturedTrackSource->RegisterSink(this);
+  }
+
+  void Destroy() override
+  {
+    if (mCapturedTrackSource) {
+      mCapturedTrackSource->UnregisterSink(this);
+      mCapturedTrackSource = nullptr;
+    }
+  }
+
+  MediaSourceEnum GetMediaSource() const override
+  {
+    return MediaSourceEnum::Other;
+  }
+
+  CORSMode GetCORSMode() const override
+  {
+    if (!mCapturedTrackSource) {
+      // This could happen during shutdown.
+      return CORS_NONE;
+    }
+
+    return mCapturedTrackSource->GetCORSMode();
+  }
+
+  void Stop() override
+  {
+    if (mElement && mElement->mSrcStream) {
+      // Only notify if we're still playing the source stream. GC might have
+      // cleared it before the track sources.
+      mElement->NotifyOutputTrackStopped(mOwningStream, mDestinationTrackID);
+    }
+    mElement = nullptr;
+    mOwningStream = nullptr;
+
+    Destroy();
+  }
+
+  /**
+   * Do not keep the track source alive. The source lifetime is controlled by
+   * its associated tracks.
+   */
+  bool KeepsSourceAlive() const override { return false; }
+
+  /**
+   * Do not keep the track source on. It is controlled by its associated tracks.
+   */
+  bool Enabled() const override { return false; }
+
+  void Disable() override {}
+
+  void Enable() override {}
+
+  void PrincipalChanged() override
+  {
+    if (!mCapturedTrackSource) {
+      // This could happen during shutdown.
+      return;
+    }
+
+    mPrincipal = mCapturedTrackSource->GetPrincipal();
+    MediaStreamTrackSource::PrincipalChanged();
+  }
+
+  void MutedChanged(bool aNewState) override
+  {
+    if (!mCapturedTrackSource) {
+      // This could happen during shutdown.
+      return;
+    }
+
+    MediaStreamTrackSource::MutedChanged(aNewState);
+  }
+
+private:
+  virtual ~StreamCaptureTrackSource() = default;
+
+  RefPtr<HTMLMediaElement> mElement;
+  RefPtr<MediaStreamTrackSource> mCapturedTrackSource;
+  RefPtr<DOMMediaStream> mOwningStream;
+  TrackID mDestinationTrackID;
+};
+
+NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
+                         MediaStreamTrackSource)
+NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
+                          MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+  HTMLMediaElement::StreamCaptureTrackSource)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
+                                   MediaStreamTrackSource,
+                                   mElement,
+                                   mCapturedTrackSource,
+                                   mOwningStream)
+
+class HTMLMediaElement::DecoderCaptureTrackSource
+  : public MediaStreamTrackSource
+  , public DecoderPrincipalChangeObserver
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecoderCaptureTrackSource,
+                                           MediaStreamTrackSource)
+
+  explicit DecoderCaptureTrackSource(HTMLMediaElement* aElement)
+    : MediaStreamTrackSource(
+        nsCOMPtr<nsIPrincipal>(aElement->GetCurrentPrincipal()).get(),
+        nsString())
+    , mElement(aElement)
+  {
+    MOZ_ASSERT(mElement);
+    mElement->AddDecoderPrincipalChangeObserver(this);
+  }
+
+  void Destroy() override
+  {
+    if (mElement) {
+      DebugOnly<bool> res =
+        mElement->RemoveDecoderPrincipalChangeObserver(this);
+      NS_ASSERTION(res,
+                   "Removing decoder principal changed observer failed. "
+                   "Had it already been removed?");
+      mElement = nullptr;
+    }
+  }
+
+  MediaSourceEnum GetMediaSource() const override
+  {
+    return MediaSourceEnum::Other;
+  }
+
+  CORSMode GetCORSMode() const override
+  {
+    if (!mElement) {
+      MOZ_ASSERT(false, "Should always have an element if in use");
+      return CORS_NONE;
+    }
+
+    return mElement->GetCORSMode();
+  }
+
+  void Stop() override
+  {
+    // We don't notify the source that a track was stopped since it will keep
+    // producing tracks until the element ends. The decoder also needs the
+    // tracks it created to be live at the source since the decoder's clock is
+    // based on MediaStreams during capture.
+  }
+
+  void Disable() override {}
+
+  void Enable() override {}
+
+  void NotifyDecoderPrincipalChanged() override
+  {
+    nsCOMPtr<nsIPrincipal> newPrincipal = mElement->GetCurrentPrincipal();
+    if (nsContentUtils::CombineResourcePrincipals(&mPrincipal, newPrincipal)) {
+      PrincipalChanged();
+    }
+  }
+
+protected:
+  virtual ~DecoderCaptureTrackSource() = default;
+
+  RefPtr<HTMLMediaElement> mElement;
+};
+
+NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource,
+                         MediaStreamTrackSource)
+NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource,
+                          MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+  HTMLMediaElement::DecoderCaptureTrackSource)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource,
+                                   MediaStreamTrackSource,
+                                   mElement)
+
+class HTMLMediaElement::CaptureStreamTrackSourceGetter
+  : public MediaStreamTrackSourceGetter
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CaptureStreamTrackSourceGetter,
+                                           MediaStreamTrackSourceGetter)
+
+  explicit CaptureStreamTrackSourceGetter(HTMLMediaElement* aElement)
+    : MediaStreamTrackSourceGetter(false)
+    , mElement(aElement)
+  {
+  }
+
+  already_AddRefed<dom::MediaStreamTrackSource> GetMediaStreamTrackSource(
+    TrackID aInputTrackID) override
+  {
+    if (mElement && mElement->mSrcStream) {
+      NS_ERROR("Captured media element playing a stream adds tracks explicitly "
+               "on main thread.");
+      return nullptr;
+    }
+
+    // We can return a new source each time here, even for different streams,
+    // since the sources don't keep any internal state and all of them call
+    // through to the same HTMLMediaElement.
+    // If this changes (after implementing Stop()?) we'll have to ensure we
+    // return the same source for all requests to the same TrackID, and only
+    // have one getter.
+    return do_AddRef(new DecoderCaptureTrackSource(mElement));
+  }
+
+protected:
+  virtual ~CaptureStreamTrackSourceGetter() = default;
+
+  RefPtr<HTMLMediaElement> mElement;
+};
+
+NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter,
+                         MediaStreamTrackSourceGetter)
+NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter,
+                          MediaStreamTrackSourceGetter)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+  HTMLMediaElement::CaptureStreamTrackSourceGetter)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSourceGetter)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(
+  HTMLMediaElement::CaptureStreamTrackSourceGetter,
+  MediaStreamTrackSourceGetter,
+  mElement)
+
 /**
  * There is a reference cycle involving this class: MediaLoadListener
  * holds a reference to the HTMLMediaElement, which holds a reference
  * to an nsIChannel, which holds a reference to this listener.
  * We break the reference cycle in OnStartRequest by clearing mElement.
  */
 class HTMLMediaElement::MediaLoadListener final
   : public nsIStreamListener
@@ -1464,16 +1718,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcAttrStream)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourcePointer)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadBlockedDoc)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelWrapper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mErrorSink->mError)
   for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams[i].mStream)
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams[i].mTrackSourceGetter)
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams[i].mPreCreatedTracks)
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingMediaKeys)
@@ -1497,16 +1752,19 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
   if (tmp->mAudioChannelWrapper) {
     tmp->mAudioChannelWrapper->Shutdown();
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelWrapper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mErrorSink->mError)
+  for (OutputMediaStream& s : tmp->mOutputStreams) {
+    s.mTrackSourceGetter->FinishOnNextInactive(s.mStream);
+  }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingMediaKeys)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
@@ -3115,269 +3373,16 @@ HTMLMediaElement::SetMuted(bool aMuted)
 
   DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
 
   // We allow inaudible autoplay. But changing our mute status may make this
   // media audible. So pause if we are no longer supposed to be autoplaying.
   PauseIfShouldNotBePlaying();
 }
 
-class HTMLMediaElement::StreamCaptureTrackSource
-  : public MediaStreamTrackSource
-  , public MediaStreamTrackSource::Sink
-{
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StreamCaptureTrackSource,
-                                           MediaStreamTrackSource)
-
-  StreamCaptureTrackSource(HTMLMediaElement* aElement,
-                           MediaStreamTrackSource* aCapturedTrackSource,
-                           DOMMediaStream* aOwningStream,
-                           TrackID aDestinationTrackID)
-    : MediaStreamTrackSource(aCapturedTrackSource->GetPrincipal(), nsString())
-    , mElement(aElement)
-    , mCapturedTrackSource(aCapturedTrackSource)
-    , mOwningStream(aOwningStream)
-    , mDestinationTrackID(aDestinationTrackID)
-  {
-    MOZ_ASSERT(mElement);
-    MOZ_ASSERT(mCapturedTrackSource);
-    MOZ_ASSERT(mOwningStream);
-    MOZ_ASSERT(IsTrackIDExplicit(mDestinationTrackID));
-
-    mCapturedTrackSource->RegisterSink(this);
-  }
-
-  void Destroy() override
-  {
-    if (mCapturedTrackSource) {
-      mCapturedTrackSource->UnregisterSink(this);
-      mCapturedTrackSource = nullptr;
-    }
-  }
-
-  MediaSourceEnum GetMediaSource() const override
-  {
-    return MediaSourceEnum::Other;
-  }
-
-  CORSMode GetCORSMode() const override
-  {
-    if (!mCapturedTrackSource) {
-      // This could happen during shutdown.
-      return CORS_NONE;
-    }
-
-    return mCapturedTrackSource->GetCORSMode();
-  }
-
-  void Stop() override
-  {
-    if (mElement && mElement->mSrcStream) {
-      // Only notify if we're still playing the source stream. GC might have
-      // cleared it before the track sources.
-      mElement->NotifyOutputTrackStopped(mOwningStream, mDestinationTrackID);
-    }
-    mElement = nullptr;
-    mOwningStream = nullptr;
-
-    Destroy();
-  }
-
-  /**
-   * Do not keep the track source alive. The source lifetime is controlled by
-   * its associated tracks.
-   */
-  bool KeepsSourceAlive() const override { return false; }
-
-  /**
-   * Do not keep the track source on. It is controlled by its associated tracks.
-   */
-  bool Enabled() const override { return false; }
-
-  void Disable() override {}
-
-  void Enable() override {}
-
-  void PrincipalChanged() override
-  {
-    if (!mCapturedTrackSource) {
-      // This could happen during shutdown.
-      return;
-    }
-
-    mPrincipal = mCapturedTrackSource->GetPrincipal();
-    MediaStreamTrackSource::PrincipalChanged();
-  }
-
-  void MutedChanged(bool aNewState) override
-  {
-    if (!mCapturedTrackSource) {
-      // This could happen during shutdown.
-      return;
-    }
-
-    MediaStreamTrackSource::MutedChanged(aNewState);
-  }
-
-private:
-  virtual ~StreamCaptureTrackSource() {}
-
-  RefPtr<HTMLMediaElement> mElement;
-  RefPtr<MediaStreamTrackSource> mCapturedTrackSource;
-  RefPtr<DOMMediaStream> mOwningStream;
-  TrackID mDestinationTrackID;
-};
-
-NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
-                         MediaStreamTrackSource)
-NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
-                          MediaStreamTrackSource)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
-  HTMLMediaElement::StreamCaptureTrackSource)
-NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
-NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
-                                   MediaStreamTrackSource,
-                                   mElement,
-                                   mCapturedTrackSource,
-                                   mOwningStream)
-
-class HTMLMediaElement::DecoderCaptureTrackSource
-  : public MediaStreamTrackSource
-  , public DecoderPrincipalChangeObserver
-{
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecoderCaptureTrackSource,
-                                           MediaStreamTrackSource)
-
-  explicit DecoderCaptureTrackSource(HTMLMediaElement* aElement)
-    : MediaStreamTrackSource(
-        nsCOMPtr<nsIPrincipal>(aElement->GetCurrentPrincipal()).get(),
-        nsString())
-    , mElement(aElement)
-  {
-    MOZ_ASSERT(mElement);
-    mElement->AddDecoderPrincipalChangeObserver(this);
-  }
-
-  void Destroy() override
-  {
-    if (mElement) {
-      DebugOnly<bool> res =
-        mElement->RemoveDecoderPrincipalChangeObserver(this);
-      NS_ASSERTION(res,
-                   "Removing decoder principal changed observer failed. "
-                   "Had it already been removed?");
-      mElement = nullptr;
-    }
-  }
-
-  MediaSourceEnum GetMediaSource() const override
-  {
-    return MediaSourceEnum::Other;
-  }
-
-  CORSMode GetCORSMode() const override
-  {
-    if (!mElement) {
-      MOZ_ASSERT(false, "Should always have an element if in use");
-      return CORS_NONE;
-    }
-
-    return mElement->GetCORSMode();
-  }
-
-  void Stop() override
-  {
-    // We don't notify the source that a track was stopped since it will keep
-    // producing tracks until the element ends. The decoder also needs the
-    // tracks it created to be live at the source since the decoder's clock is
-    // based on MediaStreams during capture.
-  }
-
-  void Disable() override {}
-
-  void Enable() override {}
-
-  void NotifyDecoderPrincipalChanged() override
-  {
-    nsCOMPtr<nsIPrincipal> newPrincipal = mElement->GetCurrentPrincipal();
-    if (nsContentUtils::CombineResourcePrincipals(&mPrincipal, newPrincipal)) {
-      PrincipalChanged();
-    }
-  }
-
-protected:
-  virtual ~DecoderCaptureTrackSource() {}
-
-  RefPtr<HTMLMediaElement> mElement;
-};
-
-NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource,
-                         MediaStreamTrackSource)
-NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource,
-                          MediaStreamTrackSource)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
-  HTMLMediaElement::DecoderCaptureTrackSource)
-NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
-NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource,
-                                   MediaStreamTrackSource,
-                                   mElement)
-
-class HTMLMediaElement::CaptureStreamTrackSourceGetter
-  : public MediaStreamTrackSourceGetter
-{
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CaptureStreamTrackSourceGetter,
-                                           MediaStreamTrackSourceGetter)
-
-  explicit CaptureStreamTrackSourceGetter(HTMLMediaElement* aElement)
-    : mElement(aElement)
-  {
-  }
-
-  already_AddRefed<dom::MediaStreamTrackSource> GetMediaStreamTrackSource(
-    TrackID aInputTrackID) override
-  {
-    if (mElement && mElement->mSrcStream) {
-      NS_ERROR("Captured media element playing a stream adds tracks explicitly "
-               "on main thread.");
-      return nullptr;
-    }
-
-    // We can return a new source each time here, even for different streams,
-    // since the sources don't keep any internal state and all of them call
-    // through to the same HTMLMediaElement.
-    // If this changes (after implementing Stop()?) we'll have to ensure we
-    // return the same source for all requests to the same TrackID, and only
-    // have one getter.
-    return do_AddRef(new DecoderCaptureTrackSource(mElement));
-  }
-
-protected:
-  virtual ~CaptureStreamTrackSourceGetter() {}
-
-  RefPtr<HTMLMediaElement> mElement;
-};
-
-NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter,
-                         MediaStreamTrackSourceGetter)
-NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter,
-                          MediaStreamTrackSourceGetter)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
-  HTMLMediaElement::CaptureStreamTrackSourceGetter)
-NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSourceGetter)
-NS_IMPL_CYCLE_COLLECTION_INHERITED(
-  HTMLMediaElement::CaptureStreamTrackSourceGetter,
-  MediaStreamTrackSourceGetter,
-  mElement)
-
 void
 HTMLMediaElement::SetCapturedOutputStreamsEnabled(bool aEnabled)
 {
   for (OutputMediaStream& ms : mOutputStreams) {
     if (ms.mCapturingDecoder) {
       MOZ_ASSERT(!ms.mCapturingMediaStream);
       continue;
     }
@@ -3529,22 +3534,21 @@ HTMLMediaElement::CaptureStreamInternal(
 
   // We don't support routing to a different graph.
   if (!mOutputStreams.IsEmpty() &&
       aGraph != mOutputStreams[0].mStream->GetInputStream()->Graph()) {
     return nullptr;
   }
 
   OutputMediaStream* out = mOutputStreams.AppendElement();
-  MediaStreamTrackSourceGetter* getter =
+  out->mTrackSourceGetter =
     new CaptureStreamTrackSourceGetter(this);
   nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
-  out->mStream =
-    DOMMediaStream::CreateTrackUnionStreamAsInput(window, aGraph, getter);
-  out->mStream->SetInactiveOnFinish();
+  out->mStream = DOMMediaStream::CreateTrackUnionStreamAsInput(
+    window, aGraph, out->mTrackSourceGetter);
   out->mFinishWhenEnded =
     aFinishBehavior == StreamCaptureBehavior::FINISH_WHEN_ENDED;
   out->mCapturingAudioOnly =
     aStreamCaptureType == StreamCaptureType::CAPTURE_AUDIO;
 
   if (aStreamCaptureType == StreamCaptureType::CAPTURE_AUDIO) {
     if (mSrcStream) {
       // We don't support applying volume and mute to the captured stream, when
@@ -3579,28 +3583,28 @@ HTMLMediaElement::CaptureStreamInternal(
     RefPtr<DOMMediaStream> result = out->mStream;
     return result.forget();
   }
 
   if (mDecoder) {
     if (HasAudio()) {
       TrackID audioTrackId = out->mNextAvailableTrackID++;
       RefPtr<MediaStreamTrackSource> trackSource =
-        getter->GetMediaStreamTrackSource(audioTrackId);
+        out->mTrackSourceGetter->GetMediaStreamTrackSource(audioTrackId);
       RefPtr<MediaStreamTrack> track = out->mStream->CreateDOMTrack(
         audioTrackId, MediaSegment::AUDIO, trackSource);
       out->mPreCreatedTracks.AppendElement(track);
       out->mStream->AddTrackInternal(track);
       LOG(LogLevel::Debug,
           ("Created audio track %d for captured decoder", audioTrackId));
     }
     if (IsVideo() && HasVideo() && !out->mCapturingAudioOnly) {
       TrackID videoTrackId = out->mNextAvailableTrackID++;
       RefPtr<MediaStreamTrackSource> trackSource =
-        getter->GetMediaStreamTrackSource(videoTrackId);
+        out->mTrackSourceGetter->GetMediaStreamTrackSource(videoTrackId);
       RefPtr<MediaStreamTrack> track = out->mStream->CreateDOMTrack(
         videoTrackId, MediaSegment::VIDEO, trackSource);
       out->mPreCreatedTracks.AppendElement(track);
       out->mStream->AddTrackInternal(track);
       LOG(LogLevel::Debug,
           ("Created video track %d for captured decoder", videoTrackId));
     }
   }
@@ -5782,16 +5786,18 @@ HTMLMediaElement::PlaybackEnded()
                "Decoder fired ended, but not in ended state");
 
   // Discard all output streams that have finished now.
   for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
     if (mOutputStreams[i].mFinishWhenEnded) {
       LOG(LogLevel::Debug,
           ("Playback ended. Removing output stream %p",
            mOutputStreams[i].mStream.get()));
+      mOutputStreams[i].mTrackSourceGetter->FinishOnNextInactive(
+        mOutputStreams[i].mStream);
       mOutputStreams.RemoveElementAt(i);
     }
   }
 
   if (mSrcStream) {
     LOG(LogLevel::Debug,
         ("%p, got duration by reaching the end of the resource", this));
     DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
@@ -7829,16 +7835,18 @@ HTMLMediaElement::AudioCaptureStreamChan
   } else if (!aCapture && mCaptureStreamPort) {
     if (mDecoder) {
       ProcessedMediaStream* ps =
         mCaptureStreamPort->GetSource()->AsProcessedStream();
       MOZ_ASSERT(ps);
 
       for (uint32_t i = 0; i < mOutputStreams.Length(); i++) {
         if (mOutputStreams[i].mStream->GetPlaybackStream() == ps) {
+          mOutputStreams[i].mTrackSourceGetter->FinishOnNextInactive(
+            mOutputStreams[i].mStream);
           mOutputStreams.RemoveElementAt(i);
           break;
         }
       }
       mDecoder->RemoveOutputStream(ps);
     }
     mCaptureStreamPort->Destroy();
     mCaptureStreamPort = nullptr;
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -864,16 +864,18 @@ protected:
 
     RefPtr<DOMMediaStream> mStream;
     TrackID mNextAvailableTrackID;
     bool mFinishWhenEnded;
     bool mCapturingAudioOnly;
     bool mCapturingDecoder;
     bool mCapturingMediaStream;
 
+    RefPtr<CaptureStreamTrackSourceGetter> mTrackSourceGetter;
+
     // The following members are keeping state for a captured MediaDecoder.
     // Tracks that were created on main thread before MediaDecoder fed them
     // to the MediaStreamGraph.
     nsTArray<RefPtr<MediaStreamTrack>> mPreCreatedTracks;
 
     // The following members are keeping state for a captured MediaStream.
     nsTArray<Pair<nsString, RefPtr<MediaInputPort>>> mTrackPorts;
   };
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -52,16 +52,28 @@ static bool ContainsLiveTracks(
     if (port->GetTrack()->ReadyState() == MediaStreamTrackState::Live) {
       return true;
     }
   }
 
   return false;
 }
 
+void MediaStreamTrackSourceGetter::FinishOnNextInactive(
+    RefPtr<DOMMediaStream>& aStream) {
+  if (mFinishedOnInactive) {
+    return;
+  }
+
+  mFinishedOnInactive = true;
+
+  // We notify now with a dummy track in case there are no live tracks.
+  aStream->NotifyTrackRemoved(nullptr);
+}
+
 DOMMediaStream::TrackPort::TrackPort(MediaInputPort* aInputPort,
                                      MediaStreamTrack* aTrack,
                                      const InputPortOwnership aOwnership)
     : mInputPort(aInputPort), mTrack(aTrack), mOwnership(aOwnership) {
   MOZ_ASSERT(mInputPort);
   MOZ_ASSERT(mTrack);
 
   MOZ_COUNT_CTOR(TrackPort);
@@ -244,45 +256,24 @@ class DOMMediaStream::PlaybackStreamList
     // dispatch. We have to do the same to notify of created tracks to stay
     // in sync. (Or NotifyTracksCreated is called before tracks are added).
     MOZ_ASSERT(mStream->GetPlaybackStream());
     mStream->GetPlaybackStream()->Graph()->AbstractMainThread()->Dispatch(
         NewRunnableMethod("DOMMediaStream::NotifyTracksCreated", mStream,
                           &DOMMediaStream::NotifyTracksCreated));
   }
 
-  void DoNotifyFinished() {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!mStream) {
-      return;
-    }
-
-    mStream->GetPlaybackStream()->Graph()->AbstractMainThread()->Dispatch(
-        NewRunnableMethod("DOMMediaStream::NotifyFinished", mStream,
-                          &DOMMediaStream::NotifyFinished));
-  }
-
   // The methods below are called on the MediaStreamGraph thread.
 
   void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override {
     aGraph->DispatchToMainThreadAfterStreamStateUpdate(NewRunnableMethod(
         "DOMMediaStream::PlaybackStreamListener::DoNotifyFinishedTrackCreation",
         this, &PlaybackStreamListener::DoNotifyFinishedTrackCreation));
   }
 
-  void NotifyEvent(MediaStreamGraph* aGraph,
-                   MediaStreamGraphEvent event) override {
-    if (event == MediaStreamGraphEvent::EVENT_FINISHED) {
-      aGraph->DispatchToMainThreadAfterStreamStateUpdate(NewRunnableMethod(
-          "DOMMediaStream::PlaybackStreamListener::DoNotifyFinished", this,
-          &PlaybackStreamListener::DoNotifyFinished));
-    }
-  }
-
  private:
   // These fields may only be accessed on the main thread
   DOMMediaStream* mStream;
 };
 
 class DOMMediaStream::PlaybackTrackListener : public MediaStreamTrackConsumer {
  public:
   explicit PlaybackTrackListener(DOMMediaStream* aStream) : mStream(aStream) {}
@@ -367,17 +358,16 @@ DOMMediaStream::DOMMediaStream(nsPIDOMWi
       mOwnedStream(nullptr),
       mPlaybackStream(nullptr),
       mTracksPendingRemoval(0),
       mTrackSourceGetter(aTrackSourceGetter),
       mPlaybackTrackListener(MakeAndAddRef<PlaybackTrackListener>(this)),
       mTracksCreated(false),
       mNotifiedOfMediaStreamGraphShutdown(false),
       mActive(false),
-      mSetInactiveOnFinish(false),
       mCORSMode(CORS_NONE) {
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
       do_GetService("@mozilla.org/uuid-generator;1", &rv);
 
   if (NS_SUCCEEDED(rv) && uuidgen) {
     nsID uuid;
     memset(&uuid, 0, sizeof(uuid));
@@ -831,18 +821,16 @@ TrackRate DOMMediaStream::GraphRate() {
   if (mInputStream) {
     return mInputStream->GraphRate();
   }
 
   MOZ_ASSERT(false, "Not hooked up to a graph");
   return 0;
 }
 
-void DOMMediaStream::SetInactiveOnFinish() { mSetInactiveOnFinish = true; }
-
 void DOMMediaStream::InitSourceStream(MediaStreamGraph* aGraph) {
   InitInputStreamCommon(aGraph->CreateSourceStream(), aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
 }
 
 void DOMMediaStream::InitTrackUnionStream(MediaStreamGraph* aGraph) {
   InitInputStreamCommon(aGraph->CreateTrackUnionStream(), aGraph);
@@ -1185,31 +1173,16 @@ void DOMMediaStream::OnTracksAvailable(O
   CheckTracksAvailable();
 }
 
 void DOMMediaStream::NotifyTracksCreated() {
   mTracksCreated = true;
   CheckTracksAvailable();
 }
 
-void DOMMediaStream::NotifyFinished() {
-  if (!mSetInactiveOnFinish) {
-    return;
-  }
-
-  if (!mActive) {
-    // This can happen if the stream never became active.
-    return;
-  }
-
-  MOZ_ASSERT(!ContainsLiveTracks(mTracks));
-  mActive = false;
-  NotifyInactive();
-}
-
 void DOMMediaStream::NotifyActive() {
   LOG(LogLevel::Info, ("DOMMediaStream %p NotifyActive(). ", this));
 
   MOZ_ASSERT(mActive);
   for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
     mTrackListeners[i]->NotifyActive();
   }
 }
@@ -1292,35 +1265,43 @@ void DOMMediaStream::NotifyTrackAdded(co
     NotifyActive();
   }
 }
 
 void DOMMediaStream::NotifyTrackRemoved(
     const RefPtr<MediaStreamTrack>& aTrack) {
   MOZ_ASSERT(NS_IsMainThread());
 
-  aTrack->RemoveConsumer(mPlaybackTrackListener);
-  aTrack->RemovePrincipalChangeObserver(this);
+  if (aTrack) {
+    // aTrack may be null to allow HTMLMediaElement::MozCaptureStream streams
+    // to be played until the source media element has ended. The source media
+    // element will then call NotifyTrackRemoved(nullptr) to signal that we can
+    // go inactive, regardless of the timing of the last track ending.
+
+    aTrack->RemoveConsumer(mPlaybackTrackListener);
+    aTrack->RemovePrincipalChangeObserver(this);
 
-  for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
-    mTrackListeners[i]->NotifyTrackRemoved(aTrack);
+    for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
+      mTrackListeners[i]->NotifyTrackRemoved(aTrack);
+    }
+
+    // Don't call RecomputePrincipal here as the track may still exist in the
+    // playback stream in the MediaStreamGraph. It will instead be called when
+    // the track has been confirmed removed by the graph. See
+    // BlockPlaybackTrack().
+
+    if (!mActive) {
+      NS_ASSERTION(false, "Shouldn't remove a live track if already inactive");
+      return;
+    }
   }
 
-  // Don't call RecomputePrincipal here as the track may still exist in the
-  // playback stream in the MediaStreamGraph. It will instead be called when the
-  // track has been confirmed removed by the graph. See BlockPlaybackTrack().
-
-  if (!mActive) {
-    NS_ASSERTION(false, "Shouldn't remove a live track if already inactive");
-    return;
-  }
-
-  if (mSetInactiveOnFinish) {
+  if (mTrackSourceGetter && !mTrackSourceGetter->FinishedOnInactive()) {
     // For compatibility with mozCaptureStream we in some cases do not go
-    // inactive until the playback stream finishes.
+    // inactive until the track source lets us.
     return;
   }
 
   // Check if we became inactive.
   if (!ContainsLiveTracks(mTracks)) {
     mActive = false;
     NotifyInactive();
   }
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -73,23 +73,40 @@ class OnTracksAvailableCallback {
  * DOMMediaStream's owned stream that has not yet been created on the main
  * thread (see DOMMediaStream::CreateOwnDOMTrack).
  */
 class MediaStreamTrackSourceGetter : public nsISupports {
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(MediaStreamTrackSourceGetter)
 
  public:
-  MediaStreamTrackSourceGetter() {}
+  explicit MediaStreamTrackSourceGetter(bool aFinishedOnInactive = true)
+      : mFinishedOnInactive(aFinishedOnInactive) {}
 
   virtual already_AddRefed<dom::MediaStreamTrackSource>
   GetMediaStreamTrackSource(TrackID aInputTrackID) = 0;
 
+  bool FinishedOnInactive() { return mFinishedOnInactive; }
+
+  /**
+   * Called by the source to signal to aStream that it should go inactive
+   * the next time there are no live tracks. This could be now if there are no
+   * live tracks currently.
+   *
+   * This is a temporary measure to allow HTMLMediaElement::MozCaptureStream
+   * to not end playback prematurely after stream.finished became stream.active.
+   * This will be removed in bug 1302379.
+   */
+  void FinishOnNextInactive(RefPtr<DOMMediaStream>& aStream);
+
  protected:
   virtual ~MediaStreamTrackSourceGetter() {}
+
+ private:
+  bool mFinishedOnInactive;
 };
 
 // clang-format off
 /**
  * DOM wrapper for MediaStreams.
  *
  * To account for track operations such as clone(), addTrack() and
  * removeTrack(), a DOMMediaStream wraps three internal (and chained)
@@ -196,16 +213,17 @@ class MediaStreamTrackSourceGetter : pub
  *                                                     (pointing to t2 in A')
  */
 // clang-format on
 class DOMMediaStream
     : public DOMEventTargetHelper,
       public dom::PrincipalChangeObserver<dom::MediaStreamTrack>,
       public RelativeTimeline {
   friend class dom::MediaStreamTrack;
+  friend class MediaStreamTrackSourceGetter;
   typedef dom::MediaStreamTrack MediaStreamTrack;
   typedef dom::AudioStreamTrack AudioStreamTrack;
   typedef dom::VideoStreamTrack VideoStreamTrack;
   typedef dom::MediaStreamTrackSource MediaStreamTrackSource;
   typedef dom::AudioTrack AudioTrack;
   typedef dom::VideoTrack VideoTrack;
   typedef dom::AudioTrackList AudioTrackList;
   typedef dom::VideoTrackList VideoTrackList;
@@ -430,21 +448,16 @@ class DOMMediaStream
    */
   virtual MediaStream* GetCameraStream() const { return nullptr; }
 
   /**
    * Legacy method that returns true when the playback stream has finished.
    */
   bool IsFinished() const;
 
-  /**
-   * Becomes inactive only when the playback stream has finished.
-   */
-  void SetInactiveOnFinish();
-
   TrackRate GraphRate();
 
   /**
    * Returns a principal indicating who may access this stream. The stream
    * contents can only be accessed by principals subsuming this principal.
    */
   nsIPrincipal* GetPrincipal() { return mPrincipal; }
 
@@ -588,19 +601,16 @@ class DOMMediaStream
   void InitPlaybackStreamCommon(MediaStreamGraph* aGraph);
 
   void CheckTracksAvailable();
 
   // Called when MediaStreamGraph has finished an iteration where tracks were
   // created.
   void NotifyTracksCreated();
 
-  // Called when our playback stream has finished in the MediaStreamGraph.
-  void NotifyFinished();
-
   // Dispatches NotifyActive() to all registered track listeners.
   void NotifyActive();
 
   // Dispatches NotifyInactive() to all registered track listeners.
   void NotifyInactive();
 
   // Dispatches NotifyTrackAdded() to all registered track listeners.
   void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack);
@@ -703,21 +713,16 @@ class DOMMediaStream
   bool mNotifiedOfMediaStreamGraphShutdown;
 
   // The track listeners subscribe to changes in this stream's track set.
   nsTArray<TrackListener*> mTrackListeners;
 
   // True if this stream has live tracks.
   bool mActive;
 
-  // True if this stream only sets mActive to false when its playback stream
-  // finishes. This is a hack to maintain legacy functionality for playing a
-  // HTMLMediaElement::MozCaptureStream(). See bug 1302379.
-  bool mSetInactiveOnFinish;
-
  private:
   void NotifyPrincipalChanged();
   // Principal identifying who may access the collected contents of this stream.
   // If null, this stream can be used by anyone because it has no content yet.
   nsCOMPtr<nsIPrincipal> mPrincipal;
   // Video principal is used by video element as access is requested to its
   // image data.
   nsCOMPtr<nsIPrincipal> mVideoPrincipal;