/* -*- 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 "DOMMediaStream.h"
#include "nsContentUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsIScriptError.h"
#include "nsIUUIDGenerator.h"
#include "nsPIDOMWindow.h"
#include "mozilla/TimerClamping.h"
#include "mozilla/dom/MediaStreamBinding.h"
#include "mozilla/dom/MediaStreamTrackEvent.h"
#include "mozilla/dom/LocalMediaStreamBinding.h"
#include "mozilla/dom/AudioNode.h"
#include "AudioChannelAgent.h"
#include "mozilla/dom/AudioTrack.h"
#include "mozilla/dom/AudioTrackList.h"
#include "mozilla/dom/VideoTrack.h"
#include "mozilla/dom/VideoTrackList.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/media/MediaUtils.h"
#include "MediaStreamGraph.h"
#include "AudioStreamTrack.h"
#include "VideoStreamTrack.h"
#include "Layers.h"

// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
// GetTickCount() and conflicts with NS_DECL_NSIDOMMEDIASTREAM, containing
// currentTime getter.
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif

#ifdef LOG
#undef LOG
#endif

// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
// GetTickCount() and conflicts with MediaStream::GetCurrentTime.
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::layers;
using namespace mozilla::media;

static LazyLogModule gMediaStreamLog("MediaStream");
#define LOG(type, msg) MOZ_LOG(gMediaStreamLog, type, msg)

const TrackID TRACK_VIDEO_PRIMARY = 1;

static bool
ContainsLiveTracks(nsTArray<RefPtr<DOMMediaStream::TrackPort>>& aTracks)
{
  for (auto& port : aTracks) {
    if (port->GetTrack()->ReadyState() == MediaStreamTrackState::Live) {
      return true;
    }
  }

  return false;
}

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);
}

DOMMediaStream::TrackPort::~TrackPort()
{
  MOZ_COUNT_DTOR(TrackPort);

  if (mOwnership == InputPortOwnership::OWNED) {
    DestroyInputPort();
  }
}

void
DOMMediaStream::TrackPort::DestroyInputPort()
{
  if (mInputPort) {
    mInputPort->Destroy();
    mInputPort = nullptr;
  }
}

MediaStream*
DOMMediaStream::TrackPort::GetSource() const
{
  return mInputPort ? mInputPort->GetSource() : nullptr;
}

TrackID
DOMMediaStream::TrackPort::GetSourceTrackId() const
{
  return mInputPort ? mInputPort->GetSourceTrackId() : TRACK_INVALID;
}

already_AddRefed<Pledge<bool>>
DOMMediaStream::TrackPort::BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode)
{
  if (mInputPort) {
    return mInputPort->BlockSourceTrackId(aTrackId, aBlockingMode);
  }
  RefPtr<Pledge<bool>> rejected = new Pledge<bool>();
  rejected->Reject(NS_ERROR_FAILURE);
  return rejected.forget();
}

NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::TrackPort, mTrack)
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::TrackPort, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMediaStream::TrackPort, Release)

NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackSourceGetter)
NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackSourceGetter)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackSourceGetter)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_0(MediaStreamTrackSourceGetter)

/**
 * Listener registered on the Owned stream to detect added and ended owned
 * tracks for keeping the list of MediaStreamTracks in sync with the tracks
 * added and ended directly at the source.
 */
class DOMMediaStream::OwnedStreamListener : public MediaStreamListener {
public:
  explicit OwnedStreamListener(DOMMediaStream* aStream)
    : mStream(aStream)
  {}

  void Forget() { mStream = nullptr; }

  void DoNotifyTrackCreated(TrackID aTrackID, MediaSegment::Type aType,
                            MediaStream* aInputStream, TrackID aInputTrackID)
  {
    MOZ_ASSERT(NS_IsMainThread());

    if (!mStream) {
      return;
    }

    MediaStreamTrack* track =
      mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID, aTrackID);

    if (track) {
      LOG(LogLevel::Debug, ("DOMMediaStream %p Track %d from owned stream %p "
                            "bound to MediaStreamTrack %p.",
                            mStream, aTrackID, aInputStream, track));
      return;
    }

    // Track had not been created on main thread before, create it now.
    NS_WARNING_ASSERTION(
      !mStream->mTracks.IsEmpty(),
      "A new track was detected on the input stream; creating a corresponding "
      "MediaStreamTrack. Initial tracks should be added manually to "
      "immediately and synchronously be available to JS.");
    RefPtr<MediaStreamTrackSource> source;
    if (mStream->mTrackSourceGetter) {
      source = mStream->mTrackSourceGetter->GetMediaStreamTrackSource(aTrackID);
    }
    if (!source) {
      NS_ASSERTION(false, "Dynamic track created without an explicit TrackSource");
      nsPIDOMWindowInner* window = mStream->GetParentObject();
      nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
      nsIPrincipal* principal = doc ? doc->NodePrincipal() : nullptr;
      source = new BasicTrackSource(principal);
    }

    RefPtr<MediaStreamTrack> newTrack =
      mStream->CreateDOMTrack(aTrackID, aType, source);
    NS_DispatchToMainThread(NewRunnableMethod<RefPtr<MediaStreamTrack>>(
        mStream, &DOMMediaStream::AddTrackInternal, newTrack));
  }

  void DoNotifyTrackEnded(MediaStream* aInputStream, TrackID aInputTrackID,
                          TrackID aTrackID)
  {
    MOZ_ASSERT(NS_IsMainThread());

    if (!mStream) {
      return;
    }

    RefPtr<MediaStreamTrack> track =
      mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID, aTrackID);
    NS_ASSERTION(track, "Owned MediaStreamTracks must be known by the DOMMediaStream");
    if (track) {
      LOG(LogLevel::Debug, ("DOMMediaStream %p MediaStreamTrack %p ended at the source. Marking it ended.",
                            mStream, track.get()));
      NS_DispatchToMainThread(NewRunnableMethod(
        track, &MediaStreamTrack::OverrideEnded));
    }
  }

  void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
                                StreamTime aTrackOffset, TrackEventCommand aTrackEvents,
                                const MediaSegment& aQueuedMedia,
                                MediaStream* aInputStream,
                                TrackID aInputTrackID) override
  {
    if (aTrackEvents & TrackEventCommand::TRACK_EVENT_CREATED) {
      nsCOMPtr<nsIRunnable> runnable =
        NewRunnableMethod<TrackID, MediaSegment::Type, RefPtr<MediaStream>, TrackID>(
          this, &OwnedStreamListener::DoNotifyTrackCreated,
          aID, aQueuedMedia.GetType(), aInputStream, aInputTrackID);
      aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
    } else if (aTrackEvents & TrackEventCommand::TRACK_EVENT_ENDED) {
      nsCOMPtr<nsIRunnable> runnable =
        NewRunnableMethod<RefPtr<MediaStream>, TrackID, TrackID>(
          this, &OwnedStreamListener::DoNotifyTrackEnded,
          aInputStream, aInputTrackID, aID);
      aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
    }
  }

