Bug 1453127 - Ensure TrackID uniqueness for captured MediaDecoder. r=jya, a=RyanVM
authorAndreas Pehrson <pehrsons@mozilla.com>
Tue, 29 May 2018 10:21:51 +0200
changeset 473629 434582a805320135960bb8703486e8a196a011af
parent 473628 bfe53eb4206ee42d8df8fe2b91e78c494c1ab771
child 473630 5218dfec0630b746e33b3fb39553ee248a1fd879
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya, RyanVM
bugs1453127
milestone61.0
Bug 1453127 - Ensure TrackID uniqueness for captured MediaDecoder. r=jya, a=RyanVM
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/MediaDecoder.cpp
dom/media/MediaDecoder.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/mediasink/DecodedStream.cpp
dom/media/mediasink/OutputStreamManager.cpp
dom/media/mediasink/OutputStreamManager.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1730,16 +1730,24 @@ void HTMLMediaElement::ShutdownDecoder()
 {
   RemoveMediaElementFromURITable();
   NS_ASSERTION(mDecoder, "Must have decoder to shut down");
 
   mWaitingForKeyListener.DisconnectIfExists();
   if (mMediaSource) {
     mMediaSource->CompletePendingTransactions();
   }
+  for (OutputMediaStream& out : mOutputStreams) {
+    if (!out.mCapturingDecoder) {
+      continue;
+    }
+    out.mNextAvailableTrackID = std::max<TrackID>(
+      mDecoder->NextAvailableTrackIDFor(out.mStream->GetInputStream()),
+      out.mNextAvailableTrackID);
+  }
   mDecoder->Shutdown();
   DDUNLINKCHILD(mDecoder.get());
   mDecoder = nullptr;
 }
 
 void HTMLMediaElement::AbortExistingLoads()
 {
   // Abort any already-running instance of the resource selection algorithm.
@@ -3513,42 +3521,44 @@ HTMLMediaElement::CaptureStreamInternal(
     // is being routed to the captureStreams *instead* of being played to
     // speakers.
     mAudioCaptured = true;
   }
 
   if (mDecoder) {
     out->mCapturingDecoder = true;
     mDecoder->AddOutputStream(out->mStream->GetInputStream()->AsProcessedStream(),
+                              out->mNextAvailableTrackID,
                               aFinishBehavior == StreamCaptureBehavior::FINISH_WHEN_ENDED);
   } else if (mSrcStream) {
     out->mCapturingMediaStream = true;
   }
 
   if (mReadyState == HAVE_NOTHING) {
     // Do not expose the tracks until we have metadata.
     RefPtr<DOMMediaStream> result = out->mStream;
     return result.forget();
   }
 
   if (mDecoder) {
     if (HasAudio()) {
-      TrackID audioTrackId = mMediaInfo.mAudio.mTrackId;
+      TrackID audioTrackId = out->mNextAvailableTrackID++;
       RefPtr<MediaStreamTrackSource> trackSource =
         getter->GetMediaStreamTrackSource(audioTrackId);
       RefPtr<MediaStreamTrack> track =
-        out->mStream->CreateDOMTrack(audioTrackId, MediaSegment::AUDIO,
+        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 = mMediaInfo.mVideo.mTrackId;
+      TrackID videoTrackId = out->mNextAvailableTrackID++;
       RefPtr<MediaStreamTrackSource> trackSource =
         getter->GetMediaStreamTrackSource(videoTrackId);
       RefPtr<MediaStreamTrack> track =
         out->mStream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO,
                                      trackSource);
       out->mPreCreatedTracks.AppendElement(track);
       out->mStream->AddTrackInternal(track);
       LOG(LogLevel::Debug,
@@ -4209,21 +4219,22 @@ HTMLMediaElement::WakeLockRelease()
     ErrorResult rv;
     mWakeLock->Unlock(rv);
     rv.SuppressException();
     mWakeLock = nullptr;
   }
 }
 
 HTMLMediaElement::OutputMediaStream::OutputMediaStream()
-  : mFinishWhenEnded(false)
+  : mNextAvailableTrackID(1)
+  , mFinishWhenEnded(false)
   , mCapturingAudioOnly(false)
   , mCapturingDecoder(false)
   , mCapturingMediaStream(false)
-  , mNextAvailableTrackID(1) {}
+{}
 
 HTMLMediaElement::OutputMediaStream::~OutputMediaStream()
 {
   for (auto pair : mTrackPorts) {
     pair.second()->Destroy();
   }
 }
 
@@ -4908,16 +4919,17 @@ HTMLMediaElement::FinishDecoderSetup(Med
   for (OutputMediaStream& ms : mOutputStreams) {
     if (ms.mCapturingMediaStream) {
       MOZ_ASSERT(!ms.mCapturingDecoder);
       continue;
     }
 
     ms.mCapturingDecoder = true;
     aDecoder->AddOutputStream(ms.mStream->GetInputStream()->AsProcessedStream(),
+                              ms.mNextAvailableTrackID,
                               ms.mFinishWhenEnded);
   }
 
   if (mMediaKeys) {
     if (mMediaKeys->GetCDMProxy()) {
       mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
     } else {
       // CDM must have crashed.
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -862,28 +862,28 @@ protected:
 
   // Holds references to the DOM wrappers for the MediaStreams that we're
   // writing to.
   struct OutputMediaStream {
     OutputMediaStream();
     ~OutputMediaStream();
 
     RefPtr<DOMMediaStream> mStream;
+    TrackID mNextAvailableTrackID;
     bool mFinishWhenEnded;
     bool mCapturingAudioOnly;
     bool mCapturingDecoder;
     bool mCapturingMediaStream;
 
     // 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.
-    TrackID mNextAvailableTrackID;
     nsTArray<Pair<nsString, RefPtr<MediaInputPort>>> mTrackPorts;
   };
 
   already_AddRefed<Promise> PlayInternal(ErrorResult& aRv);
 
   /** Use this method to change the mReadyState member, so required
    * events can be fired.
    */
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -165,33 +165,44 @@ MediaDecoder::SetVolume(double aVolume)
 {
   MOZ_ASSERT(NS_IsMainThread());
   AbstractThread::AutoEnter context(AbstractMainThread());
   mVolume = aVolume;
 }
 
 void
 MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream,
+                              TrackID aNextAvailableTrackID,
                               bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mDecoderStateMachine, "Must be called after Load().");
   AbstractThread::AutoEnter context(AbstractMainThread());
-  mDecoderStateMachine->AddOutputStream(aStream, aFinishWhenEnded);
+  mDecoderStateMachine->AddOutputStream(
+    aStream, aNextAvailableTrackID, aFinishWhenEnded);
 }
 
 void
 MediaDecoder::RemoveOutputStream(MediaStream* aStream)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mDecoderStateMachine, "Must be called after Load().");
   AbstractThread::AutoEnter context(AbstractMainThread());
   mDecoderStateMachine->RemoveOutputStream(aStream);
 }
 
+TrackID
+MediaDecoder::NextAvailableTrackIDFor(MediaStream* aOutputStream) const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mDecoderStateMachine, "Must be called after Load().");
+  AbstractThread::AutoEnter context(AbstractMainThread());
+  return mDecoderStateMachine->NextAvailableTrackIDFor(aOutputStream);
+}
+
 double
 MediaDecoder::GetDuration()
 {
   MOZ_ASSERT(NS_IsMainThread());
   AbstractThread::AutoEnter context(AbstractMainThread());
   return mDuration;
 }
 
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -165,19 +165,22 @@ public:
   // captureStream(UntilEnded). Seeking creates a new source stream, as does
   // replaying after the input as ended. In the latter case, the new source is
   // not connected to streams created by captureStreamUntilEnded.
 
   // Add an output stream. All decoder output will be sent to the stream.
   // The stream is initially blocked. The decoder is responsible for unblocking
   // it while it is playing back.
   virtual void AddOutputStream(ProcessedMediaStream* aStream,
+                               TrackID aNextAvailableTrackID,
                                bool aFinishWhenEnded);
   // Remove an output stream added with AddOutputStream.
   virtual void RemoveOutputStream(MediaStream* aStream);
