Bug 1364038 - Call SourceListener::StopTrack when the coresponding external device is disconnected. r=jib draft
authorMunro Mengjue Chiang <mchiang@mozilla.com>
Thu, 27 Jul 2017 16:51:24 +0800
changeset 618022 b79b7969c6693ba980c5135784ddb1753e711ec4
parent 616933 31773c864534787a70811aa2ee5eda967f447f6d
child 639943 57cd9ab356b4ecb9ce9cfaa6f8effbc31a5730fb
push id71193
push userbmo:mchiang@mozilla.com
push dateSat, 29 Jul 2017 10:32:17 +0000
reviewersjib
bugs1364038
milestone56.0a1
Bug 1364038 - Call SourceListener::StopTrack when the coresponding external device is disconnected. r=jib MozReview-Commit-ID: EfpmXBJf7PP
dom/media/MediaManager.cpp
dom/media/MediaManager.h
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -525,16 +525,18 @@ public:
       RemoveAll();
     }
 
     return true;
   }
 
   void StopSharing();
 
+  void StopRawID(const nsString& removedDeviceID);
+
   /**
    * Called by one of our SourceListeners when one of its tracks has stopped.
    * Schedules an event for the next stable state to update chrome.
    */
   void NotifySourceTrackStopped();
 
   /**
    * Called in stable state to send a notification to update chrome.
@@ -1723,16 +1725,18 @@ MediaManager::EnumerateRawDevices(uint64
 
     RefPtr<MediaEngine> fakeBackend, realBackend;
     if (fakeCams || fakeMics) {
       fakeBackend = new MediaEngineDefault();
     }
     if ((!fakeCams && hasVideo) || (!fakeMics && hasAudio)) {
       RefPtr<MediaManager> manager = MediaManager_GetInstance();
       realBackend = manager->GetBackend(aWindowId);
+      // We need to listen to this event even JS didn't listen to it.
+      realBackend->AddDeviceChangeCallback(manager);
     }
 
     auto result = MakeUnique<SourceSet>();
 
     if (hasVideo) {
       nsTArray<RefPtr<VideoDevice>> videos;
       GetSources(fakeCams? fakeBackend : realBackend, aVideoType,
                  &MediaEngine::EnumerateVideoDevices, videos, videoLoopDev);
@@ -2032,29 +2036,78 @@ MediaManager::NotifyRecordingStatusChang
   return NS_OK;
 }
 
 int MediaManager::AddDeviceChangeCallback(DeviceChangeCallback* aCallback)
 {
   bool fakeDeviceChangeEventOn = mPrefs.mFakeDeviceChangeEventOn;
   MediaManager::PostTask(NewTaskFrom([fakeDeviceChangeEventOn]() {
     RefPtr<MediaManager> manager = MediaManager_GetInstance();
-    manager->GetBackend(0)->AddDeviceChangeCallback(manager);
     if (fakeDeviceChangeEventOn)
       manager->GetBackend(0)->SetFakeDeviceChangeEvents();
   }));
 
   return DeviceChangeCallback::AddDeviceChangeCallback(aCallback);
 }
 
+static void
+StopRawIDCallback(MediaManager *aThis,
+                   uint64_t aWindowID,
+                   GetUserMediaWindowListener *aListener,
+                   void *aData)
+{
+  if (!aListener || !aData) {
+    return;
+  }
+
+  nsString* removedDeviceID = static_cast<nsString*>(aData);
+  aListener->StopRawID(*removedDeviceID);
+}
+
+
 void MediaManager::OnDeviceChange() {
   RefPtr<MediaManager> self(this);
-  NS_DispatchToMainThread(media::NewRunnableFrom([self,this]() mutable {
+  NS_DispatchToMainThread(media::NewRunnableFrom([self]() mutable {
     MOZ_ASSERT(NS_IsMainThread());
-    DeviceChangeCallback::OnDeviceChange();
+    self->DeviceChangeCallback::OnDeviceChange();
+
+    // On some Windows machine, if we call EnumertaeRawDevices immediately after receiving
+    // devicechange event, sometimes we would get outdated devices list.
+    PR_Sleep(PR_MillisecondsToInterval(100));
+    RefPtr<PledgeSourceSet> p = self->EnumerateRawDevices(0, MediaSourceEnum::Camera, MediaSourceEnum::Microphone, false);
+    p->Then([self](SourceSet*& aDevices) mutable {
+      UniquePtr<SourceSet> devices(aDevices);
+      nsTArray<nsString> deviceIDs;
+
+      for (auto& device : *devices) {
+        nsString id;
+        device->GetId(id);
+        id.ReplaceSubstring(NS_LITERAL_STRING("default: "), NS_LITERAL_STRING(""));
+        if (!deviceIDs.Contains(id)) {
+          deviceIDs.AppendElement(id);
+        }
+      }
+
+      for (auto& id : self->mDeviceIDs) {
+        if (!deviceIDs.Contains(id)) {
+          // Stop the coresponding SourceListener
+          nsGlobalWindow::WindowByIdTable* windowsById = nsGlobalWindow::GetWindowsTable();
+          if (windowsById) {
+            for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
+              nsGlobalWindow* window = iter.Data();
+              if (window->IsInnerWindow()) {
+                self->IterateWindowListeners(window->AsInner(), StopRawIDCallback, &id);
+              }
+            }
+          }
+        }
+      }
+
+      self->mDeviceIDs = deviceIDs;
+    }, [](MediaStreamError*& reason) {});
     return NS_OK;
   }));
 }
 
 nsresult MediaManager::GenerateUUID(nsAString& aResult)
 {
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
@@ -2645,24 +2698,44 @@ MediaManager::EnumerateDevicesImpl(uint6
   RefPtr<Pledge<nsCString>> p = media::GetPrincipalKey(principalInfo, persist);
   p->Then([id, aWindowId, aVideoType, aAudioType,
            aFake](const nsCString& aOriginKey) mutable {
     MOZ_ASSERT(NS_IsMainThread());
     RefPtr<MediaManager> mgr = MediaManager_GetInstance();
 
     RefPtr<PledgeSourceSet> p = mgr->EnumerateRawDevices(aWindowId, aVideoType,
                                                          aAudioType, aFake);
-    p->Then([id, aWindowId, aOriginKey](SourceSet*& aDevices) mutable {
+    p->Then([id,
+             aWindowId,
+             aOriginKey,
+             aFake,
+             aVideoType,
+             aAudioType](SourceSet*& aDevices) mutable {
       UniquePtr<SourceSet> devices(aDevices); // secondary result
 
       // Only run if window is still on our active list.
       RefPtr<MediaManager> mgr = MediaManager_GetInstance();
       if (!mgr) {
         return NS_OK;
       }
+
+      if (aVideoType == MediaSourceEnum::Camera &&
+          aAudioType == MediaSourceEnum::Microphone &&
+          !aFake) {
+        mgr->mDeviceIDs.Clear();
+        for (auto& device : *devices) {
+          nsString id;
+          device->GetId(id);
+          id.ReplaceSubstring(NS_LITERAL_STRING("default: "), NS_LITERAL_STRING(""));
+          if (!mgr->mDeviceIDs.Contains(id)) {
+            mgr->mDeviceIDs.AppendElement(id);
+          }
+        }
+      }
+
       RefPtr<PledgeSourceSet> p = mgr->mOutstandingPledges.Remove(id);
       if (!p || !mgr->IsWindowStillActive(aWindowId)) {
         return NS_OK;
       }
       MediaManager_AnonymizeDevices(*devices, aOriginKey);
       p->Resolve(devices.release());
       return NS_OK;
     });
@@ -2980,16 +3053,17 @@ MediaManager::Shutdown()
     prefs->RemoveObserver("media.navigator.audio.full_duplex", this);
   }
 
   // Close off any remaining active windows.
   GetActiveWindows()->Clear();
   mActiveCallbacks.Clear();
   mCallIds.Clear();
   mPendingGUMRequest.Clear();
+  mDeviceIDs.Clear();
 #ifdef MOZ_WEBRTC
   StopWebRtcLog();
 #endif
 
   // Because mMediaThread is not an nsThread, we must dispatch to it so it can
   // clean up BackgroundChild. Continue stopping thread once this is done.
 
   class ShutdownTask : public Runnable
@@ -3971,16 +4045,39 @@ GetUserMediaWindowListener::StopSharing(
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
 
   for (auto& source : mActiveListeners) {
     source->StopSharing();
   }
 }
 
 void
+GetUserMediaWindowListener::StopRawID(const nsString& removedDeviceID)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+
+  for (auto& source : mActiveListeners) {
+    if (source->GetAudioDevice()) {
+      nsString id;
+      source->GetAudioDevice()->GetRawId(id);
+      if (removedDeviceID.Equals(id)) {
+        source->Stop();
+      }
+    }
+    if (source->GetVideoDevice()) {
+      nsString id;
+      source->GetVideoDevice()->GetRawId(id);
+      if (removedDeviceID.Equals(id)) {
+        source->Stop();
+      }
+    }
+  }
+}
+
+void
 GetUserMediaWindowListener::NotifySourceTrackStopped()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // We wait until stable state before notifying chrome so chrome only does one
   // update if more tracks are stopped in this event loop.
 
   if (mChromeNotificationTaskPosted) {
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -329,16 +329,17 @@ private:
   // ONLY accessed from MediaManagerThread
   RefPtr<MediaEngine> mBackend;
 
   static StaticRefPtr<MediaManager> sSingleton;
 
   media::CoatCheck<PledgeSourceSet> mOutstandingPledges;
   media::CoatCheck<PledgeChar> mOutstandingCharPledges;
   media::CoatCheck<PledgeVoid> mOutstandingVoidPledges;
+  nsTArray<nsString> mDeviceIDs;
 public:
   media::CoatCheck<media::Pledge<nsCString>> mGetPrincipalKeyPledges;
   RefPtr<media::Parent<media::NonE10s>> mNonE10sParent;
 };
 
 } // namespace mozilla
 
 #endif // MOZILLA_MEDIAMANAGER_H