private:
  // These fields may only be accessed on the main thread
  DOMMediaStream* mStream;
};

/**
 * Listener registered on the Playback stream to detect when tracks end and when
 * all new tracks this iteration have been created - for when several tracks are
 * queued by the source and committed all at once.
 */
class DOMMediaStream::PlaybackStreamListener : public MediaStreamListener {
public:
  explicit PlaybackStreamListener(DOMMediaStream* aStream)
    : mStream(aStream)
  {}

  void Forget()
  {
    MOZ_ASSERT(NS_IsMainThread());
    mStream = nullptr;
  }

  void DoNotifyFinishedTrackCreation()
  {
    MOZ_ASSERT(NS_IsMainThread());

    if (!mStream) {
      return;
    }

    // The owned stream listener adds its tracks after another main thread
    // dispatch. We have to do the same to notify of created tracks to stay
    // in sync. (Or NotifyTracksCreated is called before tracks are added).
    NS_DispatchToMainThread(
        NewRunnableMethod(mStream, &DOMMediaStream::NotifyTracksCreated));
  }

  void DoNotifyFinished()
  {
    MOZ_ASSERT(NS_IsMainThread());

    if (!mStream) {
      return;
    }

    NS_DispatchToMainThread(NewRunnableMethod(
      mStream, &DOMMediaStream::NotifyFinished));
  }

  // The methods below are called on the MediaStreamGraph thread.

  void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override
  {
    nsCOMPtr<nsIRunnable> runnable =
      NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinishedTrackCreation);
    aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
  }


  void NotifyEvent(MediaStreamGraph* aGraph,
                   MediaStreamGraphEvent event) override
  {
    if (event == MediaStreamGraphEvent::EVENT_FINISHED) {
      aGraph->DispatchToMainThreadAfterStreamStateUpdate(
        NewRunnableMethod(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) {}

  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PlaybackTrackListener,
                                           MediaStreamTrackConsumer)

  void NotifyEnded(MediaStreamTrack* aTrack) override
  {
    if (!mStream) {
      MOZ_ASSERT(false);
      return;
    }

    if (!aTrack) {
      MOZ_ASSERT(false);
      return;
    }

    MOZ_ASSERT(mStream->HasTrack(*aTrack));
    mStream->NotifyTrackRemoved(aTrack);
  }

protected:
  virtual ~PlaybackTrackListener() {}

  RefPtr<DOMMediaStream> mStream;
};

NS_IMPL_ADDREF_INHERITED(DOMMediaStream::PlaybackTrackListener,
                         MediaStreamTrackConsumer)
NS_IMPL_RELEASE_INHERITED(DOMMediaStream::PlaybackTrackListener,
                          MediaStreamTrackConsumer)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMMediaStream::PlaybackTrackListener)
NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackConsumer)
NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMMediaStream::PlaybackTrackListener,
                                   MediaStreamTrackConsumer,
                                   mStream)

NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMMediaStream,
                                                DOMEventTargetHelper)
  tmp->Destroy();
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwnedTracks)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTracks)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumersToKeepAlive)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTrackSourceGetter)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaybackTrackListener)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMMediaStream,
                                                  DOMEventTargetHelper)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwnedTracks)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumersToKeepAlive)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTrackSourceGetter)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaybackTrackListener)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_ADDREF_INHERITED(DOMMediaStream, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(DOMMediaStream, DOMEventTargetHelper)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMMediaStream)
  NS_INTERFACE_MAP_ENTRY(DOMMediaStream)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

NS_IMPL_ADDREF_INHERITED(DOMLocalMediaStream, DOMMediaStream)
NS_IMPL_RELEASE_INHERITED(DOMLocalMediaStream, DOMMediaStream)

NS_INTERFACE_MAP_BEGIN(DOMLocalMediaStream)
  NS_INTERFACE_MAP_ENTRY(DOMLocalMediaStream)
NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)

NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream,
                                   mStreamNode)

NS_IMPL_ADDREF_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)
NS_IMPL_RELEASE_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream)
NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)

DOMMediaStream::DOMMediaStream(nsPIDOMWindowInner* aWindow,
                               MediaStreamTrackSourceGetter* aTrackSourceGetter)
  : mLogicalStreamStartTime(0), mWindow(aWindow),
    mInputStream(nullptr), mOwnedStream(nullptr), mPlaybackStream(nullptr),
    mTracksPendingRemoval(0), mTrackSourceGetter(aTrackSourceGetter),
    mPlaybackTrackListener(MakeAndAddRef<PlaybackTrackListener>(this)),
    mTracksCreated(false), mNotifiedOfMediaStreamGraphShutdown(false),
    mActive(false), mSetInactiveOnFinish(false)
{
  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));
    rv = uuidgen->GenerateUUIDInPlace(&uuid);
    if (NS_SUCCEEDED(rv)) {
      char buffer[NSID_LENGTH];
      uuid.ToProvidedString(buffer);
      mID = NS_ConvertASCIItoUTF16(buffer);
    }
  }
}

DOMMediaStream::~DOMMediaStream()
{
  Destroy();
}

