Bug 803976: Implementation of LocalMediaStreams for .stop() r=roc,anant
authorRandell Jesup <rjesup@jesup.org>
Wed, 24 Oct 2012 19:21:32 -0400
changeset 111313 0b3cc07a299a0e6a30dc6d6f38acdb1839361e84
parent 111312 d6a3f003d316a4ee20c0010fdb67e3bc089618c3
child 111314 593c26e36849b7eac66cf3c96166d68f629350b5
push id23740
push userryanvm@gmail.com
push dateThu, 25 Oct 2012 12:13:42 +0000
treeherdermozilla-central@5374fb480634 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, anant
bugs803976
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 803976: Implementation of LocalMediaStreams for .stop() r=roc,anant
content/media/MediaStreamGraph.cpp
content/media/MediaStreamGraph.h
content/media/nsDOMMediaStream.cpp
content/media/nsDOMMediaStream.h
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/camera/DOMCameraPreview.cpp
dom/camera/DOMCameraPreview.h
dom/media/MediaManager.cpp
dom/media/MediaManager.h
dom/media/nsIDOMMediaStream.idl
dom/media/nsIDOMNavigatorUserMedia.idl
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -2002,21 +2002,24 @@ SourceMediaStream::AddTrack(TrackID aID,
     GraphImpl()->EnsureNextIteration();
   }
 }
 
 void
 SourceMediaStream::AppendToTrack(TrackID aID, MediaSegment* aSegment)
 {
   MutexAutoLock lock(mMutex);
-  TrackData *track = FindDataForTrack(aID);
-  if (track) {
-    track->mData->AppendFrom(aSegment);
-  } else {
-    NS_ERROR("Append to non-existent track!");
+  // ::EndAllTrackAndFinished() can end these before the sources notice
+  if (!mFinished) {
+    TrackData *track = FindDataForTrack(aID);
+    if (track) {
+      track->mData->AppendFrom(aSegment);
+    } else {
+      NS_ERROR("Append to non-existent track!");
+    }
   }
   if (!mDestroyed) {
     GraphImpl()->EnsureNextIteration();
   }
 }
 
 bool
 SourceMediaStream::HaveEnoughBuffered(TrackID aID)
@@ -2047,21 +2050,24 @@ SourceMediaStream::DispatchWhenNotEnough
     aSignalThread->Dispatch(aSignalRunnable, 0);
   }
 }
 
 void
 SourceMediaStream::EndTrack(TrackID aID)
 {
   MutexAutoLock lock(mMutex);
-  TrackData *track = FindDataForTrack(aID);
-  if (track) {
-    track->mCommands |= TRACK_END;
-  } else {
-    NS_ERROR("End of non-existant track");
+  // ::EndAllTrackAndFinished() can end these before the sources call this
+  if (!mFinished) {
+    TrackData *track = FindDataForTrack(aID);
+    if (track) {
+      track->mCommands |= TRACK_END;
+    } else {
+      NS_ERROR("End of non-existant track");
+    }
   }
   if (!mDestroyed) {
     GraphImpl()->EnsureNextIteration();
   }
 }
 
 void
 SourceMediaStream::AdvanceKnownTracksTime(StreamTime aKnownTime)
@@ -2069,26 +2075,39 @@ SourceMediaStream::AdvanceKnownTracksTim
   MutexAutoLock lock(mMutex);
   mUpdateKnownTracksTime = aKnownTime;
   if (!mDestroyed) {
     GraphImpl()->EnsureNextIteration();
   }
 }
 
 void
-SourceMediaStream::Finish()
+SourceMediaStream::FinishWithLockHeld()
 {
-  MutexAutoLock lock(mMutex);
   mUpdateFinished = true;
   if (!mDestroyed) {
     GraphImpl()->EnsureNextIteration();
   }
 }
 
 void
