Bug 1434600 - Add a method to clean up by window ID, on a MediaEngine. r?pehrsons draft
authorPaul Adenot <paul@paul.cx>
Wed, 31 Jan 2018 19:02:00 +0100
changeset 751488 fe9e8433a4dd499ccd3267c02823f6d9cc19b504
parent 751487 8bbe185917bb4250dc6982ad5b2172201a360728
child 751489 6d3450ae866f67fe56110b0890c93a55d7ce53cd
push id97980
push userpaul@paul.cx
push dateTue, 06 Feb 2018 12:49:29 +0000
reviewerspehrsons
bugs1434600
milestone60.0a1
Bug 1434600 - Add a method to clean up by window ID, on a MediaEngine. r?pehrsons MozReview-Commit-ID: 12w4StZE2eg
dom/media/webrtc/MediaEngine.h
dom/media/webrtc/MediaEngineDefault.cpp
dom/media/webrtc/MediaEngineDefault.h
dom/media/webrtc/MediaEngineWebRTC.cpp
dom/media/webrtc/MediaEngineWebRTC.h
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -43,16 +43,17 @@ public:
   /**
    * Populate an array of sources of the requested type in the nsTArray.
    * Also include devices that are currently unavailable.
    */
   virtual void EnumerateDevices(uint64_t aWindowId,
                                 dom::MediaSourceEnum,
                                 nsTArray<RefPtr<MediaEngineSource>>*) = 0;
 