void
DOMMediaStream::Destroy()
{
  LOG(LogLevel::Debug, ("DOMMediaStream %p Being destroyed.", this));
  if (mOwnedListener) {
    mOwnedListener->Forget();
    mOwnedListener = nullptr;
  }
  if (mPlaybackListener) {
    mPlaybackListener->Forget();
    mPlaybackListener = nullptr;
  }
  for (const RefPtr<TrackPort>& info : mTracks) {
    // We must remove ourselves from each track's principal change observer list
    // before we die. CC may have cleared info->mTrack so guard against it.
    MediaStreamTrack* track = info->GetTrack();
    if (track) {
      track->RemovePrincipalChangeObserver(this);
      if (!track->Ended()) {
        track->RemoveConsumer(mPlaybackTrackListener);
      }
    }
  }
  if (mPlaybackPort) {
    mPlaybackPort->Destroy();
    mPlaybackPort = nullptr;
  }
  if (mOwnedPort) {
    mOwnedPort->Destroy();
    mOwnedPort = nullptr;
  }
  if (mPlaybackStream) {
    mPlaybackStream->UnregisterUser();
    mPlaybackStream = nullptr;
  }
  if (mOwnedStream) {
    mOwnedStream->UnregisterUser();
    mOwnedStream = nullptr;
  }
  if (mInputStream) {
    mInputStream->UnregisterUser();
    mInputStream = nullptr;
  }
  mRunOnTracksAvailable.Clear();
  mTrackListeners.Clear();
}

JSObject*
DOMMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
  return dom::MediaStreamBinding::Wrap(aCx, this, aGivenProto);
}

/* static */ already_AddRefed<DOMMediaStream>
DOMMediaStream::Constructor(const GlobalObject& aGlobal,
                            ErrorResult& aRv)
{
  Sequence<OwningNonNull<MediaStreamTrack>> emptyTrackSeq;
  return Constructor(aGlobal, emptyTrackSeq, aRv);
}

/* static */ already_AddRefed<DOMMediaStream>
DOMMediaStream::Constructor(const GlobalObject& aGlobal,
                            const DOMMediaStream& aStream,
                            ErrorResult& aRv)
{
  nsTArray<RefPtr<MediaStreamTrack>> tracks;
  aStream.GetTracks(tracks);

  Sequence<OwningNonNull<MediaStreamTrack>> nonNullTrackSeq;
  if (!nonNullTrackSeq.SetLength(tracks.Length(), fallible)) {
    MOZ_ASSERT(false);
    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    return nullptr;
  }

  for (size_t i = 0; i < tracks.Length(); ++i) {
    nonNullTrackSeq[i] = tracks[i];
  }

  return Constructor(aGlobal, nonNullTrackSeq, aRv);
}

/* static */ already_AddRefed<DOMMediaStream>
DOMMediaStream::Constructor(const GlobalObject& aGlobal,
                            const Sequence<OwningNonNull<MediaStreamTrack>>& aTracks,
                            ErrorResult& aRv)
{
  nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
  if (!ownerWindow) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  // Streams created from JS cannot have dynamically created tracks.
  MediaStreamTrackSourceGetter* getter = nullptr;
  RefPtr<DOMMediaStream> newStream = new DOMMediaStream(ownerWindow, getter);

  for (MediaStreamTrack& track : aTracks) {
    if (!newStream->GetPlaybackStream()) {
      MOZ_RELEASE_ASSERT(track.Graph());
      newStream->InitPlaybackStreamCommon(track.Graph());
    }
    newStream->AddTrack(track);
  }

  if (!newStream->GetPlaybackStream()) {
    MOZ_ASSERT(aTracks.IsEmpty());
    MediaStreamGraph* graph =
      MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER,
                                    AudioChannel::Normal);
    newStream->InitPlaybackStreamCommon(graph);
  }

  return newStream.forget();
}

double
DOMMediaStream::CurrentTime()
{
  if (!mPlaybackStream) {
    return 0.0;
  }
  return TimerClamping::ReduceSTimeValue(mPlaybackStream->
    StreamTimeToSeconds(mPlaybackStream->GetCurrentTime() - mLogicalStreamStartTime));
}

void
DOMMediaStream::GetId(nsAString& aID) const
{
  aID = mID;
}

void
DOMMediaStream::GetAudioTracks(nsTArray<RefPtr<AudioStreamTrack> >& aTracks) const
{
  for (const RefPtr<TrackPort>& info : mTracks) {
    AudioStreamTrack* t = info->GetTrack()->AsAudioStreamTrack();
    if (t) {
      aTracks.AppendElement(t);
    }
  }
}

void
DOMMediaStream::GetVideoTracks(nsTArray<RefPtr<VideoStreamTrack> >& aTracks) const
{
  for (const RefPtr<TrackPort>& info : mTracks) {
    VideoStreamTrack* t = info->GetTrack()->AsVideoStreamTrack();
    if (t) {
      aTracks.AppendElement(t);
    }
  }
}

void
DOMMediaStream::GetTracks(nsTArray<RefPtr<MediaStreamTrack> >& aTracks) const
{
  for (const RefPtr<TrackPort>& info : mTracks) {
    aTracks.AppendElement(info->GetTrack());
  }
}

void
DOMMediaStream::AddTrack(MediaStreamTrack& aTrack)
{
  MOZ_RELEASE_ASSERT(mPlaybackStream);

  RefPtr<ProcessedMediaStream> dest = mPlaybackStream->AsProcessedStream();
  MOZ_ASSERT(dest);
  if (!dest) {
    return;
  }

  LOG(LogLevel::Info, ("DOMMediaStream %p Adding track %p (from stream %p with ID %d)",
                       this, &aTrack, aTrack.mOwningStream.get(), aTrack.mTrackID));

  if (mPlaybackStream->Graph() != aTrack.Graph()) {
    NS_ASSERTION(false, "Cannot combine tracks from different MediaStreamGraphs");
    LOG(LogLevel::Error, ("DOMMediaStream %p Own MSG %p != aTrack's MSG %p",
                         this, mPlaybackStream->Graph(), aTrack.Graph()));

    nsAutoString trackId;
    aTrack.GetId(trackId);
    const char16_t* params[] = { trackId.get() };
    nsCOMPtr<nsPIDOMWindowInner> pWindow = GetParentObject();
    nsIDocument* document = pWindow ? pWindow->GetExtantDoc() : nullptr;
    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
                                    NS_LITERAL_CSTRING("Media"),
                                    document,
                                    nsContentUtils::eDOM_PROPERTIES,
                                    "MediaStreamAddTrackDifferentAudioChannel",
                                    params, ArrayLength(params));
    return;
  }

  if (HasTrack(aTrack)) {
    LOG(LogLevel::Debug, ("DOMMediaStream %p already contains track %p", this, &aTrack));
    return;
  }

  // Hook up the underlying track with our underlying playback stream.
  RefPtr<MediaInputPort> inputPort =
    GetPlaybackStream()->AllocateInputPort(aTrack.GetOwnedStream(),
                                           aTrack.mTrackID);
  RefPtr<TrackPort> trackPort =
    new TrackPort(inputPort, &aTrack, TrackPort::InputPortOwnership::OWNED);
  mTracks.AppendElement(trackPort.forget());
  NotifyTrackAdded(&aTrack);

  LOG(LogLevel::Debug, ("DOMMediaStream %p Added track %p", this, &aTrack));
}

