Backed out changeset 6442a3773f2a and 400c4131858e (bug 1070127) on a CLOSED TREE
authorRandell Jesup <rjesup@jesup.org>
Sat, 11 Oct 2014 13:22:01 -0400
changeset 233190 03de764d461a9d023d65a20279fdb6dc26ce56c4
parent 233189 6442a3773f2aafcc57753e7a689257a990339346
child 233191 16a8a5c8b96a2fe14420f3a689b099e4410ddf32
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)
bugs1070127
milestone35.0a1
backs out6442a3773f2aafcc57753e7a689257a990339346
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
Backed out changeset 6442a3773f2a and 400c4131858e (bug 1070127) on a CLOSED TREE
content/media/DOMMediaStream.cpp
content/media/DOMMediaStream.h
content/media/MediaStreamTrack.h
content/media/TrackUnionStream.cpp
content/media/TrackUnionStream.h
content/media/moz.build
--- a/content/media/DOMMediaStream.cpp
+++ b/content/media/DOMMediaStream.cpp
@@ -68,17 +68,17 @@ public:
 
       DOMMediaStream* stream = mListener->GetStream();
       if (!stream) {
         return NS_OK;
       }
 
       nsRefPtr<MediaStreamTrack> track;
       if (mEvents & MediaStreamListener::TRACK_EVENT_CREATED) {
-        track = stream->BindDOMTrack(mID, mType);
+        track = stream->CreateDOMTrack(mID, mType);
         stream->NotifyMediaStreamTrackCreated(track);
       } else {
         track = stream->GetDOMTrackFor(mID);
       }
       if (mEvents & MediaStreamListener::TRACK_EVENT_ENDED) {
         if (track) {
           track->NotifyEnded();
           stream->NotifyMediaStreamTrackEnded(track);
@@ -297,28 +297,16 @@ DOMMediaStream::AddPrincipalChangeObserv
 }
 
 bool
 DOMMediaStream::RemovePrincipalChangeObserver(PrincipalChangeObserver* aObserver)
 {
   return mPrincipalChangeObservers.RemoveElement(aObserver);
 }
 
-void
-DOMMediaStream::SetHintContents(TrackTypeHints aHintContents)
-{
-  mHintContents = aHintContents;
-  if (aHintContents & HINT_CONTENTS_AUDIO) {
-    CreateDOMTrack(0, MediaSegment::AUDIO);
-  }
-  if (aHintContents & HINT_CONTENTS_VIDEO) {
-    CreateDOMTrack(0, MediaSegment::VIDEO);
-  }
-}
-
 MediaStreamTrack*
 DOMMediaStream::CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
 {
   MediaStreamTrack* track;
   switch (aType) {
   case MediaSegment::AUDIO:
     track = new AudioStreamTrack(this, aTrackID);
     mTrackTypesAvailable |= HINT_CONTENTS_AUDIO;
@@ -327,57 +315,16 @@ DOMMediaStream::CreateDOMTrack(TrackID a
     track = new VideoStreamTrack(this, aTrackID);
     mTrackTypesAvailable |= HINT_CONTENTS_VIDEO;
     break;
   default:
     MOZ_CRASH("Unhandled track type");
   }
   mTracks.AppendElement(track);
 
-  return track;
-}
-
-MediaStreamTrack*
-DOMMediaStream::BindDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
-{
-  MediaStreamTrack* track = nullptr;
-  switch (aType) {
-  case MediaSegment::AUDIO: {
-    for (size_t i = 0; i < mTracks.Length(); ++i) {
-      track = mTracks[i]->AsAudioStreamTrack();
-      if (track) {
-        break;
-      }
-    }
-    if (track) {
-      track->BindTrackID(aTrackID);
-    } else {
-      track = CreateDOMTrack(aTrackID, aType);
-    }
-    MOZ_ASSERT(mTrackTypesAvailable & HINT_CONTENTS_AUDIO);
-    break;
-  }
-  case MediaSegment::VIDEO: {
-    for (size_t i = 0; i < mTracks.Length(); ++i) {
-      track = mTracks[i]->AsVideoStreamTrack();
-      if (track) {
-        break;
-      }
-    }
-    if (track) {
-      track->BindTrackID(aTrackID);
-    } else {
-      track = CreateDOMTrack(aTrackID, aType);
-    }
-    MOZ_ASSERT(mTrackTypesAvailable & HINT_CONTENTS_VIDEO);
-    break;
-  }
-  default:
-    MOZ_CRASH("Unhandled track type");
-  }
   CheckTracksAvailable();
 
   return track;
 }
 
 MediaStreamTrack*
 DOMMediaStream::GetDOMTrackFor(TrackID aTrackID)
 {
--- a/content/media/DOMMediaStream.h
+++ b/content/media/DOMMediaStream.h
@@ -170,17 +170,17 @@ public:
 
   // Indicate what track types we eventually expect to add to this stream
   enum {
     HINT_CONTENTS_AUDIO = 1 << 0,
     HINT_CONTENTS_VIDEO = 1 << 1,
     HINT_CONTENTS_UNKNOWN = 1 << 2
   };
   TrackTypeHints GetHintContents() const { return mHintContents; }
-  void SetHintContents(TrackTypeHints aHintContents);
+  void SetHintContents(TrackTypeHints aHintContents) { mHintContents = aHintContents; }
 
   TrackTypeHints GetTrackTypesAvailable() const { return mTrackTypesAvailable; }
 
   /**
    * Create an nsDOMMediaStream whose underlying stream is a SourceMediaStream.
    */
   static already_AddRefed<DOMMediaStream>
   CreateSourceStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents);
@@ -192,18 +192,17 @@ public:
   CreateTrackUnionStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents = 0);
 
   void SetLogicalStreamStartTime(StreamTime aTime)
   {
     mLogicalStreamStartTime = aTime;
   }
 
   // Notifications from StreamListener.
-  // BindDOMTrack should only be called when it's safe to run script.
-  MediaStreamTrack* BindDOMTrack(TrackID aTrackID, MediaSegment::Type aType);
+  // CreateDOMTrack should only be called when it's safe to run script.
   MediaStreamTrack* CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType);
   MediaStreamTrack* GetDOMTrackFor(TrackID aTrackID);
 
   class OnTracksAvailableCallback {
   public:
     explicit OnTracksAvailableCallback(uint8_t aExpectedTracks = 0)
       : mExpectedTracks(aExpectedTracks) {}
     virtual ~OnTracksAvailableCallback() {}
--- a/content/media/MediaStreamTrack.h
+++ b/content/media/MediaStreamTrack.h
@@ -34,17 +34,16 @@ public:
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamTrack,
                                            DOMEventTargetHelper)
 
   DOMMediaStream* GetParentObject() const { return mStream; }
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE = 0;
 
   DOMMediaStream* GetStream() const { return mStream; }
   TrackID GetTrackID() const { return mTrackID; }
