Bug 1070127 - refactor TrackUnionStream into cpp to unconfuse lldb debugger r=roc
☠☠ backed out by 03de764d461a ☠ ☠
authorRandell Jesup <rjesup@jesup.org>
Sat, 11 Oct 2014 11:34:36 -0400
changeset 233189 6442a3773f2aafcc57753e7a689257a990339346
parent 233188 400c4131858e9102f6f1d26f6603f9a7f16e0276
child 233190 03de764d461a9d023d65a20279fdb6dc26ce56c4
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs1070127
milestone35.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 1070127 - refactor TrackUnionStream into cpp to unconfuse lldb debugger r=roc
content/media/TrackUnionStream.cpp
content/media/TrackUnionStream.h
content/media/moz.build
copy from content/media/TrackUnionStream.h
copy to content/media/TrackUnionStream.cpp
--- a/content/media/TrackUnionStream.h
+++ b/content/media/TrackUnionStream.cpp
@@ -1,50 +1,77 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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 MOZILLA_TRACKUNIONSTREAM_H_
-#define MOZILLA_TRACKUNIONSTREAM_H_
+#include "MediaStreamGraphImpl.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/unused.h"
 
-#include "MediaStreamGraph.h"
+#include "AudioSegment.h"
+#include "VideoSegment.h"
+#include "nsContentUtils.h"
+#include "nsIAppShell.h"
+#include "nsIObserver.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWidgetsCID.h"
+#include "prerror.h"
+#include "prlog.h"
+#include "mozilla/Attributes.h"
+#include "TrackUnionStream.h"
+#include "ImageContainer.h"
+#include "AudioChannelService.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeStream.h"
+#include "AudioNodeExternalInputStream.h"
 #include <algorithm>