void
DOMMediaStream::RemoveTrack(MediaStreamTrack& aTrack)
{
  LOG(LogLevel::Info, ("DOMMediaStream %p Removing track %p (from stream %p with ID %d)",
                       this, &aTrack, aTrack.mOwningStream.get(), aTrack.mTrackID));

  RefPtr<TrackPort> toRemove = FindPlaybackTrackPort(aTrack);
  if (!toRemove) {
    LOG(LogLevel::Debug, ("DOMMediaStream %p does not contain track %p", this, &aTrack));
    return;
  }

  DebugOnly<bool> removed = mTracks.RemoveElement(toRemove);
  NS_ASSERTION(removed, "If there's a track port we should be able to remove it");

  // If the track comes from a TRACK_ANY input port (i.e., mOwnedPort), we need
  // to block it in the port. Doing this for a locked track is still OK as it
  // will first block the track, then destroy the port. Both cause the track to
  // end.
  // If the track has already ended, it's input port might be gone, so in those
  // cases blocking the underlying track should be avoided.
  if (!aTrack.Ended()) {
    BlockPlaybackTrack(toRemove);
    NotifyTrackRemoved(&aTrack);
  }

  LOG(LogLevel::Debug, ("DOMMediaStream %p Removed track %p", this, &aTrack));
}

class ClonedStreamSourceGetter :
  public MediaStreamTrackSourceGetter
{
public:
  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ClonedStreamSourceGetter,
                                           MediaStreamTrackSourceGetter)

  explicit ClonedStreamSourceGetter(DOMMediaStream* aStream)
    : mStream(aStream) {}

  already_AddRefed<MediaStreamTrackSource>
  GetMediaStreamTrackSource(TrackID aInputTrackID) override
  {
    MediaStreamTrack* sourceTrack =
      mStream->FindOwnedDOMTrack(mStream->GetOwnedStream(), aInputTrackID);
    MOZ_RELEASE_ASSERT(sourceTrack);

    return do_AddRef(&sourceTrack->GetSource());
  }

protected:
  virtual ~ClonedStreamSourceGetter() {}

  RefPtr<DOMMediaStream> mStream;
};

NS_IMPL_ADDREF_INHERITED(ClonedStreamSourceGetter,
                         MediaStreamTrackSourceGetter)
NS_IMPL_RELEASE_INHERITED(ClonedStreamSourceGetter,
                          MediaStreamTrackSourceGetter)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ClonedStreamSourceGetter)
NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSourceGetter)
NS_IMPL_CYCLE_COLLECTION_INHERITED(ClonedStreamSourceGetter,
                                   MediaStreamTrackSourceGetter,
                                   mStream)

already_AddRefed<DOMMediaStream>
DOMMediaStream::Clone()
{
  return CloneInternal(TrackForwardingOption::CURRENT);
}

already_AddRefed<DOMMediaStream>
DOMMediaStream::CloneInternal(TrackForwardingOption aForwarding)
{
  RefPtr<DOMMediaStream> newStream =
    new DOMMediaStream(GetParentObject(), new ClonedStreamSourceGetter(this));

  LOG(LogLevel::Info, ("DOMMediaStream %p created clone %p, forwarding %s tracks",
                       this, newStream.get(),
                       aForwarding == TrackForwardingOption::ALL
                         ? "all" : "current"));

  MOZ_RELEASE_ASSERT(mPlaybackStream);
  MOZ_RELEASE_ASSERT(mPlaybackStream->Graph());
  MediaStreamGraph* graph = mPlaybackStream->Graph();

  // We initiate the owned and playback streams first, since we need to create
  // all existing DOM tracks before we add the generic input port from
  // mInputStream to mOwnedStream (see AllocateInputPort wrt. destination
  // TrackID as to why).
  newStream->InitOwnedStreamCommon(graph);
  newStream->InitPlaybackStreamCommon(graph);

  // Set up existing DOM tracks.
  TrackID allocatedTrackID = 1;
  for (const RefPtr<TrackPort>& info : mTracks) {
    MediaStreamTrack& track = *info->GetTrack();

    LOG(LogLevel::Debug, ("DOMMediaStream %p forwarding external track %p to clone %p",
                          this, &track, newStream.get()));
    RefPtr<MediaStreamTrack> trackClone =
      newStream->CloneDOMTrack(track, allocatedTrackID++);
  }

  if (aForwarding == TrackForwardingOption::ALL) {
    // Set up an input port from our input stream to the new DOM stream's owned
    // stream, to allow for dynamically added tracks at the source to appear in
    // the clone. The clone may treat mInputStream as its own mInputStream but
    // ownership remains with us.
    newStream->mInputStream = mInputStream;
    if (mInputStream) {
      // We have already set up track-locked input ports for all existing DOM
      // tracks, so now we need to block those in the generic input port to
      // avoid ending up with double instances of them.
      nsTArray<TrackID> tracksToBlock;
      for (const RefPtr<TrackPort>& info : mOwnedTracks) {
        tracksToBlock.AppendElement(info->GetTrack()->mTrackID);
      }

      newStream->mInputStream->RegisterUser();
      newStream->mOwnedPort =
        newStream->mOwnedStream->AllocateInputPort(mInputStream,
                                                   TRACK_ANY, TRACK_ANY, 0, 0,
                                                   &tracksToBlock);
    }
  }

  return newStream.forget();
}

bool
DOMMediaStream::Active() const
{
  return mActive;
}

MediaStreamTrack*
DOMMediaStream::GetTrackById(const nsAString& aId) const
{
  for (const RefPtr<TrackPort>& info : mTracks) {
    nsString id;
    info->GetTrack()->GetId(id);
    if (id == aId) {
      return info->GetTrack();
    }
  }
  return nullptr;
}

MediaStreamTrack*
DOMMediaStream::GetOwnedTrackById(const nsAString& aId)
{
  for (const RefPtr<TrackPort>& info : mOwnedTracks) {
    nsString id;
    info->GetTrack()->GetId(id);
    if (id == aId) {
      return info->GetTrack();
    }
  }
  return nullptr;
}

