/* -*- 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 NSDOMMEDIASTREAM_H_
#define NSDOMMEDIASTREAM_H_

#include "ImageContainer.h"

#include "nsAutoPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsWrapperCache.h"
#include "StreamTracks.h"
#include "nsIDOMWindow.h"
#include "nsIPrincipal.h"
#include "MediaTrackConstraints.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "PrincipalChangeObserver.h"

// X11 has a #define for CurrentTime. Unbelievable :-(.
// See dom/media/webaudio/AudioContext.h for more fun!
#ifdef CurrentTime
#undef CurrentTime
#endif

namespace mozilla {

class DOMHwMediaStream;
class DOMLocalMediaStream;
class DOMMediaStream;
class MediaStream;
class MediaInputPort;
class DirectMediaStreamListener;
class MediaStreamGraph;
class ProcessedMediaStream;

enum class BlockingMode;

namespace dom {
class AudioNode;
class HTMLCanvasElement;
class MediaStreamTrack;
class MediaStreamTrackSource;
class AudioStreamTrack;
class VideoStreamTrack;
class AudioTrack;
class VideoTrack;
class AudioTrackList;
class VideoTrackList;
class MediaTrackListListener;
} // namespace dom

namespace layers {
class ImageContainer;
class OverlayImage;
} // namespace layers

namespace media {
template<typename V, typename E> class Pledge;
} // namespace media

#define NS_DOMMEDIASTREAM_IID \
{ 0x8cb65468, 0x66c0, 0x444e, \
  { 0x89, 0x9f, 0x89, 0x1d, 0x9e, 0xd2, 0xbe, 0x7c } }

class OnTracksAvailableCallback {
public:
  virtual ~OnTracksAvailableCallback() {}
  virtual void NotifyTracksAvailable(DOMMediaStream* aStream) = 0;
};

/**
 * Interface through which a DOMMediaStream can query its producer for a
 * MediaStreamTrackSource. This will be used whenever a track occurs in the
 * DOMMediaStream's owned stream that has not yet been created on the main
 * thread (see DOMMediaStream::CreateOwnDOMTrack).
 */
class MediaStreamTrackSourceGetter : public nsISupports
{
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS(MediaStreamTrackSourceGetter)

public:
  MediaStreamTrackSourceGetter()
  {
    MOZ_COUNT_CTOR(MediaStreamTrackSourceGetter);
  }

  virtual already_AddRefed<dom::MediaStreamTrackSource>
  GetMediaStreamTrackSource(TrackID aInputTrackID) = 0;

protected:
  virtual ~MediaStreamTrackSourceGetter()
  {
    MOZ_COUNT_DTOR(MediaStreamTrackSourceGetter);
  }
};