+#include "DOMMediaStream.h"
+#include "GeckoProfiler.h"
+#include "mozilla/unused.h"
+#ifdef MOZ_WEBRTC
+#include "AudioOutputObserver.h"
+#endif
+
+using namespace mozilla::layers;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
 
 namespace mozilla {
 
 #ifdef PR_LOGGING
-#define STREAM_LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg)
+PRLogModuleInfo* gTrackUnionStreamLog;
+#define STREAM_LOG(type, msg) PR_LOG(gTrackUnionStreamLog, type, msg)
 #else
 #define STREAM_LOG(type, msg)
 #endif
 
-/**
- * See MediaStreamGraph::CreateTrackUnionStream.
- * This file is only included by MediaStreamGraph.cpp so it's OK to put the
- * entire implementation in this header file.
- */
-class TrackUnionStream : public ProcessedMediaStream {
-public:
-  explicit TrackUnionStream(DOMMediaStream* aWrapper) :
-    ProcessedMediaStream(aWrapper),
-    mFilterCallback(nullptr)
-  {}
+TrackUnionStream::TrackUnionStream(DOMMediaStream* aWrapper) :
+  ProcessedMediaStream(aWrapper),
+  mFilterCallback(nullptr)
+{
+#ifdef PR_LOGGING
+  if (!gTrackUnionStreamLog) {
+    gTrackUnionStreamLog = PR_NewLogModule("TrackUnionStream");
+  }
+#endif
+}
 
-  virtual void RemoveInput(MediaInputPort* aPort) MOZ_OVERRIDE
+  void TrackUnionStream::RemoveInput(MediaInputPort* aPort)
   {
     for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
       if (mTrackMap[i].mInputPort == aPort) {
         EndTrack(i);
         mTrackMap.RemoveElementAt(i);
       }
     }
     ProcessedMediaStream::RemoveInput(aPort);
   }
-  virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE
+  void TrackUnionStream::ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags)
   {
     if (IsFinishedOnGraphThread()) {
       return;
     }
     nsAutoTArray<bool,8> mappedTracksFinished;
     nsAutoTArray<bool,8> mappedTracksWithMatchingInputTracks;
     for (uint32_t i = 0; i < mTrackMap.Length(); ++i) {
       mappedTracksFinished.AppendElement(true);
@@ -112,60 +139,35 @@ public:
     if (allHaveCurrentData) {
       // We can make progress if we're not blocked
       mHasCurrentData = true;
     }
   }
 
   // Consumers may specify a filtering callback to apply to every input track.
   // Returns true to allow the track to act as an input; false to reject it entirely.
-  typedef bool (*TrackIDFilterCallback)(StreamBuffer::Track*);
-  void SetTrackIDFilter(TrackIDFilterCallback aCallback) {
+
+  void TrackUnionStream::SetTrackIDFilter(TrackIDFilterCallback aCallback)
+  {
     mFilterCallback = aCallback;
   }
 
   // Forward SetTrackEnabled(output_track_id, enabled) to the Source MediaStream,
   // translating the output track ID into the correct ID in the source.
-  virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) MOZ_OVERRIDE {
+  void TrackUnionStream::ForwardTrackEnabled(TrackID aOutputID, bool aEnabled)
+  {
     for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
       if (mTrackMap[i].mOutputTrackID == aOutputID) {
         mTrackMap[i].mInputPort->GetSource()->
           SetTrackEnabled(mTrackMap[i].mInputTrackID, aEnabled);
       }
     }
   }
 
-protected:
-  TrackIDFilterCallback mFilterCallback;
-
-  // Only non-ended tracks are allowed to persist in this map.
-  struct TrackMapEntry {
-    // mEndOfConsumedInputTicks is the end of the input ticks that we've consumed.
-    // 0 if we haven't consumed any yet.
-    TrackTicks mEndOfConsumedInputTicks;
-    // mEndOfLastInputIntervalInInputStream is the timestamp for the end of the
-    // previous interval which was unblocked for both the input and output
-    // stream, in the input stream's timeline, or -1 if there wasn't one.
-    StreamTime mEndOfLastInputIntervalInInputStream;
-    // mEndOfLastInputIntervalInOutputStream is the timestamp for the end of the
-    // previous interval which was unblocked for both the input and output
-    // stream, in the output stream's timeline, or -1 if there wasn't one.
-    StreamTime mEndOfLastInputIntervalInOutputStream;
-    MediaInputPort* mInputPort;
-    // We keep track IDs instead of track pointers because
-    // tracks can be removed without us being notified (e.g.
-    // when a finished track is forgotten.) When we need a Track*,
-    // we call StreamBuffer::FindTrack, which will return null if
-    // the track has been deleted.
-    TrackID mInputTrackID;
-    TrackID mOutputTrackID;
-    nsAutoPtr<MediaSegment> mSegment;
-  };
-
-  uint32_t AddTrack(MediaInputPort* aPort, StreamBuffer::Track* aTrack,
+  uint32_t TrackUnionStream::AddTrack(MediaInputPort* aPort, StreamBuffer::Track* aTrack,
                     GraphTime aFrom)
   {
     // Use the ID of the source track if it's not already assigned to a track,
     // otherwise allocate a new unique ID.
     TrackID id = aTrack->GetID();
     TrackID maxTrackID = 0;
     for (uint32_t i = 0; i < mTrackMap.Length(); ++i) {
       TrackID outID = mTrackMap[i].mOutputTrackID;
@@ -208,34 +210,36 @@ protected:
     map->mEndOfLastInputIntervalInInputStream = -1;
     map->mEndOfLastInputIntervalInOutputStream = -1;
     map->mInputPort = aPort;
     map->mInputTrackID = aTrack->GetID();
     map->mOutputTrackID = track->GetID();
     map->mSegment = aTrack->GetSegment()->CreateEmptyClone();
     return mTrackMap.Length() - 1;
   }
-  void EndTrack(uint32_t aIndex)
+
+  void TrackUnionStream::EndTrack(uint32_t aIndex)
   {
     StreamBuffer::Track* outputTrack = mBuffer.FindTrack(mTrackMap[aIndex].mOutputTrackID);
     if (!outputTrack || outputTrack->IsEnded())
       return;
     for (uint32_t j = 0; j < mListeners.Length(); ++j) {
       MediaStreamListener* l = mListeners[j];
       TrackTicks offset = outputTrack->GetSegment()->GetDuration();
       nsAutoPtr<MediaSegment> segment;
       segment = outputTrack->GetSegment()->CreateEmptyClone();
       l->NotifyQueuedTrackChanges(Graph(), outputTrack->GetID(),
                                   outputTrack->GetRate(), offset,
                                   MediaStreamListener::TRACK_EVENT_ENDED,
                                   *segment);
     }
     outputTrack->SetEnded();
   }
-  void CopyTrackData(StreamBuffer::Track* aInputTrack,
+
+  void TrackUnionStream::CopyTrackData(StreamBuffer::Track* aInputTrack,
                      uint32_t aMapIndex, GraphTime aFrom, GraphTime aTo,
                      bool* aOutputTrackFinished)
   {
     TrackMapEntry* map = &mTrackMap[aMapIndex];
     StreamBuffer::Track* outputTrack = mBuffer.FindTrack(map->mOutputTrackID);
     MOZ_ASSERT(outputTrack && !outputTrack->IsEnded(), "Can't copy to ended track");
 
     TrackRate rate = outputTrack->GetRate();
@@ -360,15 +364,9 @@ protected:
         MediaStreamListener* l = mListeners[j];
         l->NotifyQueuedTrackChanges(Graph(), outputTrack->GetID(),
                                     outputTrack->GetRate(), startTicks, 0,
                                     *segment);
       }
       outputTrack->GetSegment()->AppendFrom(segment);
     }
   }
-
-  nsTArray<TrackMapEntry> mTrackMap;
-};
-
 }