bool
DOMMediaStream::HasTrack(const MediaStreamTrack& aTrack) const
{
  return !!FindPlaybackTrackPort(aTrack);
}

bool
DOMMediaStream::OwnsTrack(const MediaStreamTrack& aTrack) const
{
  return !!FindOwnedTrackPort(aTrack);
}

bool
DOMMediaStream::AddDirectListener(DirectMediaStreamListener* aListener)
{
  if (GetInputStream() && GetInputStream()->AsSourceStream()) {
    GetInputStream()->AsSourceStream()->AddDirectListener(aListener);
    return true; // application should ignore NotifyQueuedTrackData
  }
  return false;
}

void
DOMMediaStream::RemoveDirectListener(DirectMediaStreamListener* aListener)
{
  if (GetInputStream() && GetInputStream()->AsSourceStream()) {
    GetInputStream()->AsSourceStream()->RemoveDirectListener(aListener);
  }
}

bool
DOMMediaStream::IsFinished() const
{
  return !mPlaybackStream || mPlaybackStream->IsFinished();
}

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);
  InitOwnedStreamCommon(aGraph);
  InitPlaybackStreamCommon(aGraph);
}

void
DOMMediaStream::InitAudioCaptureStream(nsIPrincipal* aPrincipal, MediaStreamGraph* aGraph)
{
  const TrackID AUDIO_TRACK = 1;

  RefPtr<BasicTrackSource> audioCaptureSource =
    new BasicTrackSource(aPrincipal, MediaSourceEnum::AudioCapture);

  AudioCaptureStream* audioCaptureStream =
    static_cast<AudioCaptureStream*>(aGraph->CreateAudioCaptureStream(AUDIO_TRACK));
  InitInputStreamCommon(audioCaptureStream, aGraph);
  InitOwnedStreamCommon(aGraph);
  InitPlaybackStreamCommon(aGraph);
  RefPtr<MediaStreamTrack> track =
    CreateDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, audioCaptureSource);
  AddTrackInternal(track);

  audioCaptureStream->Start();
}

void
DOMMediaStream::InitInputStreamCommon(MediaStream* aStream,
                                      MediaStreamGraph* aGraph)
{
  MOZ_ASSERT(!mOwnedStream, "Input stream must be initialized before owned stream");

  mInputStream = aStream;
  mInputStream->RegisterUser();
}

void
DOMMediaStream::InitOwnedStreamCommon(MediaStreamGraph* aGraph)
{
  MOZ_ASSERT(!mPlaybackStream, "Owned stream must be initialized before playback stream");

  mOwnedStream = aGraph->CreateTrackUnionStream();
  mOwnedStream->SetAutofinish(true);
  mOwnedStream->RegisterUser();
  if (mInputStream) {
    mOwnedPort = mOwnedStream->AllocateInputPort(mInputStream);
  }

  // Setup track listeners
  mOwnedListener = new OwnedStreamListener(this);
  mOwnedStream->AddListener(mOwnedListener);
}

void
DOMMediaStream::InitPlaybackStreamCommon(MediaStreamGraph* aGraph)
{
  mPlaybackStream = aGraph->CreateTrackUnionStream();
  mPlaybackStream->SetAutofinish(true);
  mPlaybackStream->RegisterUser();
  if (mOwnedStream) {
    mPlaybackPort = mPlaybackStream->AllocateInputPort(mOwnedStream);
  }

  mPlaybackListener = new PlaybackStreamListener(this);
  mPlaybackStream->AddListener(mPlaybackListener);

  LOG(LogLevel::Debug, ("DOMMediaStream %p Initiated with mInputStream=%p, mOwnedStream=%p, mPlaybackStream=%p",
                        this, mInputStream, mOwnedStream, mPlaybackStream));
}

already_AddRefed<DOMMediaStream>
DOMMediaStream::CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow,
                                          MediaStreamGraph* aGraph,
                                          MediaStreamTrackSourceGetter* aTrackSourceGetter)
{
  RefPtr<DOMMediaStream> stream = new DOMMediaStream(aWindow, aTrackSourceGetter);
  stream->InitSourceStream(aGraph);
  return stream.forget();
}

already_AddRefed<DOMMediaStream>
DOMMediaStream::CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow,
                                              MediaStreamGraph* aGraph,
                                              MediaStreamTrackSourceGetter* aTrackSourceGetter)
{
  RefPtr<DOMMediaStream> stream = new DOMMediaStream(aWindow, aTrackSourceGetter);
  stream->InitTrackUnionStream(aGraph);
  return stream.forget();
}

already_AddRefed<DOMMediaStream>
DOMMediaStream::CreateAudioCaptureStreamAsInput(nsPIDOMWindowInner* aWindow,
                                                nsIPrincipal* aPrincipal,
                                                MediaStreamGraph* aGraph)
{
  // Audio capture doesn't create tracks dynamically
  MediaStreamTrackSourceGetter* getter = nullptr;
  RefPtr<DOMMediaStream> stream = new DOMMediaStream(aWindow, getter);
  stream->InitAudioCaptureStream(aPrincipal, aGraph);
  return stream.forget();
}

void
DOMMediaStream::PrincipalChanged(MediaStreamTrack* aTrack)
{
  MOZ_ASSERT(aTrack);
  NS_ASSERTION(HasTrack(*aTrack), "Principal changed for an unknown track");
  LOG(LogLevel::Info, ("DOMMediaStream %p Principal changed for track %p",
                       this, aTrack));
  RecomputePrincipal();
}