-  void BindTrackID(TrackID aTrackID) { mTrackID = aTrackID; }
   virtual AudioStreamTrack* AsAudioStreamTrack() { return nullptr; }
   virtual VideoStreamTrack* AsVideoStreamTrack() { return nullptr; }
 
   // WebIDL
   virtual void GetKind(nsAString& aKind) = 0;
   void GetId(nsAString& aID);
   void GetLabel(nsAString& aLabel) { aLabel.Truncate(); }
   bool Enabled() { return mEnabled; }
deleted file mode 100644
--- a/content/media/TrackUnionStream.cpp
+++ /dev/null
@@ -1,372 +0,0 @@
-/* -*- 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/. */
-
-#include "MediaStreamGraphImpl.h"
-#include "mozilla/MathAlgorithms.h"
-#include "mozilla/unused.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
-PRLogModuleInfo* gTrackUnionStreamLog;
-#define STREAM_LOG(type, msg) PR_LOG(gTrackUnionStreamLog, type, msg)
-#else
-#define STREAM_LOG(type, msg)
-#endif
-
-TrackUnionStream::TrackUnionStream(DOMMediaStream* aWrapper) :
-  ProcessedMediaStream(aWrapper),
-  mFilterCallback(nullptr)
-{
-#ifdef PR_LOGGING
-  if (!gTrackUnionStreamLog) {
-    gTrackUnionStreamLog = PR_NewLogModule("TrackUnionStream");
-  }
-#endif
-}
-
-  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);
-  }
-  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);
-      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;
-    }
-  }
-
-  // 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.
-
-  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.
-  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);
-      }
-    }
-  }
-
-  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;
-      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 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 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();
-    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);
-    }
-  }
-}
--- a/content/media/TrackUnionStream.h
+++ b/content/media/TrackUnionStream.h
@@ -6,35 +6,137 @@
 #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);
+  explicit TrackUnionStream(DOMMediaStream* aWrapper) :
+    ProcessedMediaStream(aWrapper),
+    mFilterCallback(nullptr)
+  {}
 
-  virtual void RemoveInput(MediaInputPort* aPort) MOZ_OVERRIDE;
-  virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE;
+  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;
+    }
+  }
 
   // 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 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;
+  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);
+      }
+    }
+  }
 
 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.
@@ -54,20 +156,219 @@ 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);
-  void EndTrack(uint32_t aIndex);
+                    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();
+  }
   void CopyTrackData(StreamBuffer::Track* aInputTrack,
                      uint32_t aMapIndex, GraphTime aFrom, GraphTime aTo,
-                     bool* aOutputTrackFinished);
+                     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);
+    }
+  }
 
   nsTArray<TrackMapEntry> mTrackMap;
 };
 
 }
 
 #endif /* MOZILLA_MEDIASTREAMGRAPH_H_ */
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -159,17 +159,16 @@ 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',