-
-#endif /* MOZILLA_MEDIASTREAMGRAPH_H_ */
--- a/content/media/TrackUnionStream.h
+++ b/content/media/TrackUnionStream.h
@@ -6,137 +6,35 @@
 #ifndef MOZILLA_TRACKUNIONSTREAM_H_
 #define MOZILLA_TRACKUNIONSTREAM_H_
 
 #include "MediaStreamGraph.h"
 #include <algorithm>
 
 namespace mozilla {
 
-#ifdef PR_LOGGING
-#define STREAM_LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg)
-#else
-#define STREAM_LOG(type, msg)
-#endif
-
 /**
  * See MediaStreamGraph::CreateTrackUnionStream.
- * This file is only included by MediaStreamGraph.cpp so it's OK to put the
- * entire implementation in this header file.
  */
 class TrackUnionStream : public ProcessedMediaStream {
 public:
-  explicit TrackUnionStream(DOMMediaStream* aWrapper) :
-    ProcessedMediaStream(aWrapper),
-    mFilterCallback(nullptr)
-  {}
+  explicit TrackUnionStream(DOMMediaStream* aWrapper);
 
-  virtual void RemoveInput(MediaInputPort* aPort) MOZ_OVERRIDE
-  {
-    for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
-      if (mTrackMap[i].mInputPort == aPort) {
-        EndTrack(i);
-        mTrackMap.RemoveElementAt(i);
-      }
-    }
-    ProcessedMediaStream::RemoveInput(aPort);
-  }
-  virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE
-  {
-    if (IsFinishedOnGraphThread()) {
-      return;
-    }
-    nsAutoTArray<bool,8> mappedTracksFinished;
-    nsAutoTArray<bool,8> mappedTracksWithMatchingInputTracks;
-    for (uint32_t i = 0; i < mTrackMap.Length(); ++i) {
-      mappedTracksFinished.AppendElement(true);
-      mappedTracksWithMatchingInputTracks.AppendElement(false);
-    }
-    bool allFinished = true;
-    bool allHaveCurrentData = true;
-    for (uint32_t i = 0; i < mInputs.Length(); ++i) {
-      MediaStream* stream = mInputs[i]->GetSource();
-      if (!stream->IsFinishedOnGraphThread()) {
-        // XXX we really should check whether 'stream' has finished within time aTo,
-        // not just that it's finishing when all its queued data eventually runs
-        // out.
-        allFinished = false;
-      }
-      if (!stream->HasCurrentData()) {
-        allHaveCurrentData = false;
-      }
-      for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer());
-           !tracks.IsEnded(); tracks.Next()) {
-        bool found = false;
-        for (uint32_t j = 0; j < mTrackMap.Length(); ++j) {
-          TrackMapEntry* map = &mTrackMap[j];
-          if (map->mInputPort == mInputs[i] && map->mInputTrackID == tracks->GetID()) {
-            bool trackFinished;
-            StreamBuffer::Track* outputTrack = mBuffer.FindTrack(map->mOutputTrackID);
-            if (!outputTrack || outputTrack->IsEnded()) {
-              trackFinished = true;
-            } else {
-              CopyTrackData(tracks.get(), j, aFrom, aTo, &trackFinished);
-            }
-            mappedTracksFinished[j] = trackFinished;
-            mappedTracksWithMatchingInputTracks[j] = true;
-            found = true;
-            break;
-          }
-        }
-        if (!found && (!mFilterCallback || mFilterCallback(tracks.get()))) {
-          bool trackFinished = false;
-          uint32_t mapIndex = AddTrack(mInputs[i], tracks.get(), aFrom);
-          CopyTrackData(tracks.get(), mapIndex, aFrom, aTo, &trackFinished);
-          mappedTracksFinished.AppendElement(trackFinished);
-          mappedTracksWithMatchingInputTracks.AppendElement(true);
-        }
-      }
-    }
-    for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
-      if (mappedTracksFinished[i]) {
-        EndTrack(i);
-      } else {
-        allFinished = false;
-      }
-      if (!mappedTracksWithMatchingInputTracks[i]) {
-        mTrackMap.RemoveElementAt(i);
-      }
-    }
-    if (allFinished && mAutofinish && (aFlags & ALLOW_FINISH)) {
-      // All streams have finished and won't add any more tracks, and
-      // all our tracks have actually finished and been removed from our map,
-      // so we're finished now.
-      FinishOnGraphThread();
-    } else {
-      mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTime(aTo));
-    }
-    if (allHaveCurrentData) {
-      // We can make progress if we're not blocked
-      mHasCurrentData = true;
-    }
-  }
+  virtual void RemoveInput(MediaInputPort* aPort) MOZ_OVERRIDE;
+  virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE;
 
   // Consumers may specify a filtering callback to apply to every input track.
   // Returns true to allow the track to act as an input; false to reject it entirely.
   typedef bool (*TrackIDFilterCallback)(StreamBuffer::Track*);