void
DOMMediaStream::RecomputePrincipal()
{
  nsCOMPtr<nsIPrincipal> previousPrincipal = mPrincipal.forget();
  nsCOMPtr<nsIPrincipal> previousVideoPrincipal = mVideoPrincipal.forget();

  if (mTracksPendingRemoval > 0) {
    LOG(LogLevel::Info, ("DOMMediaStream %p RecomputePrincipal() Cannot "
                         "recompute stream principal with tracks pending "
                         "removal.", this));
    return;
  }

  LOG(LogLevel::Debug, ("DOMMediaStream %p Recomputing principal. "
                        "Old principal was %p.", this, previousPrincipal.get()));

  // mPrincipal is recomputed based on all current tracks, and tracks that have
  // not ended in our playback stream.
  for (const RefPtr<TrackPort>& info : mTracks) {
    if (info->GetTrack()->Ended()) {
      continue;
    }
    LOG(LogLevel::Debug, ("DOMMediaStream %p Taking live track %p with "
                          "principal %p into account.", this,
                          info->GetTrack(), info->GetTrack()->GetPrincipal()));
    nsContentUtils::CombineResourcePrincipals(&mPrincipal,
                                              info->GetTrack()->GetPrincipal());
    if (info->GetTrack()->AsVideoStreamTrack()) {
      nsContentUtils::CombineResourcePrincipals(&mVideoPrincipal,
                                                info->GetTrack()->GetPrincipal());
    }
  }

  LOG(LogLevel::Debug, ("DOMMediaStream %p new principal is %p.",
                        this, mPrincipal.get()));

  if (previousPrincipal != mPrincipal ||
      previousVideoPrincipal != mVideoPrincipal) {
    NotifyPrincipalChanged();
  }
}

void
DOMMediaStream::NotifyPrincipalChanged()
{
  if (!mPrincipal) {
    // When all tracks are removed, mPrincipal will change to nullptr.
    LOG(LogLevel::Info, ("DOMMediaStream %p Principal changed to nothing.",
                         this));
  } else {
    LOG(LogLevel::Info, ("DOMMediaStream %p Principal changed. Now: "
                         "null=%d, codebase=%d, expanded=%d, system=%d", this,
                          mPrincipal->GetIsNullPrincipal(),
                          mPrincipal->GetIsCodebasePrincipal(),
                          mPrincipal->GetIsExpandedPrincipal(),
                          mPrincipal->GetIsSystemPrincipal()));
  }

  for (uint32_t i = 0; i < mPrincipalChangeObservers.Length(); ++i) {
    mPrincipalChangeObservers[i]->PrincipalChanged(this);
  }
}


bool
DOMMediaStream::AddPrincipalChangeObserver(
  PrincipalChangeObserver<DOMMediaStream>* aObserver)
{
  return mPrincipalChangeObservers.AppendElement(aObserver) != nullptr;
}

bool
DOMMediaStream::RemovePrincipalChangeObserver(
  PrincipalChangeObserver<DOMMediaStream>* aObserver)
{
  return mPrincipalChangeObservers.RemoveElement(aObserver);
}

void
DOMMediaStream::AddTrackInternal(MediaStreamTrack* aTrack)
{
  MOZ_ASSERT(aTrack->mOwningStream == this);
  MOZ_ASSERT(FindOwnedDOMTrack(aTrack->GetInputStream(),
                               aTrack->mInputTrackID,
                               aTrack->mTrackID));
  MOZ_ASSERT(!FindPlaybackDOMTrack(aTrack->GetOwnedStream(),
                                   aTrack->mTrackID));

  LOG(LogLevel::Debug, ("DOMMediaStream %p Adding owned track %p", this, aTrack));

  mTracks.AppendElement(
    new TrackPort(mPlaybackPort, aTrack, TrackPort::InputPortOwnership::EXTERNAL));

  NotifyTrackAdded(aTrack);

  DispatchTrackEvent(NS_LITERAL_STRING("addtrack"), aTrack);
}

already_AddRefed<MediaStreamTrack>
DOMMediaStream::CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType,
                               MediaStreamTrackSource* aSource,
                               const MediaTrackConstraints& aConstraints)
{
  MOZ_RELEASE_ASSERT(mInputStream);
  MOZ_RELEASE_ASSERT(mOwnedStream);

  MOZ_ASSERT(FindOwnedDOMTrack(GetInputStream(), aTrackID) == nullptr);

  RefPtr<MediaStreamTrack> track;
  switch (aType) {
  case MediaSegment::AUDIO:
    track = new AudioStreamTrack(this, aTrackID, aTrackID, aSource, aConstraints);
    break;
  case MediaSegment::VIDEO:
    track = new VideoStreamTrack(this, aTrackID, aTrackID, aSource, aConstraints);
    break;
  default:
    MOZ_CRASH("Unhandled track type");
  }

  LOG(LogLevel::Debug, ("DOMMediaStream %p Created new track %p with ID %u",
                        this, track.get(), aTrackID));

  mOwnedTracks.AppendElement(
    new TrackPort(mOwnedPort, track, TrackPort::InputPortOwnership::EXTERNAL));

  return track.forget();
}

already_AddRefed<MediaStreamTrack>
DOMMediaStream::CloneDOMTrack(MediaStreamTrack& aTrack,
                              TrackID aCloneTrackID)
{
  MOZ_RELEASE_ASSERT(mOwnedStream);
  MOZ_RELEASE_ASSERT(mPlaybackStream);
  MOZ_RELEASE_ASSERT(IsTrackIDExplicit(aCloneTrackID));

  TrackID inputTrackID = aTrack.mInputTrackID;
  MediaStream* inputStream = aTrack.GetInputStream();

  RefPtr<MediaStreamTrack> newTrack = aTrack.CloneInternal(this, aCloneTrackID);

  newTrack->mOriginalTrack =
    aTrack.mOriginalTrack ? aTrack.mOriginalTrack.get() : &aTrack;

  LOG(LogLevel::Debug, ("DOMMediaStream %p Created new track %p cloned from stream %p track %d",
                        this, newTrack.get(), inputStream, inputTrackID));

  RefPtr<MediaInputPort> inputPort =
    mOwnedStream->AllocateInputPort(inputStream, inputTrackID, aCloneTrackID);

  mOwnedTracks.AppendElement(
    new TrackPort(inputPort, newTrack, TrackPort::InputPortOwnership::OWNED));

  mTracks.AppendElement(
    new TrackPort(mPlaybackPort, newTrack, TrackPort::InputPortOwnership::EXTERNAL));

  NotifyTrackAdded(newTrack);

  newTrack->SetEnabled(aTrack.Enabled());
  newTrack->SetReadyState(aTrack.ReadyState());

  if (aTrack.Ended()) {
    // For extra suspenders, make sure that we don't forward data by mistake
    // to the clone when the original has already ended.
    // We only block END_EXISTING to allow any pending clones to end.
    RefPtr<Pledge<bool, nsresult>> blockingPledge =
      inputPort->BlockSourceTrackId(inputTrackID,
                                    BlockingMode::END_EXISTING);
    Unused << blockingPledge;
  }

  return newTrack.forget();
}