+  // The next TrackID that can be used without risk of a collision.
+  virtual TrackID NextAvailableTrackIDFor(MediaStream* aOutputStream) const;
 
   // Return the duration of the video in seconds.
   virtual double GetDuration();
 
   // Return true if the stream is infinite.
   bool IsInfinite() const;
 
   // Return true if we are currently seeking in the media resource.
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -3821,21 +3821,22 @@ MediaDecoderStateMachine::RequestDebugIn
       [self, p]() { p->Resolve(self->GetDebugInfo(), __func__); }),
     AbstractThread::TailDispatch);
   MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
   Unused << rv;
   return p.forget();
 }
 
 void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
+                                               TrackID aNextAvailableTrackID,
                                                bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
   LOG("AddOutputStream aStream=%p!", aStream);
-  mOutputStreamManager->Add(aStream, aFinishWhenEnded);
+  mOutputStreamManager->Add(aStream, aNextAvailableTrackID, aFinishWhenEnded);
   nsCOMPtr<nsIRunnable> r =
     NewRunnableMethod<bool>("MediaDecoderStateMachine::SetAudioCaptured",
                             this,
                             &MediaDecoderStateMachine::SetAudioCaptured,
                             true);
   nsresult rv = OwnerThread()->Dispatch(r.forget());
   MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
   Unused << rv;