+SourceMediaStream::EndAllTrackAndFinish()
+{
+  {
+    MutexAutoLock lock(mMutex);
+    for (uint32_t i = 0; i < mUpdateTracks.Length(); ++i) {
+      SourceMediaStream::TrackData* data = &mUpdateTracks[i];
+      data->mCommands |= TRACK_END;
+    }
+  }
+  FinishWithLockHeld();
+  // we will call NotifyFinished() to let GetUserMedia know
+}
+
+void
 MediaInputPort::Init()
 {
   LOG(PR_LOG_DEBUG, ("Adding MediaInputPort %p (from %p to %p) to the graph",
       this, mSource, mDest));
   mSource->AddConsumer(this);
   mDest->AddInput(this);
   // mPortCount decremented via MediaInputPort::Destroy's message
   ++mDest->GraphImpl()->mPortCount;
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -548,17 +548,29 @@ public:
    */
   void AdvanceKnownTracksTime(StreamTime aKnownTime);
   /**
    * Indicate that this stream should enter the "finished" state. All tracks
    * must have been ended via EndTrack. The finish time of the stream is
    * when all tracks have ended and when latest time sent to
    * AdvanceKnownTracksTime() has been reached.
    */
-  void Finish();
+  void FinishWithLockHeld();
+  void Finish()
+    {
+      MutexAutoLock lock(mMutex);
+      FinishWithLockHeld();
+    }
+
+
+  /**
+   * End all tracks and Finish() this stream.  Used to voluntarily revoke access
+   * to a LocalMediaStream.
+   */
+  void EndAllTrackAndFinish();
 
   // XXX need a Reset API
 
   friend class MediaStreamGraphImpl;
 
   struct ThreadAndRunnable {
     void Init(nsIThread* aThread, nsIRunnable* aRunnable)
     {
--- a/content/media/nsDOMMediaStream.cpp
+++ b/content/media/nsDOMMediaStream.cpp
@@ -23,46 +23,97 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMe
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMMediaStream)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMMediaStream)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMMediaStream)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
+// LocalMediaStream currently is the same C++ class as MediaStream;
+// they may eventually split
+DOMCI_DATA(LocalMediaStream, nsDOMLocalMediaStream)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMLocalMediaStream)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMLocalMediaStream)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMMediaStream, nsDOMMediaStream)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMLocalMediaStream)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(LocalMediaStream)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMLocalMediaStream)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMLocalMediaStream)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMLocalMediaStream)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMLocalMediaStream)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMLocalMediaStream)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+
 nsDOMMediaStream::~nsDOMMediaStream()
 {
   if (mStream) {
     mStream->Destroy();
   }
 }
 
 NS_IMETHODIMP
 nsDOMMediaStream::GetCurrentTime(double *aCurrentTime)
 {
   *aCurrentTime = mStream ? MediaTimeToSeconds(mStream->GetCurrentTime()) : 0.0;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDOMLocalMediaStream::Stop()
+{
+  if (mStream && mStream->AsSourceStream()) {
+    mStream->AsSourceStream()->EndAllTrackAndFinish();
+  }
+  return NS_OK;
+}
+
 already_AddRefed<nsDOMMediaStream>
 nsDOMMediaStream::CreateInputStream(uint32_t aHintContents)
 {
   nsRefPtr<nsDOMMediaStream> stream = new nsDOMMediaStream();
   stream->SetHintContents(aHintContents);
   MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
   stream->mStream = gm->CreateInputStream(stream);
   return stream.forget();
 }
 
+already_AddRefed<nsDOMLocalMediaStream>
+nsDOMLocalMediaStream::CreateInputStream(uint32_t aHintContents)
+{
+  nsRefPtr<nsDOMLocalMediaStream> stream = new nsDOMLocalMediaStream();
+  stream->SetHintContents(aHintContents);
+  MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
+  stream->mStream = gm->CreateInputStream(stream);
+  return stream.forget();
+}
+
 already_AddRefed<nsDOMMediaStream>
 nsDOMMediaStream::CreateTrackUnionStream()
 {
   nsRefPtr<nsDOMMediaStream> stream = new nsDOMMediaStream();
   MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
   stream->mStream = gm->CreateTrackUnionStream(stream);
   return stream.forget();
 }
 
+already_AddRefed<nsDOMLocalMediaStream>
+nsDOMLocalMediaStream::CreateTrackUnionStream()
+{
+  nsRefPtr<nsDOMLocalMediaStream> stream = new nsDOMLocalMediaStream();
+  MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
+  stream->mStream = gm->CreateTrackUnionStream(stream);
+  return stream.forget();
+}
+
 bool
 nsDOMMediaStream::CombineWithPrincipal(nsIPrincipal* aPrincipal)
 {
   return nsContentUtils::CombineResourcePrincipals(&mPrincipal, aPrincipal);
 }
--- a/content/media/nsDOMMediaStream.h
+++ b/content/media/nsDOMMediaStream.h
@@ -6,28 +6,31 @@
 #ifndef NSDOMMEDIASTREAM_H_
 #define NSDOMMEDIASTREAM_H_
 
 #include "nsIDOMMediaStream.h"
 #include "MediaStreamGraph.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIPrincipal.h"
 
+class nsXPCClassInfo;
+
 // 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
 
 /**
  * DOM wrapper for MediaStreams.
  */
 class nsDOMMediaStream : public nsIDOMMediaStream
 {
+  friend class nsDOMLocalMediaStream;
   typedef mozilla::MediaStream MediaStream;
 
 public:
   nsDOMMediaStream() : mStream(nullptr), mHintContents(0) {}
   virtual ~nsDOMMediaStream();
 
   NS_DECL_CYCLE_COLLECTION_CLASS(nsDOMMediaStream)
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@@ -77,9 +80,33 @@ protected:
   // If null, this stream can be used by anyone because it has no content yet.
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   // tells the SDP generator about whether this
   // MediaStream probably has audio and/or video
   uint32_t mHintContents;
 };
 
+class nsDOMLocalMediaStream : public nsDOMMediaStream,
+                              public nsIDOMLocalMediaStream
+{
+public:
+  nsDOMLocalMediaStream() {}
+  virtual ~nsDOMLocalMediaStream() {}
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMLocalMediaStream, nsDOMMediaStream)
+  NS_DECL_NSIDOMLOCALMEDIASTREAM
+
+  NS_FORWARD_NSIDOMMEDIASTREAM(nsDOMMediaStream::)
+
+  /**
+   * Create an nsDOMLocalMediaStream whose underlying stream is a SourceMediaStream.
+   */
+  static already_AddRefed<nsDOMLocalMediaStream> CreateInputStream(uint32_t aHintContents);
+
+  /**
+   * Create an nsDOMLocalMediaStream whose underlying stream is a TrackUnionStream.
+   */
+  static already_AddRefed<nsDOMLocalMediaStream> CreateTrackUnionStream();
+};
+
 #endif /* NSDOMMEDIASTREAM_H_ */
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -1513,16 +1513,18 @@ static nsDOMClassInfoData sClassInfoData
   NS_DEFINE_CLASSINFO_DATA(MediaError, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(HTMLAudioElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(TimeRanges, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MediaStream, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
+  NS_DEFINE_CLASSINFO_DATA(LocalMediaStream, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
 #endif
 
   NS_DEFINE_CLASSINFO_DATA(XMLHttpRequestUpload, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
 
   // DOM Traversal NodeIterator class  
   NS_DEFINE_CLASSINFO_DATA(NodeIterator, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
@@ -4123,16 +4125,20 @@ nsDOMClassInfo::Init()
 
   DOM_CLASSINFO_MAP_BEGIN(TimeRanges, nsIDOMTimeRanges)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMTimeRanges)
   DOM_CLASSINFO_MAP_END  
 
   DOM_CLASSINFO_MAP_BEGIN(MediaStream, nsIDOMMediaStream)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMediaStream)
   DOM_CLASSINFO_MAP_END
+
+  DOM_CLASSINFO_MAP_BEGIN(LocalMediaStream, nsIDOMLocalMediaStream)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMLocalMediaStream)
+  DOM_CLASSINFO_MAP_END
 #endif
 
   DOM_CLASSINFO_MAP_BEGIN(XMLHttpRequestUpload, nsIXMLHttpRequestUpload)
     DOM_CLASSINFO_MAP_ENTRY(nsIXMLHttpRequestEventTarget)
     DOM_CLASSINFO_MAP_ENTRY(nsIXMLHttpRequestUpload)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -421,16 +421,17 @@ DOMCI_CLASS(CSSFontFaceStyleDecl)
 DOMCI_CLASS(HTMLVideoElement)
 DOMCI_CLASS(HTMLSourceElement)
 DOMCI_CLASS(MediaError)
 DOMCI_CLASS(HTMLAudioElement)
 DOMCI_CLASS(TimeRanges)
 
 // Media streams
 DOMCI_CLASS(MediaStream)
+DOMCI_CLASS(LocalMediaStream)
 #endif
 
 DOMCI_CLASS(XMLHttpRequestUpload)
 
 // DOM Traversal NodeIterator class
 DOMCI_CLASS(NodeIterator)
 
 DOMCI_CLASS(DataTransfer)
--- a/dom/camera/DOMCameraPreview.cpp
+++ b/dom/camera/DOMCameraPreview.cpp
@@ -39,17 +39,17 @@ public:
     NS_ASSERTION(NS_IsMainThread(), "PreviewControl not run on main thread");
 
     switch (mControl) {
       case START:
         mDOMPreview->Start();
         break;
 
       case STOP:
-        mDOMPreview->Stop();
+        mDOMPreview->StopPreview();
         break;
 
       case STARTED:
         mDOMPreview->SetStateStarted();
         break;
 
       case STOPPED:
         mDOMPreview->SetStateStopped();
@@ -233,19 +233,19 @@ DOMCameraPreview::Started()
   nsCOMPtr<nsIRunnable> started = new PreviewControl(this, PreviewControl::STARTED);
   nsresult rv = NS_DispatchToMainThread(started);
   if (NS_FAILED(rv)) {
     DOM_CAMERA_LOGE("failed to set statrted state (%d), POTENTIAL MEMORY LEAK!\n", rv);
   }
 }
 
 void
-DOMCameraPreview::Stop()
+DOMCameraPreview::StopPreview()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Stop() not called from main thread");
+  NS_ASSERTION(NS_IsMainThread(), "StopPreview() not called from main thread");
   if (mState != STARTED) {
     return;
   }
 
   DOM_CAMERA_LOGI("Stopping preview stream\n");
   mState = STOPPING;
   mCameraControl->StopPreview();
   mInput->EndTrack(TRACK_VIDEO);
--- a/dom/camera/DOMCameraPreview.h
+++ b/dom/camera/DOMCameraPreview.h
@@ -35,17 +35,17 @@ public:
 
   NS_IMETHODIMP
   GetCurrentTime(double* aCurrentTime) {
     return nsDOMMediaStream::GetCurrentTime(aCurrentTime);
   }
 
   void Start();   // called by the MediaStreamListener to start preview
   void Started(); // called by the CameraControl when preview is started
-  void Stop();    // called by the MediaStreamListener to stop preview
+  void StopPreview();    // called by the MediaStreamListener to stop preview
   void Stopped(bool aForced = false);
                   // called by the CameraControl when preview is stopped
   void Error();   // something went wrong, NS_RELEASE needed
 
   void SetStateStarted();
   void SetStateStopped();
 
 protected:
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -239,21 +239,21 @@ public:
   ~GetUserMediaStreamRunnable() {}
 
   NS_IMETHOD
   Run()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
     // Create a media stream.
-    nsCOMPtr<nsDOMMediaStream> stream;
+    nsRefPtr<nsDOMLocalMediaStream> stream;
     uint32_t hints = (mAudioSource ? nsDOMMediaStream::HINT_CONTENTS_AUDIO : 0);
     hints |= (mVideoSource ? nsDOMMediaStream::HINT_CONTENTS_VIDEO : 0);
 
-    stream = nsDOMMediaStream::CreateInputStream(hints);
+    stream = nsDOMLocalMediaStream::CreateInputStream(hints);
 
     nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
       (nsGlobalWindow::GetInnerWindowWithId(mWindowID));
     WindowTable* activeWindows = MediaManager::Get()->GetActiveWindows();
     {
       MutexAutoLock lock(MediaManager::Get()->GetMutex());
 
       if (!stream) {
@@ -294,17 +294,17 @@ public:
     // We're in the main thread, so no worries here either.
     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success(mSuccess);
     nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
 
     {
       MutexAutoLock lock(MediaManager::Get()->GetMutex());
       if (activeWindows->Get(mWindowID)) {
         LOG(("Returning success for getUserMedia()"));
-        success->OnSuccess(stream);
+        success->OnSuccess(static_cast<nsIDOMLocalMediaStream*>(stream));
       }
     }
 
     return NS_OK;
   }
 
 private:
   already_AddRefed<nsIDOMGetUserMediaSuccessCallback> mSuccess;
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -127,17 +127,17 @@ public:
           if (mVideoSource) {
             rv = mVideoSource->Start(mSourceStream, kVideoTrack);
             if (NS_FAILED(rv)) {
               MM_LOG(("Starting video failed, rv=%d",rv));
             }
           }
 
           MM_LOG(("started all sources"));
-          nsCOMPtr<GetUserMediaNotificationEvent> event =
+          nsRefPtr<GetUserMediaNotificationEvent> event =
             new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING);
 
           NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
         }
         break;
 
       case MEDIA_STOP:
         {
@@ -148,17 +148,17 @@ public:
           }
           if (mVideoSource) {
             mVideoSource->Stop();
             mVideoSource->Deallocate();
           }
           // Do this after stopping all tracks with EndTrack()
           mSourceStream->Finish();
 
-          nsCOMPtr<GetUserMediaNotificationEvent> event =
+          nsRefPtr<GetUserMediaNotificationEvent> event =
             new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STOPPING);
 
           NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
         }
         break;
       case MEDIA_RELEASE:
         // We go to MainThread to die
         break;