static DOMMediaStream::TrackPort*
FindTrackPortAmongTracks(const MediaStreamTrack& aTrack,
                         const nsTArray<RefPtr<DOMMediaStream::TrackPort>>& aTracks)
{
  for (const RefPtr<DOMMediaStream::TrackPort>& info : aTracks) {
    if (info->GetTrack() == &aTrack) {
      return info;
    }
  }
  return nullptr;
}

MediaStreamTrack*
DOMMediaStream::FindOwnedDOMTrack(MediaStream* aInputStream,
                                  TrackID aInputTrackID,
                                  TrackID aTrackID) const
{
  MOZ_RELEASE_ASSERT(mOwnedStream);

  for (const RefPtr<TrackPort>& info : mOwnedTracks) {
    if (info->GetInputPort() &&
        info->GetInputPort()->GetSource() == aInputStream &&
        info->GetTrack()->mInputTrackID == aInputTrackID &&
        (aTrackID == TRACK_ANY || info->GetTrack()->mTrackID == aTrackID)) {
      // This track is owned externally but in our playback stream.
      return info->GetTrack();
    }
  }
  return nullptr;
}

DOMMediaStream::TrackPort*
DOMMediaStream::FindOwnedTrackPort(const MediaStreamTrack& aTrack) const
{
  return FindTrackPortAmongTracks(aTrack, mOwnedTracks);
}


MediaStreamTrack*
DOMMediaStream::FindPlaybackDOMTrack(MediaStream* aInputStream, TrackID aInputTrackID) const
{
  if (!mPlaybackStream) {
    // One would think we can assert mPlaybackStream here, but track clones have
    // a dummy DOMMediaStream that doesn't have a playback stream, so we can't.
    return nullptr;
  }

  for (const RefPtr<TrackPort>& info : mTracks) {
    if (info->GetInputPort() == mPlaybackPort &&
        aInputStream == mOwnedStream &&
        info->GetTrack()->mInputTrackID == aInputTrackID) {
      // This track is in our owned and playback streams.
      return info->GetTrack();
    }
    if (info->GetInputPort() &&
        info->GetInputPort()->GetSource() == aInputStream &&
        info->GetSourceTrackId() == aInputTrackID) {
      // This track is owned externally but in our playback stream.
      MOZ_ASSERT(IsTrackIDExplicit(aInputTrackID));
      return info->GetTrack();
    }
  }
  return nullptr;
}

DOMMediaStream::TrackPort*
DOMMediaStream::FindPlaybackTrackPort(const MediaStreamTrack& aTrack) const
{
  return FindTrackPortAmongTracks(aTrack, mTracks);
}

void
DOMMediaStream::OnTracksAvailable(OnTracksAvailableCallback* aRunnable)
{
  if (mNotifiedOfMediaStreamGraphShutdown) {
    // No more tracks will ever be added, so just delete the callback now.
    delete aRunnable;
    return;
  }
  mRunOnTracksAvailable.AppendElement(aRunnable);
  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();
  }
}

void
DOMMediaStream::NotifyInactive()
{
  LOG(LogLevel::Info, ("DOMMediaStream %p NotifyInactive(). ", this));

  MOZ_ASSERT(!mActive);
  for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
    mTrackListeners[i]->NotifyInactive();
  }
}

void
DOMMediaStream::CheckTracksAvailable()
{
  if (!mTracksCreated) {
    return;
  }
  nsTArray<nsAutoPtr<OnTracksAvailableCallback> > callbacks;
  callbacks.SwapElements(mRunOnTracksAvailable);

  for (uint32_t i = 0; i < callbacks.Length(); ++i) {
    callbacks[i]->NotifyTracksAvailable(this);
  }
}

void
DOMMediaStream::RegisterTrackListener(TrackListener* aListener)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (mNotifiedOfMediaStreamGraphShutdown) {
    // No more tracks will ever be added, so just do nothing.
    return;
  }
  mTrackListeners.AppendElement(aListener);
}

void
DOMMediaStream::UnregisterTrackListener(TrackListener* aListener)
{
  MOZ_ASSERT(NS_IsMainThread());
  mTrackListeners.RemoveElement(aListener);
}

void
DOMMediaStream::NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (mTracksPendingRemoval > 0) {
    // If there are tracks pending removal we may not degrade the current
    // principals until those tracks have been confirmed removed from the
    // playback stream. Instead combine with the new track and the (potentially)
    // degraded principal will be calculated when it's safe.
    nsContentUtils::CombineResourcePrincipals(&mPrincipal,
                                              aTrack->GetPrincipal());
    LOG(LogLevel::Debug, ("DOMMediaStream %p saw a track get added. Combining "
                          "its principal %p into our while waiting for pending "
                          "tracks to be removed. New principal is %p.",
                          this, aTrack->GetPrincipal(), mPrincipal.get()));
    if (aTrack->AsVideoStreamTrack()) {
      nsContentUtils::CombineResourcePrincipals(&mVideoPrincipal,
                                                aTrack->GetPrincipal());
    }
  } else {
    LOG(LogLevel::Debug, ("DOMMediaStream %p saw a track get added. "
                          "Recomputing principal.", this));
    RecomputePrincipal();
  }

  aTrack->AddPrincipalChangeObserver(this);
  aTrack->AddConsumer(mPlaybackTrackListener);

  for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
    mTrackListeners[i]->NotifyTrackAdded(aTrack);
  }

  if (mActive) {
    return;
  }

  // Check if we became active.
  if (ContainsLiveTracks(mTracks)) {
    mActive = true;
    NotifyActive();
  }
}

void
DOMMediaStream::NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack)
{
  MOZ_ASSERT(NS_IsMainThread());

  aTrack->RemoveConsumer(mPlaybackTrackListener);
  aTrack->RemovePrincipalChangeObserver(this);

  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;
  }

  if (mSetInactiveOnFinish) {
    // For compatibility with mozCaptureStream we in some cases do not go
    // inactive until the playback stream finishes.
    return;
  }

  // Check if we became inactive.
  if (!ContainsLiveTracks(mTracks)) {
    mActive = false;
    NotifyInactive();
  }
}