+  virtual void ReleaseResourcesForWindow(uint64_t aWindowId) = 0;
   virtual void Shutdown() = 0;
 
   virtual void SetFakeDeviceChangeEvents() {}
 
 protected:
   virtual ~MediaEngine() {}
 };
 
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -556,50 +556,91 @@ MediaEngineDefault::EnumerateDevices(uin
 
   switch (aMediaSource) {
     case dom::MediaSourceEnum::Camera: {
       // Only supports camera video sources. See Bug 1038241.
 
       // We once had code here to find a VideoSource with the same settings and
       // re-use that. This is no longer possible since the resolution gets set
       // in Allocate().
+
+      nsTArray<RefPtr<MediaEngineSource>>*
+        devicesForThisWindow = mVSources.LookupOrAdd(aWindowId);
       auto newSource = MakeRefPtr<MediaEngineDefaultVideoSource>();
-      mVSources.AppendElement(newSource);
+      devicesForThisWindow->AppendElement(newSource);
       aSources->AppendElement(newSource);
       return;
     }
     case dom::MediaSourceEnum::Microphone: {
-      for (const RefPtr<MediaEngineDefaultAudioSource>& source : mASources) {
+      nsTArray<RefPtr<MediaEngineDefaultAudioSource>>*
+        devicesForThisWindow = mASources.LookupOrAdd(aWindowId);
+      for (const RefPtr<MediaEngineDefaultAudioSource>& source : *devicesForThisWindow) {
         if (source->IsAvailable()) {
           aSources->AppendElement(source);
         }
       }
 
       if (aSources->IsEmpty()) {
         // All streams are currently busy, just make a new one.
         auto newSource = MakeRefPtr<MediaEngineDefaultAudioSource>();
-        mASources.AppendElement(newSource);
+        devicesForThisWindow->AppendElement(newSource);
         aSources->AppendElement(newSource);
       }
       return;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("Unsupported source type");
       return;
   }
 }
 
 void
+MediaEngineDefault::ReleaseResourcesForWindow(uint64_t aWindowId)
+{
+  nsTArray<RefPtr<MediaEngineDefaultAudioSource>>* audioDevicesForThisWindow =
+   mASources.Get(aWindowId);
+
+  if (audioDevicesForThisWindow) {
+    for (const RefPtr<MediaEngineDefaultAudioSource>& source :
+         *audioDevicesForThisWindow) {
+      source->Shutdown();
+    }
+  }
+
+  mASources.Remove(aWindowId);
+
+  nsTArray<RefPtr<MediaEngineSource>>* videoDevicesForThisWindow =
+    mVSources.Get(aWindowId);
+
+  if (videoDevicesForThisWindow) {
+    for (const RefPtr<MediaEngineSource>& source :
+         *videoDevicesForThisWindow) {
+      source->Shutdown();
+    }
+  }
+
+  mVSources.Remove(aWindowId);
+}
+
+void
 MediaEngineDefault::Shutdown()
 {
   AssertIsOnOwningThread();
 
-  for (RefPtr<MediaEngineDefaultVideoSource>& source : mVSources) {
-    source->Shutdown();
+  for (auto iter = mVSources.Iter(); !iter.Done(); iter.Next()) {
+    for (const RefPtr<MediaEngineSource>& source : *iter.UserData()) {
+      if (source) {
+        source->Shutdown();
+      }
+    }
   }
-  for (RefPtr<MediaEngineDefaultAudioSource>& source : mASources) {
-    source->Shutdown();
+  for (auto iter = mASources.Iter(); !iter.Done(); iter.Next()) {
+    for (const RefPtr<MediaEngineDefaultAudioSource>& source : *iter.UserData()) {
+      if (source) {
+        source->Shutdown();
+      }
+    }
   }
   mVSources.Clear();
   mASources.Clear();
 };
 
 } // namespace mozilla
--- a/dom/media/webrtc/MediaEngineDefault.h
+++ b/dom/media/webrtc/MediaEngineDefault.h
@@ -186,19 +186,23 @@ class MediaEngineDefault : public MediaE
 {
 public:
   MediaEngineDefault() = default;
 
   void EnumerateDevices(uint64_t aWindowId,
                         dom::MediaSourceEnum,
                         nsTArray<RefPtr<MediaEngineSource>>*) override;
   void Shutdown() override;
+  void ReleaseResourcesForWindow(uint64_t aWindowId) override;
 
 private:
   ~MediaEngineDefault() = default;
 
-  nsTArray<RefPtr<MediaEngineDefaultVideoSource>> mVSources;
-  nsTArray<RefPtr<MediaEngineDefaultAudioSource>> mASources;
+  // WindowID -> Array of devices.
+  nsClassHashtable<nsUint64HashKey,
+                   nsTArray<RefPtr<MediaEngineSource>>> mVSources;
+  nsClassHashtable<nsUint64HashKey,
+                   nsTArray<RefPtr<MediaEngineDefaultAudioSource>>> mASources;
 };
 
 } // namespace mozilla
 
 #endif /* NSMEDIAENGINEDEFAULT_H_ */
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -233,25 +233,30 @@ MediaEngineWebRTC::EnumerateDevices(uint
 
       if (uniqueId[0] == '\0') {
         // In case a device doesn't set uniqueId!
         strncpy(uniqueId, deviceName, sizeof(uniqueId));
         uniqueId[sizeof(uniqueId)-1] = '\0'; // strncpy isn't safe
       }
 
       NS_ConvertUTF8toUTF16 uuid(uniqueId);
-      RefPtr<MediaEngineSource> vSource = mVideoSources.Get(uuid);
-      if (vSource && vSource->RequiresSharing()) {
+      RefPtr<MediaEngineSource> vSource;
+
+      nsRefPtrHashtable<nsStringHashKey, MediaEngineSource>*
+        devicesForThisWindow = mVideoSources.LookupOrAdd(aWindowId);
+
+      if (devicesForThisWindow->Get(uuid, getter_AddRefs(vSource)) &&
+          vSource->RequiresSharing()) {
         // We've already seen this shared device, just refresh and append.
         static_cast<MediaEngineRemoteVideoSource*>(vSource.get())->Refresh(i);
         aSources->AppendElement(vSource.get());
       } else {
         vSource = new MediaEngineRemoteVideoSource(i, capEngine, aMediaSource,
                                                    scaryKind || scarySource);
-        mVideoSources.Put(uuid, vSource);
+        devicesForThisWindow->Put(uuid, vSource);
         aSources->AppendElement(vSource);
       }
     }
 
     if (mHasTabVideoSource || dom::MediaSourceEnum::Browser == aMediaSource) {
       aSources->AppendElement(new MediaEngineTabVideoSource());
     }
   } else {
@@ -295,65 +300,106 @@ MediaEngineWebRTC::EnumerateDevices(uint
         continue;
       }
 
       if (uniqueId[0] == '\0') {
         // Mac and Linux don't set uniqueId!
         strcpy(uniqueId, deviceName); // safe given assert and initialization/error-check
       }
 
+
+      RefPtr<MediaEngineSource> aSource;
       NS_ConvertUTF8toUTF16 uuid(uniqueId);
-      RefPtr<MediaEngineSource> aSource = mAudioSources.Get(uuid);
-      if (aSource && aSource->RequiresSharing()) {
+
+      nsRefPtrHashtable<nsStringHashKey, MediaEngineSource>*
+        devicesForThisWindow = mAudioSources.LookupOrAdd(aWindowId);
+
+      if (devicesForThisWindow->Get(uuid, getter_AddRefs(aSource)) &&
+          aSource->RequiresSharing()) {
         // We've already seen this device, just append.
         aSources->AppendElement(aSource.get());
       } else {
         aSource = new MediaEngineWebRTCMicrophoneSource(
             new mozilla::AudioInputCubeb(i),
             i, deviceName, uniqueId,
             mDelayAgnostic, mExtendedFilter);
-        mAudioSources.Put(uuid, aSource); // Hashtable takes ownership.
+        devicesForThisWindow->Put(uuid, aSource);
         aSources->AppendElement(aSource);
       }
     }
   }
 }
 
 bool
 MediaEngineWebRTC::SupportsDuplex()
 {
   return mFullDuplex;
 }
 
 void
+MediaEngineWebRTC::ReleaseResourcesForWindow(uint64_t aWindowId)
+{
+  {
+    nsRefPtrHashtable<nsStringHashKey, MediaEngineSource>*
+      audioDevicesForThisWindow = mAudioSources.Get(aWindowId);
+
+    if (audioDevicesForThisWindow) {
+      for (auto iter = audioDevicesForThisWindow->Iter(); !iter.Done();
+           iter.Next()) {
+        iter.UserData()->Shutdown();
+      }
+
+      // This makes audioDevicesForThisWindow invalid.
+      mAudioSources.Remove(aWindowId);
+    }
+  }
+
+  {
+    nsRefPtrHashtable<nsStringHashKey, MediaEngineSource>*
+      videoDevicesForThisWindow = mVideoSources.Get(aWindowId);
+    if (videoDevicesForThisWindow) {
+      for (auto iter = videoDevicesForThisWindow->Iter(); !iter.Done();
+           iter.Next()) {
+        iter.UserData()->Shutdown();
+      }
+
+      // This makes videoDevicesForThisWindow invalid.
+      mVideoSources.Remove(aWindowId);
+    }
+  }
+}
+
+namespace {
+template<typename T>
+void ShutdownSources(T& aHashTable)
+{
+  for (auto iter = aHashTable.Iter(); !iter.Done(); iter.Next()) {
+    for (auto iterInner = iter.UserData()->Iter(); !iterInner.Done();
+         iterInner.Next()) {
+      MediaEngineSource* source = iterInner.UserData();
+      source->Shutdown();
+    }
+  }
+}
+}
+
+void
 MediaEngineWebRTC::Shutdown()
 {
   // This is likely paranoia
   MutexAutoLock lock(mMutex);
 
   if (camera::GetCamerasChildIfExists()) {
     camera::GetChildAndCall(
       &camera::CamerasChild::RemoveDeviceChangeCallback, this);
   }
 
   LOG(("%s", __FUNCTION__));
   // Shutdown all the sources, since we may have dangling references to the
   // sources in nsDOMUserMediaStreams waiting for GC/CC
-  for (auto iter = mVideoSources.Iter(); !iter.Done(); iter.Next()) {
-    MediaEngineSource* source = iter.UserData();
-    if (source) {
-      source->Shutdown();
-    }
-  }
-  for (auto iter = mAudioSources.Iter(); !iter.Done(); iter.Next()) {
-    MediaEngineSource* source = iter.UserData();
-    if (source) {
-      source->Shutdown();
-    }
-  }
-  mVideoSources.Clear();
-  mAudioSources.Clear();
+  ShutdownSources(mVideoSources);
+  ShutdownSources(mAudioSources);
 
   mozilla::camera::Shutdown();
   AudioInputCubeb::CleanupGlobalData();
 }
 
 }
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -620,30 +620,35 @@ public:
   void Shutdown() override;
 
   // Returns whether the host supports duplex audio stream.
   bool SupportsDuplex();
 
   void EnumerateDevices(uint64_t aWindowId,
                         dom::MediaSourceEnum,
                         nsTArray<RefPtr<MediaEngineSource>>*) override;
