Bug 1208371 - Ensure that MediaManager ends all tracks before finishing its source stream. r?jib draft
authorAndreas Pehrson <pehrsons@gmail.com>
Tue, 26 Jan 2016 19:03:58 +0800
changeset 327698 5ca6f64fd90d9cccf82323e9dd0acb70e40ec373
parent 327697 3e92e5986c53ba0e68ea5e7404979a07c7ed2f20
child 327699 1b3c17817cd0449588fb05db841252fec06f0dfe
push id10267
push userpehrsons@gmail.com
push dateMon, 01 Feb 2016 15:35:08 +0000
reviewersjib
bugs1208371
milestone47.0a1
Bug 1208371 - Ensure that MediaManager ends all tracks before finishing its source stream. r?jib This puts all the logic in GetUserMediaCallbackMediaStreamListener and none in MediaOperationTask to make it simpler to reason about what's happening. When we want to stop a track, the gUMCallbackListener will send a MEDIA_STOP_TRACK if other tracks will still be live. If it was the last live track, the gUMCallbackListener will send a MEDIA_STOP instead. The MEDIA_STOP makes sure the passed in devices (we pass in all) are stopped before finishing the stream.
dom/media/MediaManager.cpp
dom/media/MediaManager.h
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -328,21 +328,18 @@ public:
           if (mAudioDevice) {
             mAudioDevice->GetSource()->Stop(source, kAudioTrack);
             mAudioDevice->GetSource()->Deallocate();
           }
           if (mVideoDevice) {
             mVideoDevice->GetSource()->Stop(source, kVideoTrack);
             mVideoDevice->GetSource()->Deallocate();
           }
-          // We consider ourselves finished if all tracks have been stopped, as
-          // there is no way to restart them from the JS APIs.
-          if (mBool || ((!mAudioDevice || mAudioDevice->GetSource()->IsAvailable()) &&
-                        (!mVideoDevice || mVideoDevice->GetSource()->IsAvailable()))) {
-            source->Finish();
+          if (mType == MEDIA_STOP) {
+            source->EndAllTrackAndFinish();
           }
 
           nsIRunnable *event =
             new GetUserMediaNotificationEvent(mListener,
                                               mType == MEDIA_STOP ?
                                               GetUserMediaNotificationEvent::STOPPING :
                                               GetUserMediaNotificationEvent::STOPPED_TRACK,
                                               mAudioDevice != nullptr,
@@ -2390,35 +2387,35 @@ StopSharingCallback(MediaManager *aThis,
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (aListeners) {
     auto length = aListeners->Length();
     for (size_t i = 0; i < length; ++i) {
       GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
 
       if (listener->Stream()) { // aka HasBeenActivate()ed
-        listener->Invalidate();
+        listener->Stop();
       }
       listener->Remove();
       listener->StopSharing();
     }
     aListeners->Clear();
     aThis->RemoveWindowID(aWindowID);
   }
 }
 
 
 void
 MediaManager::OnNavigation(uint64_t aWindowID)
 {
   MOZ_ASSERT(NS_IsMainThread());
   LOG(("OnNavigation for %llu", aWindowID));
 
-  // Invalidate this window. The runnables check this value before making
-  // a call to content.
+  // Stop the streams for this window. The runnables check this value before
+  // making a call to content.
 
   nsTArray<nsString>* callIDs;
   if (mCallIds.Get(aWindowID, &callIDs)) {
     for (auto& callID : *callIDs) {
       mActiveCallbacks.Remove(callID);
     }
     mCallIds.Remove(aWindowID);
   }
@@ -3037,56 +3034,49 @@ MediaManager::IsActivelyCapturingOrHasAP
       return false;
     }
   }
   return audio == nsIPermissionManager::ALLOW_ACTION ||
          video == nsIPermissionManager::ALLOW_ACTION;
 }
 
 void
-GetUserMediaCallbackMediaStreamListener::Invalidate()
+GetUserMediaCallbackMediaStreamListener::Stop()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
   if (mStopped) {
     return;
   }
 
   // We can't take a chance on blocking here, so proxy this to another
   // thread.
   // Pass a ref to us (which is threadsafe) so it can query us for the
   // source stream info.
   MediaManager::PostTask(FROM_HERE,
     new MediaOperationTask(MEDIA_STOP,
                            this, nullptr, nullptr,
                            !mAudioStopped ? mAudioDevice.get() : nullptr,
                            !mVideoStopped ? mVideoDevice.get() : nullptr,
-                           mFinished, mWindowID, nullptr));
+                           false, mWindowID, nullptr));
   mStopped = mAudioStopped = mVideoStopped = true;
 }
 
 // Doesn't kill audio
 void
 GetUserMediaCallbackMediaStreamListener::StopSharing()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (mVideoDevice && !mStopped &&
+  if (mVideoDevice &&
       (mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen ||
        mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application ||
        mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window)) {
-    // Stop the whole stream if there's no audio; just the video track if we have both
-    if (!mAudioDevice) {
-      Invalidate();
-    } else if (!mVideoStopped) {
-      MediaManager::PostTask(FROM_HERE,
-        new MediaOperationTask(MEDIA_STOP_TRACK,
-                               this, nullptr, nullptr,
-                               nullptr, mVideoDevice,
-                               mFinished, mWindowID, nullptr));
-      mVideoStopped = true;
-    }
+    // We want to stop the whole stream if there's no audio;
+    // just the video track if we have both.
+    // StopTrack figures this out for us.
+    StopTrack(kVideoTrack);
   } else if (mAudioDevice &&
              mAudioDevice->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
     nsCOMPtr<nsPIDOMWindowInner> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID)->AsInner();
     MOZ_ASSERT(window);
     window->SetAudioCapture(false);
     MediaStreamGraph* graph =
       MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
                                     dom::AudioChannel::Normal);