nsresult
DOMMediaStream::DispatchTrackEvent(const nsAString& aName,
                                   const RefPtr<MediaStreamTrack>& aTrack)
{
  MOZ_ASSERT(aName == NS_LITERAL_STRING("addtrack"),
             "Only 'addtrack' is supported at this time");

  MediaStreamTrackEventInit init;
  init.mTrack = aTrack;

  RefPtr<MediaStreamTrackEvent> event =
    MediaStreamTrackEvent::Constructor(this, aName, init);

  return DispatchTrustedEvent(event);
}

void
DOMMediaStream::BlockPlaybackTrack(TrackPort* aTrack)
{
  MOZ_ASSERT(aTrack);
  ++mTracksPendingRemoval;
  RefPtr<Pledge<bool>> p =
    aTrack->BlockSourceTrackId(aTrack->GetTrack()->mTrackID,
                               BlockingMode::CREATION);
  RefPtr<DOMMediaStream> self = this;
  p->Then([self] (const bool& aIgnore) { self->NotifyPlaybackTrackBlocked(); },
          [] (const nsresult& aIgnore) { NS_ERROR("Could not remove track from MSG"); }
  );
}

void
DOMMediaStream::NotifyPlaybackTrackBlocked()
{
  MOZ_ASSERT(mTracksPendingRemoval > 0,
             "A track reported finished blocking more times than we asked for");
  if (--mTracksPendingRemoval == 0) {
    // The MediaStreamGraph has reported a track was blocked and we are not
    // waiting for any further tracks to get blocked. It is now safe to
    // recompute the principal based on our main thread track set state.
    LOG(LogLevel::Debug, ("DOMMediaStream %p saw all tracks pending removal "
                          "finish. Recomputing principal.", this));
    RecomputePrincipal();
  }
}

DOMLocalMediaStream::~DOMLocalMediaStream()
{
  if (mInputStream) {
    // Make sure Listeners of this stream know it's going away
    StopImpl();
  }
}

JSObject*
DOMLocalMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
  return dom::LocalMediaStreamBinding::Wrap(aCx, this, aGivenProto);
}

void
DOMLocalMediaStream::Stop()
{
  LOG(LogLevel::Debug, ("DOMMediaStream %p Stop()", this));
  nsCOMPtr<nsPIDOMWindowInner> pWindow = GetParentObject();
  nsIDocument* document = pWindow ? pWindow->GetExtantDoc() : nullptr;
  nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                  NS_LITERAL_CSTRING("Media"),
                                  document,
                                  nsContentUtils::eDOM_PROPERTIES,
                                  "MediaStreamStopDeprecatedWarning");

  StopImpl();
}

void
DOMLocalMediaStream::StopImpl()
{
  if (mInputStream && mInputStream->AsSourceStream()) {
    mInputStream->AsSourceStream()->EndAllTrackAndFinish();
  }
}

already_AddRefed<DOMLocalMediaStream>
DOMLocalMediaStream::CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow,
                                               MediaStreamGraph* aGraph,
                                               MediaStreamTrackSourceGetter* aTrackSourceGetter)
{
  RefPtr<DOMLocalMediaStream> stream =
    new DOMLocalMediaStream(aWindow, aTrackSourceGetter);
  stream->InitSourceStream(aGraph);
  return stream.forget();
}

already_AddRefed<DOMLocalMediaStream>
DOMLocalMediaStream::CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow,
                                                   MediaStreamGraph* aGraph,
                                                   MediaStreamTrackSourceGetter* aTrackSourceGetter)
{
  RefPtr<DOMLocalMediaStream> stream =
    new DOMLocalMediaStream(aWindow, aTrackSourceGetter);
  stream->InitTrackUnionStream(aGraph);
  return stream.forget();
}

DOMAudioNodeMediaStream::DOMAudioNodeMediaStream(nsPIDOMWindowInner* aWindow, AudioNode* aNode)
  : DOMMediaStream(aWindow, nullptr),
    mStreamNode(aNode)
{
}

DOMAudioNodeMediaStream::~DOMAudioNodeMediaStream()
{
}

already_AddRefed<DOMAudioNodeMediaStream>
DOMAudioNodeMediaStream::CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow,
                                                       AudioNode* aNode,
                                                       MediaStreamGraph* aGraph)
{
  RefPtr<DOMAudioNodeMediaStream> stream = new DOMAudioNodeMediaStream(aWindow, aNode);
  stream->InitTrackUnionStream(aGraph);
  return stream.forget();
}

DOMHwMediaStream::DOMHwMediaStream(nsPIDOMWindowInner* aWindow)
  : DOMLocalMediaStream(aWindow, nullptr)
{
}

DOMHwMediaStream::~DOMHwMediaStream()
{
}

already_AddRefed<DOMHwMediaStream>
DOMHwMediaStream::CreateHwStream(nsPIDOMWindowInner* aWindow,
                                 OverlayImage* aImage)
{
  RefPtr<DOMHwMediaStream> stream = new DOMHwMediaStream(aWindow);

  MediaStreamGraph* graph =
    MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER,
                                  AudioChannel::Normal);
  stream->InitSourceStream(graph);
  stream->Init(stream->GetInputStream(), aImage);

  return stream.forget();
}

void
DOMHwMediaStream::Init(MediaStream* stream, OverlayImage* aImage)
{
  SourceMediaStream* srcStream = stream->AsSourceStream();

  if (srcStream) {
    VideoSegment segment;
    srcStream->AddTrack(TRACK_VIDEO_PRIMARY, 0, new VideoSegment());
    srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment);
    srcStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
  }
}

int32_t
DOMHwMediaStream::RequestOverlayId()
{
  /*** STUB ***/
  return -1;
}

void
DOMHwMediaStream::SetImageSize(uint32_t width, uint32_t height)
{
  SourceMediaStream* srcStream = GetInputStream()->AsSourceStream();
  StreamTracks::Track* track = srcStream->FindTrack(TRACK_VIDEO_PRIMARY);

  if (!track || !track->GetSegment()) {
    return;
  }

}

void
DOMHwMediaStream::SetOverlayImage(OverlayImage* aImage)
{
  if (!aImage) {
    return;
  }

  SourceMediaStream* srcStream = GetInputStream()->AsSourceStream();
  StreamTracks::Track* track = srcStream->FindTrack(TRACK_VIDEO_PRIMARY);

  if (!track || !track->GetSegment()) {
    return;
  }

}

void
DOMHwMediaStream::SetOverlayId(int32_t aOverlayId)
{
  /*** STUB ***/
}