/**
 * DOM wrapper for MediaStreams.
 *
 * To account for track operations such as clone(), addTrack() and
 * removeTrack(), a DOMMediaStream wraps three internal (and chained)
 * MediaStreams:
 *   1. mInputStream
 *      - Controlled by the owner/source of the DOMMediaStream.
 *        It's a stream of the type indicated by
 *      - DOMMediaStream::CreateSourceStream/CreateTrackUnionStream. A source
 *        typically creates its DOMMediaStream, creates the MediaStreamTracks
 *        owned by said stream, then gets the internal input stream to which it
 *        feeds data for the previously created tracks.
 *      - When necessary it can create tracks on the internal stream only and
 *        their corresponding MediaStreamTracks will be asynchronously created.
 *   2. mOwnedStream
 *      - A TrackUnionStream containing tracks owned by this stream.
 *      - The internal model of a MediaStreamTrack consists of its owning
 *        DOMMediaStream and the TrackID of the corresponding internal track in
 *        the owning DOMMediaStream's mOwnedStream.
 *      - The owned stream is different from the input stream since a cloned
 *        DOMMediaStream is also the owner of its (cloned) MediaStreamTracks.
 *      - Stopping an original track shall not stop its clone. This is
 *        solved by stopping it at the owned stream, while the clone's owned
 *        stream gets data directly from the original input stream.
 *      - A DOMMediaStream (original or clone) gets all tracks dynamically
 *        added by the source automatically forwarded by having a TRACK_ANY
 *        MediaInputPort set up from the owning DOMMediaStream's input stream
 *        to this DOMMediaStream's owned stream.
 *   3. mPlaybackStream
 *      - A TrackUnionStream containing the tracks corresponding to the
 *        MediaStreamTracks currently in this DOMMediaStream (per getTracks()).
 *      - Similarly as for mOwnedStream, there's a TRACK_ANY MediaInputPort set
 *        up from the owned stream to the playback stream to allow tracks
 *        dynamically added by the source to be automatically forwarded to any
 *        audio or video sinks.
 *      - MediaStreamTracks added by addTrack() are set up with a MediaInputPort
 *        locked to their internal TrackID, from their owning DOMMediaStream's
 *        owned stream to this playback stream.
 *
 *
 * A graphical representation of how tracks are connected in various cases as
 * follows:
 *
 *                     addTrack()ed case:
 * DOMStream A
 *           Input        Owned          Playback
 *            t1 ---------> t1 ------------> t1     <- MediaStreamTrack X
 *                                                     (pointing to t1 in A)
 *                                 --------> t2     <- MediaStreamTrack Y
 *                                /                    (pointing to t1 in B)
 * DOMStream B                   /
 *           Input        Owned /        Playback
 *            t1 ---------> t1 ------------> t1     <- MediaStreamTrack Y
 *                                                     (pointing to t1 in B)
 *
 *                     removeTrack()ed case:
 * DOMStream A
 *           Input        Owned          Playback
 *            t1 ---------> t1                      <- No tracks
 *
 *
 *                     clone()d case:
 * DOMStream A
 *           Input        Owned          Playback
 *            t1 ---------> t1 ------------> t1     <- MediaStreamTrack X
 *               \                                     (pointing to t1 in A)
 *                -----
 * DOMStream B         \
 *           Input      \ Owned          Playback
 *                       -> t1 ------------> t1     <- MediaStreamTrack Y
 *                                                     (pointing to t1 in B)
 *
 *
 *            addTrack()ed, removeTrack()ed and clone()d case:
 *
 *  Here we have done the following:
 *    var A = someStreamWithTwoTracks;
 *    var B = someStreamWithOneTrack;
 *    var X = A.getTracks()[0];
 *    var Y = A.getTracks()[1];
 *    var Z = B.getTracks()[0];
 *    A.addTrack(Z);
 *    A.removeTrack(X);
 *    B.removeTrack(Z);
 *    var A' = A.clone();
 *
 * DOMStream A
 *           Input        Owned          Playback
 *            t1 ---------> t1                      <- MediaStreamTrack X (removed)
 *                                                     (pointing to t1 in A)
 *            t2 ---------> t2 ------------> t2     <- MediaStreamTrack Y
 *             \                                       (pointing to t2 in A)
 *              \                    ------> t3     <- MediaStreamTrack Z
 *               \                  /                  (pointing to t1 in B)
 * DOMStream B    \                /
 *           Input \      Owned   /      Playback
 *            t1 ---^-----> t1 ---                  <- MediaStreamTrack Z (removed)
 *              \    \                                 (pointing to t1 in B)
 *               \    \
 * DOMStream A'   \    \
 *           Input \    \ Owned          Playback
 *                  \    -> t1 ------------> t1     <- MediaStreamTrack Y'
 *                   \                                 (pointing to t1 in A')
 *                    ----> t2 ------------> t2     <- MediaStreamTrack Z'
 *                                                     (pointing to t2 in A')
 */