@@ -170,17 +170,17 @@ public:
     }
     return NS_OK;
   }
 
 private:
   MediaOperation mType;
   nsRefPtr<MediaEngineSource> mAudioSource;
   nsRefPtr<MediaEngineSource> mVideoSource;
-  nsCOMPtr<nsDOMMediaStream> mStream;
+  nsRefPtr<nsDOMMediaStream> mStream;
   SourceMediaStream *mSourceStream;
 };
 
 /**
  * This class is an implementation of MediaStreamListener. This is used
  * to Start() and Stop() the underlying MediaEngineSource when MediaStreams
  * are assigned and deassigned in content.
  */
@@ -223,21 +223,27 @@ public:
     if (mAudioSource) {
       mAudioSource->NotifyPull(aGraph, aDesiredTime);
     }
     if (mVideoSource) {
       mVideoSource->NotifyPull(aGraph, aDesiredTime);
     }
   }
 
+  void
+  NotifyFinished(MediaStreamGraph* aGraph)
+  {
+    Invalidate();
+  }
+
 private:
   nsCOMPtr<nsIThread> mMediaThread;
   nsRefPtr<MediaEngineSource> mAudioSource;
   nsRefPtr<MediaEngineSource> mVideoSource;
-  nsCOMPtr<nsDOMMediaStream> mStream;
+  nsRefPtr<nsDOMMediaStream> mStream;
   bool mValid;
 };
 
 typedef nsTArray<nsRefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
 typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
 
 class MediaDevice : public nsIMediaDevice
 {
--- a/dom/media/nsIDOMMediaStream.idl
+++ b/dom/media/nsIDOMMediaStream.idl
@@ -5,8 +5,15 @@
 
 #include "nsISupports.idl"
  
 [scriptable, builtinclass, uuid(f37c2871-4cb7-4672-bb28-c2d601f7cc9e)]
 interface nsIDOMMediaStream : nsISupports
 {
   readonly attribute double currentTime;
 };
+
+[scriptable, builtinclass, uuid(210a16e3-2a38-4ae9-b0f6-0fb5a8252814)]
+interface nsIDOMLocalMediaStream : nsIDOMMediaStream
+{
+  void stop();
+};
+
--- a/dom/media/nsIDOMNavigatorUserMedia.idl
+++ b/dom/media/nsIDOMNavigatorUserMedia.idl
@@ -19,17 +19,17 @@ interface nsIGetUserMediaDevicesSuccessC
   void onSuccess(in nsIVariant devices);
 };
 
 [scriptable, function, uuid(f2a144fc-3534-4761-8c5d-989ae720f89a)]
 interface nsIDOMGetUserMediaSuccessCallback : nsISupports
 {
   /*
    * value must be a nsIDOMBlob if picture is true and a
-   * nsIDOMMediaStream if either audio or video are true.
+   * nsIDOMLocalMediaStream if either audio or video are true.
    */
   void onSuccess(in nsISupports value);
 };
 
 [scriptable, function, uuid(2614bbcf-85cc-43e5-8740-964f52bdc7ca)]
 interface nsIDOMGetUserMediaErrorCallback : nsISupports
 {
   void onError(in DOMString error);