@@ -3853,16 +3854,23 @@ void MediaDecoderStateMachine::RemoveOut
                               &MediaDecoderStateMachine::SetAudioCaptured,
                               false);
     nsresult rv = OwnerThread()->Dispatch(r.forget());
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     Unused << rv;
   }
 }
 
+TrackID
+MediaDecoderStateMachine::NextAvailableTrackIDFor(MediaStream* aOutputStream) const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mOutputStreamManager->NextAvailableTrackIDFor(aOutputStream);
+}
+
 class VideoQueueMemoryFunctor : public nsDequeFunctor
 {
 public:
   VideoQueueMemoryFunctor()
     : mSize(0)
   {
   }
 
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -196,19 +196,22 @@ public:
     DECODER_STATE_SHUTDOWN
   };
 
   // Returns the state machine task queue.
   TaskQueue* OwnerThread() const { return mTaskQueue; }
 
   RefPtr<MediaDecoder::DebugInfoPromise> RequestDebugInfo();
 
-  void AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
+  void AddOutputStream(ProcessedMediaStream* aStream,
+                       TrackID aNextAvailableTrackID,
+                       bool aFinishWhenEnded);
   // Remove an output stream added with AddOutputStream.
   void RemoveOutputStream(MediaStream* aStream);
+  TrackID NextAvailableTrackIDFor(MediaStream* aOutputStream) const;
 
   // Seeks to the decoder to aTarget asynchronously.
   RefPtr<MediaDecoder::SeekPromise> InvokeSeek(const SeekTarget& aTarget);
 
   void DispatchSetPlaybackRate(double aPlaybackRate)
   {
     OwnerThread()->DispatchStateChange(
       NewRunnableMethod<double>("MediaDecoderStateMachine::SetPlaybackRate",
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -188,27 +188,32 @@ DecodedStreamData::DecodedStreamData(Out
   // mPlaying is initially true because MDSM won't start playback until playing
   // becomes true. This is consistent with the settings of AudioSink.
   , mPlaying(true)
   , mEOSVideoCompensation(false)
   , mOutputStreamManager(aOutputStreamManager)
   , mAbstractMainThread(aMainThread)
 {
   mStream->AddListener(mListener);
-  mOutputStreamManager->Connect(mStream);
+  TrackID audioTrack = TRACK_NONE;
+  TrackID videoTrack = TRACK_NONE;
 
   // Initialize tracks.
   if (aInit.mInfo.HasAudio()) {
-    mStream->AddAudioTrack(aInit.mInfo.mAudio.mTrackId,
+    audioTrack = aInit.mInfo.mAudio.mTrackId;
+    mStream->AddAudioTrack(audioTrack,
                            aInit.mInfo.mAudio.mRate,
                            0, new AudioSegment());
   }
   if (aInit.mInfo.HasVideo()) {
-    mStream->AddTrack(aInit.mInfo.mVideo.mTrackId, 0, new VideoSegment());
+    videoTrack = aInit.mInfo.mVideo.mTrackId;
+    mStream->AddTrack(videoTrack, 0, new VideoSegment());
   }
+
+  mOutputStreamManager->Connect(mStream, audioTrack, videoTrack);
 }
 
 DecodedStreamData::~DecodedStreamData()
 {
   mOutputStreamManager->Disconnect();
   mStream->Destroy();
 }
 
--- a/dom/media/mediasink/OutputStreamManager.cpp
+++ b/dom/media/mediasink/OutputStreamManager.cpp
@@ -8,126 +8,164 @@
 #include "OutputStreamManager.h"
 
 namespace mozilla {
 
 OutputStreamData::~OutputStreamData()
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Break the connection to the input stream if necessary.
-  if (mPort) {
-    mPort->Destroy();
+  for (RefPtr<MediaInputPort>& port : mPorts) {
+    port->Destroy();
   }
 }
 
 void
-OutputStreamData::Init(OutputStreamManager* aOwner, ProcessedMediaStream* aStream)
+OutputStreamData::Init(OutputStreamManager* aOwner,
+                       ProcessedMediaStream* aStream,
+                       TrackID aNextAvailableTrackID)
 {
   mOwner = aOwner;
   mStream = aStream;
+  mNextAvailableTrackID = aNextAvailableTrackID;
 }
 
 bool
-OutputStreamData::Connect(MediaStream* aStream)
+OutputStreamData::Connect(MediaStream* aStream,
+                          TrackID aInputAudioTrackID,
+                          TrackID aInputVideoTrackID)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mPort, "Already connected?");
+  MOZ_ASSERT(mPorts.IsEmpty(), "Already connected?");
 
   if (mStream->IsDestroyed()) {
     return false;
   }
 
-  mPort = mStream->AllocateInputPort(aStream);
+  for (TrackID tid : {aInputAudioTrackID, aInputVideoTrackID}) {
+    if (tid == TRACK_NONE) {
+      continue;
+    }
+    MOZ_ASSERT(IsTrackIDExplicit(tid));
+    mPorts.AppendElement(mStream->AllocateInputPort(
+        aStream, tid, mNextAvailableTrackID++));
+  }
   return true;
 }
 
 bool
 OutputStreamData::Disconnect()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // During cycle collection, DOMMediaStream can be destroyed and send
   // its Destroy message before this decoder is destroyed. So we have to
   // be careful not to send any messages after the Destroy().
   if (mStream->IsDestroyed()) {
     return false;
   }
 
-  // Disconnect the existing port if necessary.
-  if (mPort) {
-    mPort->Destroy();
-    mPort = nullptr;
+  // Disconnect any existing port.
+  for (RefPtr<MediaInputPort>& port : mPorts) {
+    port->Destroy();
   }
+  mPorts.Clear();
   return true;
 }
 
 bool
 OutputStreamData::Equals(MediaStream* aStream) const
 {
   return mStream == aStream;
 }
 
 MediaStreamGraph*
 OutputStreamData::Graph() const
 {
   return mStream->Graph();
 }
 
+TrackID
+OutputStreamData::NextAvailableTrackID() const
+{
+  return mNextAvailableTrackID;
+}
+
 void
-OutputStreamManager::Add(ProcessedMediaStream* aStream, bool aFinishWhenEnded)
+OutputStreamManager::Add(ProcessedMediaStream* aStream,
+                         TrackID aNextAvailableTrackID,
+                         bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
   // All streams must belong to the same graph.
   MOZ_ASSERT(!Graph() || Graph() == aStream->Graph());
 
   // Ensure that aStream finishes the moment mDecodedStream does.
   if (aFinishWhenEnded) {
     aStream->QueueSetAutofinish(true);
   }
 
   OutputStreamData* p = mStreams.AppendElement();
-  p->Init(this, aStream);
+  p->Init(this, aStream, aNextAvailableTrackID);
 
   // Connect to the input stream if we have one. Otherwise the output stream
   // will be connected in Connect().
   if (mInputStream) {
-    p->Connect(mInputStream);
+    p->Connect(mInputStream, mInputAudioTrackID, mInputVideoTrackID);
   }
 }
 
 void
 OutputStreamManager::Remove(MediaStream* aStream)
 {
   MOZ_ASSERT(NS_IsMainThread());
   for (int32_t i = mStreams.Length() - 1; i >= 0; --i) {
     if (mStreams[i].Equals(aStream)) {
       mStreams.RemoveElementAt(i);
       break;
     }
   }
 }
 
+TrackID
+OutputStreamManager::NextAvailableTrackIDFor(MediaStream* aOutputStream) const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  for (const OutputStreamData& out : mStreams) {
+    if (out.Equals(aOutputStream)) {
+      return out.NextAvailableTrackID();
+    }
+  }
+  return TRACK_INVALID;
+}
+
 void
-OutputStreamManager::Connect(MediaStream* aStream)
+OutputStreamManager::Connect(MediaStream* aStream,
+                             TrackID aAudioTrackID,
+                             TrackID aVideoTrackID)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mInputStream = aStream;
+  mInputAudioTrackID = aAudioTrackID;
+  mInputVideoTrackID = aVideoTrackID;
   for (int32_t i = mStreams.Length() - 1; i >= 0; --i) {
-    if (!mStreams[i].Connect(aStream)) {
+    if (!mStreams[i].Connect(aStream, mInputAudioTrackID, mInputVideoTrackID)) {
       // Probably the DOMMediaStream was GCed. Clean up.
       mStreams.RemoveElementAt(i);
     }
   }
 }
 
 void
 OutputStreamManager::Disconnect()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mInputStream = nullptr;
+  mInputAudioTrackID = TRACK_INVALID;
+  mInputVideoTrackID = TRACK_INVALID;
   for (int32_t i = mStreams.Length() - 1; i >= 0; --i) {
     if (!mStreams[i].Disconnect()) {
       // Probably the DOMMediaStream was GCed. Clean up.
       mStreams.RemoveElementAt(i);
     }
   }
 }
 
--- a/dom/media/mediasink/OutputStreamManager.h
+++ b/dom/media/mediasink/OutputStreamManager.h
@@ -4,77 +4,92 @@
  * 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 OutputStreamManager_h
 #define OutputStreamManager_h
 
 #include "mozilla/RefPtr.h"
 #include "nsTArray.h"
+#include "TrackID.h"
 
 namespace mozilla {
 
 class MediaInputPort;
 class MediaStream;
 class MediaStreamGraph;
 class OutputStreamManager;
 class ProcessedMediaStream;
 
 class OutputStreamData {
 public:
   ~OutputStreamData();
-  void Init(OutputStreamManager* aOwner, ProcessedMediaStream* aStream);
+  void Init(OutputStreamManager* aOwner,
+            ProcessedMediaStream* aStream,
+            TrackID aNextAvailableTrackID);
 
-  // Connect mStream to the input stream.
+  // Connect the given input stream's audio and video tracks to mStream.
   // Return false is mStream is already destroyed, otherwise true.
-  bool Connect(MediaStream* aStream);
+  bool Connect(MediaStream* aStream, TrackID aAudioTrackID, TrackID aVideoTrackID);
   // Disconnect mStream from its input stream.
   // Return false is mStream is already destroyed, otherwise true.
   bool Disconnect();
   // Return true if aStream points to the same object as mStream.
   // Used by OutputStreamManager to remove an output stream.
   bool Equals(MediaStream* aStream) const;
   // Return the graph mStream belongs to.
   MediaStreamGraph* Graph() const;
+  // The next TrackID that will not cause a collision in mStream.
+  TrackID NextAvailableTrackID() const;
 
 private:
   OutputStreamManager* mOwner;
   RefPtr<ProcessedMediaStream> mStream;
-  // mPort connects our mStream to an input stream.
-  RefPtr<MediaInputPort> mPort;
+  // mPort connects an input stream to our mStream.
+  nsTArray<RefPtr<MediaInputPort>> mPorts;
+  // For guaranteeing TrackID uniqueness in our mStream.
+  TrackID mNextAvailableTrackID = TRACK_INVALID;
 };
 
 class OutputStreamManager {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OutputStreamManager);
 
 public:
   // Add the output stream to the collection.
-  void Add(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
+  void Add(ProcessedMediaStream* aStream,
+           TrackID aNextAvailableTrackID,
+           bool aFinishWhenEnded);
   // Remove the output stream from the collection.
   void Remove(MediaStream* aStream);
+  // The next TrackID that will not cause a collision in aOutputStream.
+  TrackID NextAvailableTrackIDFor(MediaStream* aOutputStream) const;
   // Return true if the collection empty.
   bool IsEmpty() const
   {
     MOZ_ASSERT(NS_IsMainThread());
     return mStreams.IsEmpty();
   }
-  // Connect all output streams in the collection to the input stream.
-  void Connect(MediaStream* aStream);
-  // Disconnect all output streams from the input stream.
+  // Connect the given input stream's tracks to all output streams.
+  void Connect(MediaStream* aStream,
+               TrackID aAudioTrackID,
+               TrackID aVideoTrackID);
+  // Disconnect the input stream to all output streams.
   void Disconnect();
   // Return the graph these streams belong to or null if empty.
   MediaStreamGraph* Graph() const
   {
     MOZ_ASSERT(NS_IsMainThread());
     return !IsEmpty() ? mStreams[0].Graph() : nullptr;
   }
 
 private:
   ~OutputStreamManager() {}
   // Keep the input stream so we can connect the output streams that
   // are added after Connect().
   RefPtr<MediaStream> mInputStream;
+  TrackID mInputAudioTrackID = TRACK_INVALID;
+  TrackID mInputVideoTrackID = TRACK_INVALID;
   nsTArray<OutputStreamData> mStreams;
 };
 
 } // namespace mozilla
 
 #endif // OutputStreamManager_h