class DOMMediaStream : public DOMEventTargetHelper,
                       public dom::PrincipalChangeObserver<dom::MediaStreamTrack>
{
  friend class DOMLocalMediaStream;
  friend class dom::MediaStreamTrack;
  typedef dom::MediaStreamTrack MediaStreamTrack;
  typedef dom::AudioStreamTrack AudioStreamTrack;
  typedef dom::VideoStreamTrack VideoStreamTrack;
  typedef dom::MediaStreamTrackSource MediaStreamTrackSource;
  typedef dom::AudioTrack AudioTrack;
  typedef dom::VideoTrack VideoTrack;
  typedef dom::AudioTrackList AudioTrackList;
  typedef dom::VideoTrackList VideoTrackList;

public:
  typedef dom::MediaTrackConstraints MediaTrackConstraints;

  class TrackListener {
  public:
    virtual ~TrackListener() {}

    /**
     * Called when the DOMMediaStream has a new track added, either by
     * JS (addTrack()) or the source creating one.
     */
    virtual void
    NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) {};

    /**
     * Called when the DOMMediaStream removes a track, either by
     * JS (removeTrack()) or the source ending it.
     */
    virtual void
    NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) {};
  };

  /**
   * TrackPort is a representation of a MediaStreamTrack-MediaInputPort pair
   * that make up a link between the Owned stream and the Playback stream.
   *
   * Semantically, the track is the identifier/key and the port the value of this
   * connection.
   *
   * The input port can be shared between several TrackPorts. This is the case
   * for DOMMediaStream's mPlaybackPort which forwards all tracks in its
   * mOwnedStream automatically.
   *
   * If the MediaStreamTrack is owned by another DOMMediaStream (called A) than
   * the one owning the TrackPort (called B), the input port (locked to the
   * MediaStreamTrack's TrackID) connects A's mOwnedStream to B's mPlaybackStream.
   *
   * A TrackPort may never leave the DOMMediaStream it was created in. Internal
   * use only.
   */
  class TrackPort
  {
  public:
    NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(TrackPort)
    NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(TrackPort)

    /**
     * Indicates MediaInputPort ownership to the TrackPort.
     *
     * OWNED    - Owned by the TrackPort itself. TrackPort must destroy the
     *            input port when it's destructed.
     * EXTERNAL - Owned by another entity. It's the caller's responsibility to
     *            ensure the the MediaInputPort outlives the TrackPort.
     */
    enum class InputPortOwnership {
      OWNED = 1,
      EXTERNAL
    };

    TrackPort(MediaInputPort* aInputPort,
              MediaStreamTrack* aTrack,
              const InputPortOwnership aOwnership);

  protected:
    virtual ~TrackPort();

  public:
    void DestroyInputPort();

    /**
     * Returns the source stream of the input port.
     */
    MediaStream* GetSource() const;

    /**
     * Returns the track ID this track is locked to in the source stream of the
     * input port.
     */
    TrackID GetSourceTrackId() const;

    MediaInputPort* GetInputPort() const { return mInputPort; }
    MediaStreamTrack* GetTrack() const { return mTrack; }

    /**
     * Blocks aTrackId from going into mInputPort unless the port has been
     * destroyed. Returns a pledge that gets resolved when the MediaStreamGraph
     * has applied the block in the playback stream.
     */
    already_AddRefed<media::Pledge<bool, nsresult>>
    BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode);

  private:
    RefPtr<MediaInputPort> mInputPort;
    RefPtr<MediaStreamTrack> mTrack;

    // Defines if we've been given ownership of the input port or if it's owned
    // externally. The owner is responsible for destroying the port.
    const InputPortOwnership mOwnership;
  };

  DOMMediaStream(nsPIDOMWindowInner* aWindow,
                 MediaStreamTrackSourceGetter* aTrackSourceGetter);

  NS_DECL_ISUPPORTS_INHERITED
  NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DOMMediaStream,
                                           DOMEventTargetHelper)
  NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOMMEDIASTREAM_IID)

  nsPIDOMWindowInner* GetParentObject() const
  {
    return mWindow;
  }
  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;

  // WebIDL

  static already_AddRefed<DOMMediaStream>
  Constructor(const dom::GlobalObject& aGlobal,
              ErrorResult& aRv);

  static already_AddRefed<DOMMediaStream>
  Constructor(const dom::GlobalObject& aGlobal,
              const DOMMediaStream& aStream,
              ErrorResult& aRv);

  static already_AddRefed<DOMMediaStream>
  Constructor(const dom::GlobalObject& aGlobal,
              const dom::Sequence<OwningNonNull<MediaStreamTrack>>& aTracks,
              ErrorResult& aRv);

  double CurrentTime();

  void GetId(nsAString& aID) const;

  void GetAudioTracks(nsTArray<RefPtr<AudioStreamTrack> >& aTracks) const;
  void GetVideoTracks(nsTArray<RefPtr<VideoStreamTrack> >& aTracks) const;
  void GetTracks(nsTArray<RefPtr<MediaStreamTrack> >& aTracks) const;
  MediaStreamTrack* GetTrackById(const nsAString& aId) const;
  void AddTrack(MediaStreamTrack& aTrack);
  void RemoveTrack(MediaStreamTrack& aTrack);

  /** Identical to CloneInternal(TrackForwardingOption::EXPLICIT) */
  already_AddRefed<DOMMediaStream> Clone();

  IMPL_EVENT_HANDLER(addtrack)

  // NON-WebIDL

  /**
   * Option to provide to CloneInternal() of which tracks should be forwarded
   * from the source stream (`this`) to the returned stream clone.
   *
   * CURRENT forwards the tracks currently in the source stream's track set.
   * ALL     forwards like EXPLICIT plus any and all future tracks originating
   *         from the same input stream as the source DOMMediaStream (`this`).
   */
  enum class TrackForwardingOption {
    CURRENT,
    ALL
  };
  already_AddRefed<DOMMediaStream> CloneInternal(TrackForwardingOption aForwarding);

  MediaStreamTrack* GetOwnedTrackById(const nsAString& aId);

  /**
   * Returns true if this DOMMediaStream has aTrack in its mPlaybackStream.
   */
  bool HasTrack(const MediaStreamTrack& aTrack) const;

  /**
   * Returns true if this DOMMediaStream owns aTrack.
   */
  bool OwnsTrack(const MediaStreamTrack& aTrack) const;

  /**
   * Returns the corresponding MediaStreamTrack if it's in our mOwnedStream.
   * aInputTrackID should match the track's TrackID in its input stream,
   * and aTrackID the TrackID in mOwnedStream.
   *
   * When aTrackID is not supplied or set to TRACK_ANY, we return the first
   * MediaStreamTrack that matches the given input track. Note that there may
   * be multiple MediaStreamTracks matching the same input track, but that they
   * in that case all share the same MediaStreamTrackSource.
   */
  MediaStreamTrack* FindOwnedDOMTrack(MediaStream* aInputStream,
                                      TrackID aInputTrackID,
                                      TrackID aTrackID = TRACK_ANY) const;

  /**
   * Returns the TrackPort connecting aTrack's input stream to mOwnedStream,
   * or nullptr if aTrack is not owned by this DOMMediaStream.
   */
  TrackPort* FindOwnedTrackPort(const MediaStreamTrack& aTrack) const;

  /**
   * Returns the corresponding MediaStreamTrack if it's in our mPlaybackStream.
   * aInputTrackID should match the track's TrackID in its owned stream.
   */
  MediaStreamTrack* FindPlaybackDOMTrack(MediaStream* aInputStream,
                                         TrackID aInputTrackID) const;

  /**
   * Returns the TrackPort connecting mOwnedStream to mPlaybackStream for aTrack.
   */
  TrackPort* FindPlaybackTrackPort(const MediaStreamTrack& aTrack) const;

  MediaStream* GetInputStream() const { return mInputStream; }
  ProcessedMediaStream* GetOwnedStream() const { return mOwnedStream; }
  ProcessedMediaStream* GetPlaybackStream() const { return mPlaybackStream; }

  /**
   * Allows a video element to identify this stream as a camera stream, which
   * needs special treatment.
   */
  virtual MediaStream* GetCameraStream() const { return nullptr; }

  /**
   * Allows users to get access to media data without going through graph
   * queuing. Returns a bool to let us know if direct data will be delivered.
   */
  bool AddDirectListener(DirectMediaStreamListener *aListener);
  void RemoveDirectListener(DirectMediaStreamListener *aListener);

  virtual DOMLocalMediaStream* AsDOMLocalMediaStream() { return nullptr; }
  virtual DOMHwMediaStream* AsDOMHwMediaStream() { return nullptr; }

  bool IsFinished();
  /**
   * Returns a principal indicating who may access this stream. The stream contents
   * can only be accessed by principals subsuming this principal.
   */
  nsIPrincipal* GetPrincipal() { return mPrincipal; }

  /**
   * Returns a principal indicating who may access video data of this stream.
   * The video principal will be a combination of all live video tracks.
   */
  nsIPrincipal* GetVideoPrincipal() { return mVideoPrincipal; }

  // From PrincipalChangeObserver<MediaStreamTrack>.
  void PrincipalChanged(MediaStreamTrack* aTrack) override;

  /**
   * Add a PrincipalChangeObserver to this stream.
   *
   * Returns true if it was successfully added.
   *
   * Ownership of the PrincipalChangeObserver remains with the caller, and it's
   * the caller's responsibility to remove the observer before it dies.
   */
  bool AddPrincipalChangeObserver(dom::PrincipalChangeObserver<DOMMediaStream>* aObserver);

  /**
   * Remove an added PrincipalChangeObserver from this stream.
   *
   * Returns true if it was successfully removed.
   */
  bool RemovePrincipalChangeObserver(dom::PrincipalChangeObserver<DOMMediaStream>* aObserver);

  // Webrtc allows the remote side to name a stream whatever it wants, and we
  // need to surface this to content.
  void AssignId(const nsAString& aID) { mID = aID; }

  /**
   * Create a DOMMediaStream whose underlying input stream is a SourceMediaStream.
   */
  static already_AddRefed<DOMMediaStream> CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow,
                                                                    MediaStreamGraph* aGraph,
                                                                    MediaStreamTrackSourceGetter* aTrackSourceGetter = nullptr);

  /**
   * Create a DOMMediaStream whose underlying input stream is a TrackUnionStream.
   */
  static already_AddRefed<DOMMediaStream> CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow,
                                                                        MediaStreamGraph* aGraph,
                                                                        MediaStreamTrackSourceGetter* aTrackSourceGetter = nullptr);

  /**
   * Create an DOMMediaStream whose underlying input stream is an
   * AudioCaptureStream.
   */
  static already_AddRefed<DOMMediaStream>
  CreateAudioCaptureStreamAsInput(nsPIDOMWindowInner* aWindow,
                                  nsIPrincipal* aPrincipal,
                                  MediaStreamGraph* aGraph);

  void SetLogicalStreamStartTime(StreamTime aTime)
  {
    mLogicalStreamStartTime = aTime;
  }

  /**
   * Adds a MediaStreamTrack to mTracks and raises "addtrack".
   *
   * Note that "addtrack" is raised synchronously and only has an effect if
   * this MediaStream is already exposed to script. For spec compliance this is
   * to be called from an async task.
   */
  void AddTrackInternal(MediaStreamTrack* aTrack);

  /**
   * Called for each track in our owned stream to indicate to JS that we
   * are carrying that track.
   *
   * Pre-creates a MediaStreamTrack and returns it.
   * It is up to the caller to make sure it is added through AddTrackInternal.
   */
  already_AddRefed<MediaStreamTrack> CreateDOMTrack(TrackID aTrackID,
                                                    MediaSegment::Type aType,
                                                    MediaStreamTrackSource* aSource,
                                                    const MediaTrackConstraints& aConstraints = MediaTrackConstraints());

  /**
   * Creates a MediaStreamTrack cloned from aTrack, adds it to mTracks and
   * returns it.
   * aCloneTrackID is the TrackID the new track will get in mOwnedStream.
   */
  already_AddRefed<MediaStreamTrack> CloneDOMTrack(MediaStreamTrack& aTrack,
                                                   TrackID aCloneTrackID);

  // When the initial set of tracks has been added, run
  // aCallback->NotifyTracksAvailable.
  // It is allowed to do anything, including run script.
  // aCallback may run immediately during this call if tracks are already
  // available!
  // We only care about track additions, we'll fire the notification even if
  // some of the tracks have been removed.
  // Takes ownership of aCallback.
  void OnTracksAvailable(OnTracksAvailableCallback* aCallback);

  /**
   * Add an nsISupports object that this stream will keep alive as long as
   * the stream itself is alive.
   */
  void AddConsumerToKeepAlive(nsISupports* aConsumer)
  {
    mConsumersToKeepAlive.AppendElement(aConsumer);
  }

  // Registers a track listener to this MediaStream, for listening to changes
  // to our track set. The caller must call UnregisterTrackListener before
  // being destroyed, so we don't hold on to a dead pointer. Main thread only.
  void RegisterTrackListener(TrackListener* aListener);

  // Unregisters a track listener from this MediaStream. The caller must call
  // UnregisterTrackListener before being destroyed, so we don't hold on to
  // a dead pointer. Main thread only.
  void UnregisterTrackListener(TrackListener* aListener);