+  void ReleaseResourcesForWindow(uint64_t aWindowId) override;
 private:
   ~MediaEngineWebRTC() = default;
 
   nsCOMPtr<nsIThread> mThread;
 
   // gUM runnables can e.g. Enumerate from multiple threads
   Mutex mMutex;
   RefPtr<mozilla::AudioInput> mAudioInput;
   bool mFullDuplex;
   bool mDelayAgnostic;
   bool mExtendedFilter;
   bool mHasTabVideoSource;
 
-  // Store devices we've already seen in a hashtable for quick return.
-  // Maps UUID to MediaEngineSource (one set for audio, one for video).
-  nsRefPtrHashtable<nsStringHashKey, MediaEngineSource> mVideoSources;
-  nsRefPtrHashtable<nsStringHashKey, MediaEngineSource> mAudioSources;
+  // Maps WindowID to a map of device uuid to their MediaEngineSource,
+  // separately for audio and video.
+  nsClassHashtable<nsUint64HashKey,
+                    nsRefPtrHashtable<nsStringHashKey,
+                                      MediaEngineSource>> mVideoSources;
+  nsClassHashtable<nsUint64HashKey,
+                    nsRefPtrHashtable<nsStringHashKey,
+                                      MediaEngineSource>> mAudioSources;
 };
 
 }
 
 #endif /* NSMEDIAENGINEWEBRTC_H_ */