@@ -3189,43 +3179,58 @@ GetUserMediaCallbackMediaStreamListener:
 }
 
 // Stop backend for track
 
 void
 GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (((aTrackID == kAudioTrack && mAudioDevice) ||
-       (aTrackID == kVideoTrack && mVideoDevice)) && !mStopped)
+  MOZ_ASSERT(aTrackID == kAudioTrack || aTrackID == kVideoTrack);
+
+  // XXX to support multiple tracks of a type in a stream, this should key off
+  // the TrackID and not just hard coded values.
+
+  bool stopAudio = aTrackID == kAudioTrack;
+  bool stopVideo = aTrackID == kVideoTrack;
+
+  if (mStopped ||
+      (stopAudio && (mAudioStopped || !mAudioDevice)) ||
+      (stopVideo && (mVideoStopped || !mVideoDevice)))
   {
-    // XXX to support multiple tracks of a type in a stream, this should key off
-    // the TrackID and not just hard coded values.
-    bool stopAudio = aTrackID == kAudioTrack && !mAudioStopped;
-    bool stopVideo = aTrackID == kVideoTrack && !mVideoStopped;
-    MediaManager::PostTask(FROM_HERE,
-      new MediaOperationTask(MEDIA_STOP_TRACK,
-                             this, nullptr, nullptr,
-                             stopAudio ? mAudioDevice.get() : nullptr,
-                             stopVideo ? mVideoDevice.get() : nullptr,
-                             mFinished, mWindowID, nullptr));
-    mAudioStopped |= stopAudio;
-    mVideoStopped |= stopVideo;
-  } else {
-    LOG(("gUM track %d ended, but we don't have type %s",
-         aTrackID, aTrackID == kAudioTrack ? "audio" : "video"));
+    LOG(("Can't stop gUM track %d (%s), exists=%d, stopped=%d",
+         aTrackID,
+         aTrackID == kAudioTrack ? "audio" : "video",
+         aTrackID == kAudioTrack ? !!mAudioDevice : !!mVideoDevice,
+         aTrackID == kAudioTrack ? mAudioStopped : mVideoStopped));
+    return;
   }
+
+  if ((stopAudio || mAudioStopped || !mAudioDevice) &&
+      (stopVideo || mVideoStopped || !mVideoDevice)) {
+    Stop();
+    return;
+  }
+
+  MediaManager::PostTask(FROM_HERE,
+    new MediaOperationTask(MEDIA_STOP_TRACK,
+                           this, nullptr, nullptr,
+                           stopAudio ? mAudioDevice.get() : nullptr,
+                           stopVideo ? mVideoDevice.get() : nullptr,
+                           false , mWindowID, nullptr));
+  mAudioStopped |= stopAudio;
+  mVideoStopped |= stopVideo;
 }
 
 void
 GetUserMediaCallbackMediaStreamListener::NotifyFinished()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mFinished = true;
-  Invalidate(); // we know it's been activated
+  Stop(); // we know it's been activated
 
   RefPtr<MediaManager> manager(MediaManager::GetInstance());
   manager->RemoveFromWindowList(mWindowID, this);
 }
 
 // Called from the MediaStreamGraph thread
 void
 GetUserMediaCallbackMediaStreamListener::NotifyDirectListeners(MediaStreamGraph* aGraph,
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -220,17 +220,17 @@ public:
     MOZ_ASSERT(NS_IsMainThread());
     return mVideoDevice && !mStopped &&
            mVideoDevice->GetSource()->IsAvailable() &&
            mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Browser;
   }
 
   // implement in .cpp to avoid circular dependency with MediaOperationTask
   // Can be invoked from EITHER MainThread or MSG thread
-  void Invalidate();
+  void Stop();
 
   void
   AudioConfig(bool aEchoOn, uint32_t aEcho,
               bool aAgcOn, uint32_t aAGC,
               bool aNoiseOn, uint32_t aNoise,
               int32_t aPlayoutDelay);
 
   void
@@ -313,17 +313,17 @@ private:
   // true after this listener has been removed from its MediaStream.
   // MainThread only.
   bool mRemoved;
 
   // true if we have sent MEDIA_STOP or MEDIA_STOP_TRACK for mAudioDevice.
   // MainThread only.
   bool mAudioStopped;
 
-  // true if we have sent MEDIA_STOP or MEDIA_STOP_TRACK for mAudioDevice.
+  // true if we have sent MEDIA_STOP or MEDIA_STOP_TRACK for mVideoDevice.
   // MainThread only.
   bool mVideoStopped;
 
   // Set at Activate on MainThread
 
   // Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
   // No locking needed as they're only addrefed except on the MediaManager thread
   RefPtr<AudioDevice> mAudioDevice; // threadsafe refcnt