protected:
  virtual ~DOMMediaStream();

  void Destroy();
  void InitSourceStream(MediaStreamGraph* aGraph);
  void InitTrackUnionStream(MediaStreamGraph* aGraph);
  void InitAudioCaptureStream(nsIPrincipal* aPrincipal, MediaStreamGraph* aGraph);

  // Sets up aStream as mInputStream. A producer may append data to a
  // SourceMediaStream input stream, or connect another stream to a
  // TrackUnionStream input stream.
  void InitInputStreamCommon(MediaStream* aStream, MediaStreamGraph* aGraph);

  // Sets up a new TrackUnionStream as mOwnedStream and connects it to
  // mInputStream with a TRACK_ANY MediaInputPort if available.
  // If this DOMMediaStream should have an input stream (producing data),
  // it has to be initiated before the owned stream.
  void InitOwnedStreamCommon(MediaStreamGraph* aGraph);

  // Sets up a new TrackUnionStream as mPlaybackStream and connects it to
  // mOwnedStream with a TRACK_ANY MediaInputPort if available.
  // If this DOMMediaStream should have an owned stream (producer or clone),
  // it has to be initiated before the playback stream.
  void InitPlaybackStreamCommon(MediaStreamGraph* aGraph);

  void CheckTracksAvailable();

  // Called when MediaStreamGraph has finished an iteration where tracks were
  // created.
  void NotifyTracksCreated();

  // Dispatches NotifyTrackAdded() to all registered track listeners.
  void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack);

  // Dispatches NotifyTrackRemoved() to all registered track listeners.
  void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack);

  // Dispatches "addtrack" or "removetrack".
  nsresult DispatchTrackEvent(const nsAString& aName,
                              const RefPtr<MediaStreamTrack>& aTrack);

  class OwnedStreamListener;
  friend class OwnedStreamListener;

  class PlaybackStreamListener;
  friend class PlaybackStreamListener;

  // XXX Bug 1124630. Remove with CameraPreviewMediaStream.
  void CreateAndAddPlaybackStreamListener(MediaStream*);

  /**
   * Block a track in our playback stream. Calls NotifyPlaybackTrackBlocked()
   * after the MediaStreamGraph has applied the block and the track is no longer
   * live.
   */
  void BlockPlaybackTrack(TrackPort* aTrack);

  /**
   * Called on main thread after MediaStreamGraph has applied a track block in
   * our playback stream.
   */
  void NotifyPlaybackTrackBlocked();

  // Recomputes the current principal of this stream based on the set of tracks
  // it currently contains. PrincipalChangeObservers will be notified only if
  // the principal changes.
  void RecomputePrincipal();

  // StreamTime at which the currentTime attribute would return 0.
  StreamTime mLogicalStreamStartTime;

  // We need this to track our parent object.
  nsCOMPtr<nsPIDOMWindowInner> mWindow;

  // MediaStreams are owned by the graph, but we tell them when to die,
  // and they won't die until we let them.

  // This stream contains tracks used as input by us. Cloning happens from this
  // stream. Tracks may exist in these stream but not in |mOwnedStream| if they
  // have been stopped.
  MediaStream* mInputStream;

  // This stream contains tracks owned by us (if we were created directly from
  // source, or cloned from some other stream). Tracks map to |mOwnedTracks|.
  ProcessedMediaStream* mOwnedStream;

  // This stream contains tracks currently played by us, despite of owner.
  // Tracks map to |mTracks|.
  ProcessedMediaStream* mPlaybackStream;

  // This port connects mInputStream to mOwnedStream. All tracks forwarded.
  RefPtr<MediaInputPort> mOwnedPort;

  // This port connects mOwnedStream to mPlaybackStream. All tracks not
  // explicitly blocked due to removal are forwarded.
  RefPtr<MediaInputPort> mPlaybackPort;

  // MediaStreamTracks corresponding to tracks in our mOwnedStream.
  AutoTArray<RefPtr<TrackPort>, 2> mOwnedTracks;

  // MediaStreamTracks corresponding to tracks in our mPlaybackStream.
  AutoTArray<RefPtr<TrackPort>, 2> mTracks;

  // Number of MediaStreamTracks that have been removed on main thread but are
  // waiting to be removed on MediaStreamGraph thread.
  size_t mTracksPendingRemoval;

  // The interface through which we can query the stream producer for
  // track sources.
  RefPtr<MediaStreamTrackSourceGetter> mTrackSourceGetter;

  // Listener tracking changes to mOwnedStream. We use this to notify the
  // MediaStreamTracks we own about state changes.
  RefPtr<OwnedStreamListener> mOwnedListener;

  // Listener tracking changes to mPlaybackStream. This drives state changes
  // in this DOMMediaStream and notifications to mTrackListeners.
  RefPtr<PlaybackStreamListener> mPlaybackListener;

  nsTArray<nsAutoPtr<OnTracksAvailableCallback> > mRunOnTracksAvailable;

  // Set to true after MediaStreamGraph has created tracks for mPlaybackStream.
  bool mTracksCreated;

  nsString mID;

  // Keep these alive while the stream is alive.
  nsTArray<nsCOMPtr<nsISupports>> mConsumersToKeepAlive;

  bool mNotifiedOfMediaStreamGraphShutdown;

  // The track listeners subscribe to changes in this stream's track set.
  nsTArray<TrackListener*> mTrackListeners;