-  void SetTrackIDFilter(TrackIDFilterCallback aCallback) {
-    mFilterCallback = aCallback;
-  }
+
+  void SetTrackIDFilter(TrackIDFilterCallback aCallback);
 
   // Forward SetTrackEnabled(output_track_id, enabled) to the Source MediaStream,
   // translating the output track ID into the correct ID in the source.
-  virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) MOZ_OVERRIDE {
-    for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
-      if (mTrackMap[i].mOutputTrackID == aOutputID) {
-        mTrackMap[i].mInputPort->GetSource()->
-          SetTrackEnabled(mTrackMap[i].mInputTrackID, aEnabled);
-      }
-    }
-  }
+  virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) MOZ_OVERRIDE;
 
 protected:
   TrackIDFilterCallback mFilterCallback;
 
   // Only non-ended tracks are allowed to persist in this map.
   struct TrackMapEntry {
     // mEndOfConsumedInputTicks is the end of the input ticks that we've consumed.
     // 0 if we haven't consumed any yet.
@@ -156,219 +54,20 @@ protected:
     // we call StreamBuffer::FindTrack, which will return null if
     // the track has been deleted.
     TrackID mInputTrackID;
     TrackID mOutputTrackID;
     nsAutoPtr<MediaSegment> mSegment;
   };
 
   uint32_t AddTrack(MediaInputPort* aPort, StreamBuffer::Track* aTrack,
-                    GraphTime aFrom)
-  {
-    // Use the ID of the source track if it's not already assigned to a track,
-    // otherwise allocate a new unique ID.
-    TrackID id = aTrack->GetID();
-    TrackID maxTrackID = 0;
-    for (uint32_t i = 0; i < mTrackMap.Length(); ++i) {
-      TrackID outID = mTrackMap[i].mOutputTrackID;
-      maxTrackID = std::max(maxTrackID, outID);
-    }
-    // Note: we might have removed it here, but it might still be in the
-    // StreamBuffer if the TrackUnionStream sees its input stream flip from
-    // A to B, where both A and B have a track with the same ID
-    while (1) {
-      // search until we find one not in use here, and not in mBuffer
-      if (!mBuffer.FindTrack(id)) {
-        break;
-      }
-      id = ++maxTrackID;
-    }
-
-    TrackRate rate = aTrack->GetRate();
-    // Round up the track start time so the track, if anything, starts a
-    // little later than the true time. This means we'll have enough
-    // samples in our input stream to go just beyond the destination time.
-    TrackTicks outputStart = TimeToTicksRoundUp(rate, GraphTimeToStreamTime(aFrom));
-
-    nsAutoPtr<MediaSegment> segment;
-    segment = aTrack->GetSegment()->CreateEmptyClone();
-    for (uint32_t j = 0; j < mListeners.Length(); ++j) {
-      MediaStreamListener* l = mListeners[j];
-      l->NotifyQueuedTrackChanges(Graph(), id, rate, outputStart,
-                                  MediaStreamListener::TRACK_EVENT_CREATED,
-                                  *segment);
-    }
-    segment->AppendNullData(outputStart);
-    StreamBuffer::Track* track =
-      &mBuffer.AddTrack(id, rate, outputStart, segment.forget());
-    STREAM_LOG(PR_LOG_DEBUG, ("TrackUnionStream %p adding track %d for input stream %p track %d, start ticks %lld",
-                              this, id, aPort->GetSource(), aTrack->GetID(),
-                              (long long)outputStart));
-
-    TrackMapEntry* map = mTrackMap.AppendElement();
-    map->mEndOfConsumedInputTicks = 0;
-    map->mEndOfLastInputIntervalInInputStream = -1;
-    map->mEndOfLastInputIntervalInOutputStream = -1;
-    map->mInputPort = aPort;
-    map->mInputTrackID = aTrack->GetID();
-    map->mOutputTrackID = track->GetID();
-    map->mSegment = aTrack->GetSegment()->CreateEmptyClone();
-    return mTrackMap.Length() - 1;
-  }
-  void EndTrack(uint32_t aIndex)
-  {
-    StreamBuffer::Track* outputTrack = mBuffer.FindTrack(mTrackMap[aIndex].mOutputTrackID);
-    if (!outputTrack || outputTrack->IsEnded())
-      return;
-    for (uint32_t j = 0; j < mListeners.Length(); ++j) {
-      MediaStreamListener* l = mListeners[j];
-      TrackTicks offset = outputTrack->GetSegment()->GetDuration();
-      nsAutoPtr<MediaSegment> segment;
-      segment = outputTrack->GetSegment()->CreateEmptyClone();
-      l->NotifyQueuedTrackChanges(Graph(), outputTrack->GetID(),
-                                  outputTrack->GetRate(), offset,
-                                  MediaStreamListener::TRACK_EVENT_ENDED,
-                                  *segment);
-    }
-    outputTrack->SetEnded();
-  }
+                    GraphTime aFrom);
+  void EndTrack(uint32_t aIndex);
   void CopyTrackData(StreamBuffer::Track* aInputTrack,
                      uint32_t aMapIndex, GraphTime aFrom, GraphTime aTo,
-                     bool* aOutputTrackFinished)
-  {
-    TrackMapEntry* map = &mTrackMap[aMapIndex];
-    StreamBuffer::Track* outputTrack = mBuffer.FindTrack(map->mOutputTrackID);
-    MOZ_ASSERT(outputTrack && !outputTrack->IsEnded(), "Can't copy to ended track");
-
-    TrackRate rate = outputTrack->GetRate();
-    MediaSegment* segment = map->mSegment;
-    MediaStream* source = map->mInputPort->GetSource();
-
-    GraphTime next;
-    *aOutputTrackFinished = false;
-    for (GraphTime t = aFrom; t < aTo; t = next) {
-      MediaInputPort::InputInterval interval = map->mInputPort->GetNextInputInterval(t);
-      interval.mEnd = std::min(interval.mEnd, aTo);
-      StreamTime inputEnd = source->GraphTimeToStreamTime(interval.mEnd);
-      TrackTicks inputTrackEndPoint = aInputTrack->GetEnd();
-
-      if (aInputTrack->IsEnded() &&
-          aInputTrack->GetEndTimeRoundDown() <= inputEnd) {
-        *aOutputTrackFinished = true;
-      }
-
-      if (interval.mStart >= interval.mEnd)
-        break;
-      next = interval.mEnd;
-
-      // Ticks >= startTicks and < endTicks are in the interval
-      StreamTime outputEnd = GraphTimeToStreamTime(interval.mEnd);
-      TrackTicks startTicks = outputTrack->GetEnd();
-      StreamTime outputStart = GraphTimeToStreamTime(interval.mStart);
-      MOZ_ASSERT(startTicks == TimeToTicksRoundUp(rate, outputStart), "Samples missing");
-      TrackTicks endTicks = TimeToTicksRoundUp(rate, outputEnd);
-      TrackTicks ticks = endTicks - startTicks;
-      StreamTime inputStart = source->GraphTimeToStreamTime(interval.mStart);
-
-      if (interval.mInputIsBlocked) {
-        // Maybe the input track ended?
-        segment->AppendNullData(ticks);
-        STREAM_LOG(PR_LOG_DEBUG+1, ("TrackUnionStream %p appending %lld ticks of null data to track %d",
-                   this, (long long)ticks, outputTrack->GetID()));
-      } else {
-        // Figuring out which samples to use from the input stream is tricky
-        // because its start time and our start time may differ by a fraction
-        // of a tick. Assuming the input track hasn't ended, we have to ensure
-        // that 'ticks' samples are gathered, even though a tick boundary may
-        // occur between outputStart and outputEnd but not between inputStart
-        // and inputEnd.
-        // These are the properties we need to ensure:
-        // 1) Exactly 'ticks' ticks of output are produced, i.e.
-        // inputEndTicks - inputStartTicks = ticks.
-        // 2) inputEndTicks <= aInputTrack->GetSegment()->GetDuration().
-        // 3) In any sequence of intervals where neither stream is blocked,
-        // the content of the input track we use is a contiguous sequence of
-        // ticks with no gaps or overlaps.
-        if (map->mEndOfLastInputIntervalInInputStream != inputStart ||
-            map->mEndOfLastInputIntervalInOutputStream != outputStart) {
-          // Start of a new series of intervals where neither stream is blocked.
-          map->mEndOfConsumedInputTicks = TimeToTicksRoundDown(rate, inputStart) - 1;
-        }
-        TrackTicks inputStartTicks = map->mEndOfConsumedInputTicks;
-        TrackTicks inputEndTicks = inputStartTicks + ticks;
-        map->mEndOfConsumedInputTicks = inputEndTicks;
-        map->mEndOfLastInputIntervalInInputStream = inputEnd;
-        map->mEndOfLastInputIntervalInOutputStream = outputEnd;
-        // Now we prove that the above properties hold:
-        // Property #1: trivial by construction.
-        // Property #3: trivial by construction. Between every two
-        // intervals where both streams are not blocked, the above if condition
-        // is false and mEndOfConsumedInputTicks advances exactly to match
-        // the ticks that were consumed.
-        // Property #2:
-        // Let originalOutputStart be the value of outputStart and originalInputStart
-        // be the value of inputStart when the body of the "if" block was last
-        // executed.
-        // Let allTicks be the sum of the values of 'ticks' computed since then.
-        // The interval [originalInputStart/rate, inputEnd/rate) is the
-        // same length as the interval [originalOutputStart/rate, outputEnd/rate),
-        // so the latter interval can have at most one more integer in it. Thus
-        // TimeToTicksRoundUp(rate, outputEnd) - TimeToTicksRoundUp(rate, originalOutputStart)
-        //   <= TimeToTicksRoundDown(rate, inputEnd) - TimeToTicksRoundDown(rate, originalInputStart) + 1
-        // Then
-        // inputEndTicks = TimeToTicksRoundDown(rate, originalInputStart) - 1 + allTicks
-        //   = TimeToTicksRoundDown(rate, originalInputStart) - 1 + TimeToTicksRoundUp(rate, outputEnd) - TimeToTicksRoundUp(rate, originalOutputStart)
-        //   <= TimeToTicksRoundDown(rate, originalInputStart) - 1 + TimeToTicksRoundDown(rate, inputEnd) - TimeToTicksRoundDown(rate, originalInputStart) + 1
-        //   = TimeToTicksRoundDown(rate, inputEnd)
-        //   <= inputEnd/rate
-        // (now using the fact that inputEnd <= track->GetEndTimeRoundDown() for a non-ended track)
-        //   <= TicksToTimeRoundDown(rate, aInputTrack->GetSegment()->GetDuration())/rate
-        //   <= rate*aInputTrack->GetSegment()->GetDuration()/rate
-        //   = aInputTrack->GetSegment()->GetDuration()
-        // as required.
-        // Note that while the above proof appears to be generally right, if we are suffering
-        // from a lot of underrun, then in rare cases inputStartTicks >> inputTrackEndPoint.
-        // As such, we still need to verify the sanity of property #2 and use null data as
-        // appropriate.
-
-        if (inputStartTicks < 0) {
-          // Data before the start of the track is just null.
-          // We have to add a small amount of delay to ensure that there is
-          // always a sample available if we see an interval that contains a
-          // tick boundary on the output stream's timeline but does not contain
-          // a tick boundary on the input stream's timeline. 1 tick delay is
-          // necessary and sufficient.
-          segment->AppendNullData(-inputStartTicks);
-          inputStartTicks = 0;
-        }
-        if (inputEndTicks > inputStartTicks) {
-          if (inputEndTicks <= inputTrackEndPoint) {
-            segment->AppendSlice(*aInputTrack->GetSegment(), inputStartTicks, inputEndTicks);
-            STREAM_LOG(PR_LOG_DEBUG+1, ("TrackUnionStream %p appending %lld ticks of input data to track %d",
-                       this, ticks, outputTrack->GetID()));
-          } else {
-            if (inputStartTicks < inputTrackEndPoint) {
-              segment->AppendSlice(*aInputTrack->GetSegment(), inputStartTicks, inputTrackEndPoint);
-              ticks -= inputTrackEndPoint - inputStartTicks;
-            }
-            segment->AppendNullData(ticks);
-            STREAM_LOG(PR_LOG_DEBUG+1, ("TrackUnionStream %p appending %lld ticks of input data and %lld of null data to track %d",
-                       this, inputTrackEndPoint - inputStartTicks, ticks, outputTrack->GetID()));
-          }
-        }
-      }
-      ApplyTrackDisabling(outputTrack->GetID(), segment);
-      for (uint32_t j = 0; j < mListeners.Length(); ++j) {
-        MediaStreamListener* l = mListeners[j];
-        l->NotifyQueuedTrackChanges(Graph(), outputTrack->GetID(),
-                                    outputTrack->GetRate(), startTicks, 0,
-                                    *segment);
-      }
-      outputTrack->GetSegment()->AppendFrom(segment);
-    }
-  }
+                     bool* aOutputTrackFinished);
 
   nsTArray<TrackMapEntry> mTrackMap;
 };
 
 }
 
 #endif /* MOZILLA_MEDIASTREAMGRAPH_H_ */
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -159,16 +159,17 @@ UNIFIED_SOURCES += [
     'RtspMediaResource.cpp',
     'SharedThreadPool.cpp',
     'StreamBuffer.cpp',
     'TextTrack.cpp',
     'TextTrackCue.cpp',
     'TextTrackCueList.cpp',
     'TextTrackList.cpp',
     'TextTrackRegion.cpp',
+    'TrackUnionStream.cpp',
     'VideoFrameContainer.cpp',
     'VideoPlaybackQuality.cpp',
     'VideoSegment.cpp',
     'VideoStreamTrack.cpp',
     'VideoTrack.cpp',
     'VideoTrackList.cpp',
     'VideoUtils.cpp',
     'WebVTTListener.cpp',