private:
  void NotifyPrincipalChanged();
  // Principal identifying who may access the collected contents of this stream.
  // If null, this stream can be used by anyone because it has no content yet.
  nsCOMPtr<nsIPrincipal> mPrincipal;
  // Video principal is used by video element as access is requested to its
  // image data.
  nsCOMPtr<nsIPrincipal> mVideoPrincipal;
  nsTArray<dom::PrincipalChangeObserver<DOMMediaStream>*> mPrincipalChangeObservers;
  CORSMode mCORSMode;
};

NS_DEFINE_STATIC_IID_ACCESSOR(DOMMediaStream,
                              NS_DOMMEDIASTREAM_IID)

#define NS_DOMLOCALMEDIASTREAM_IID \
{ 0xb1437260, 0xec61, 0x4dfa, \
  { 0x92, 0x54, 0x04, 0x44, 0xe2, 0xb5, 0x94, 0x9c } }

class DOMLocalMediaStream : public DOMMediaStream
{
public:
  explicit DOMLocalMediaStream(nsPIDOMWindowInner* aWindow,
                               MediaStreamTrackSourceGetter* aTrackSourceGetter)
    : DOMMediaStream(aWindow, aTrackSourceGetter) {}

  NS_DECL_ISUPPORTS_INHERITED
  NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOMLOCALMEDIASTREAM_IID)

  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;

  void Stop();

  /**
   * Create an nsDOMLocalMediaStream whose underlying stream is a SourceMediaStream.
   */
  static already_AddRefed<DOMLocalMediaStream>
  CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow,
                            MediaStreamGraph* aGraph,
                            MediaStreamTrackSourceGetter* aTrackSourceGetter = nullptr);

  /**
   * Create an nsDOMLocalMediaStream whose underlying stream is a TrackUnionStream.
   */
  static already_AddRefed<DOMLocalMediaStream>
  CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow,
                                MediaStreamGraph* aGraph,
                                MediaStreamTrackSourceGetter* aTrackSourceGetter = nullptr);

protected:
  virtual ~DOMLocalMediaStream();

  void StopImpl();
};

NS_DEFINE_STATIC_IID_ACCESSOR(DOMLocalMediaStream,
                              NS_DOMLOCALMEDIASTREAM_IID)

class DOMAudioNodeMediaStream : public DOMMediaStream
{
  typedef dom::AudioNode AudioNode;
public:
  DOMAudioNodeMediaStream(nsPIDOMWindowInner* aWindow, AudioNode* aNode);

  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)

  /**
   * Create a DOMAudioNodeMediaStream whose underlying stream is a TrackUnionStream.
   */
  static already_AddRefed<DOMAudioNodeMediaStream>
  CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow,
                                AudioNode* aNode,
                                MediaStreamGraph* aGraph);

protected:
  ~DOMAudioNodeMediaStream();

private:
  // If this object wraps a stream owned by an AudioNode, we need to ensure that
  // the node isn't cycle-collected too early.
  RefPtr<AudioNode> mStreamNode;
};

class DOMHwMediaStream : public DOMLocalMediaStream
{
  typedef mozilla::gfx::IntSize IntSize;
  typedef layers::OverlayImage OverlayImage;
#ifdef MOZ_WIDGET_GONK
  typedef layers::OverlayImage::Data Data;
#endif

public:
  explicit DOMHwMediaStream(nsPIDOMWindowInner* aWindow);

  static already_AddRefed<DOMHwMediaStream> CreateHwStream(nsPIDOMWindowInner* aWindow,
                                                           OverlayImage* aImage = nullptr);
  virtual DOMHwMediaStream* AsDOMHwMediaStream() override { return this; }
  int32_t RequestOverlayId();
  void SetOverlayId(int32_t aOverlayId);
  void SetImageSize(uint32_t width, uint32_t height);
  void SetOverlayImage(OverlayImage* aImage);

protected:
  ~DOMHwMediaStream();

private:
  void Init(MediaStream* aStream, OverlayImage* aImage);

#ifdef MOZ_WIDGET_GONK
  const int DEFAULT_IMAGE_ID = 0x01;
  const int DEFAULT_IMAGE_WIDTH = 400;
  const int DEFAULT_IMAGE_HEIGHT = 300;
  RefPtr<OverlayImage> mOverlayImage;
  PrincipalHandle mPrincipalHandle;
#endif
};

} // namespace mozilla

#endif /* NSDOMMEDIASTREAM_H_ */
