Index: content/browser/device_monitor_mac.mm |
diff --git a/content/browser/device_monitor_mac.mm b/content/browser/device_monitor_mac.mm |
index eb429afe6402bb07bfc3f100ec31e9c05e2665e9..2d872d74775b637325df2a987a807a6870588384 100644 |
--- a/content/browser/device_monitor_mac.mm |
+++ b/content/browser/device_monitor_mac.mm |
@@ -10,11 +10,14 @@ |
#include "base/bind_helpers.h" |
#include "base/logging.h" |
+#include "base/mac/bind_objc_block.h" |
#include "base/mac/scoped_nsobject.h" |
#include "base/threading/thread_checker.h" |
#include "content/public/browser/browser_thread.h" |
#import "media/video/capture/mac/avfoundation_glue.h" |
+using content::BrowserThread; |
+ |
namespace { |
// This class is used to keep track of system devices names and their types. |
@@ -216,57 +219,127 @@ class SuspendObserverDelegate; |
// This class is a Key-Value Observer (KVO) shim. It is needed because C++ |
// classes cannot observe Key-Values directly. Created, manipulated, and |
-// destroyed on the Device Thread by SuspendedObserverDelegate. |
+// destroyed on the UI Thread by SuspendObserverDelegate. |
@interface CrAVFoundationDeviceObserver : NSObject { |
@private |
- SuspendObserverDelegate* receiver_; // weak |
+ // Callback for device changed, has to run on Device Thread. |
+ base::Closure onDeviceChangedCallback_; |
+ |
// Member to keep track of the devices we are already monitoring. |
- std::set<CrAVCaptureDevice*> monitoredDevices_; |
+ std::set<base::scoped_nsobject<CrAVCaptureDevice> > monitoredDevices_; |
} |
-- (id)initWithChangeReceiver:(SuspendObserverDelegate*)receiver; |
-- (void)startObserving:(CrAVCaptureDevice*)device; |
+- (id)initWithOnChangedCallback:(const base::Closure&)callback; |
+- (void)startObserving:(base::scoped_nsobject<CrAVCaptureDevice>)device; |
- (void)stopObserving:(CrAVCaptureDevice*)device; |
+- (void)clearOnDeviceChangedCallback; |
@end |
namespace { |
// This class owns and manages the lifetime of a CrAVFoundationDeviceObserver. |
-// Provides a callback for this device observer to indicate that there has been |
-// a device change of some kind. Created by AVFoundationMonitorImpl in UI thread |
-// but living in Device Thread. |
+// It is created and destroyed in UI thread by AVFoundationMonitorImpl, and it |
+// operates on this thread except for the expensive device enumerations which |
+// are run on Device Thread. |
class SuspendObserverDelegate : |
public base::RefCountedThreadSafe<SuspendObserverDelegate> { |
public: |
- explicit SuspendObserverDelegate(DeviceMonitorMacImpl* monitor) |
- : avfoundation_monitor_impl_(monitor) { |
- device_thread_checker_.DetachFromThread(); |
- } |
- |
- void OnDeviceChanged(); |
- void StartObserver(); |
- void ResetDeviceMonitorOnUIThread(); |
+ explicit SuspendObserverDelegate(DeviceMonitorMacImpl* monitor); |
+ |
+ // Create |suspend_observer_| for all devices and register OnDeviceChanged() |
+ // as its change callback. Schedule bottom half in DoStartObserver(). |
+ void StartObserver( |
+ const scoped_refptr<base::SingleThreadTaskRunner>& device_thread); |
+ // Enumerate devices in |device_thread| and run the bottom half in |
+ // DoOnDeviceChange(). |suspend_observer_| calls back here on suspend event, |
+ // and our parent AVFoundationMonitorImpl calls on connect/disconnect device. |
+ void OnDeviceChanged( |
+ const scoped_refptr<base::SingleThreadTaskRunner>& device_thread); |
+ // Remove the device monitor's weak reference. Remove ourselves as suspend |
+ // notification observer from |suspend_observer_|. |
+ void ResetDeviceMonitor(); |
private: |
friend class base::RefCountedThreadSafe<SuspendObserverDelegate>; |
- virtual ~SuspendObserverDelegate() {} |
+ virtual ~SuspendObserverDelegate(); |
- void OnDeviceChangedOnUIThread( |
- const std::vector<DeviceInfo>& snapshot_devices); |
+ // Bottom half of StartObserver(), starts |suspend_observer_| for all devices. |
+ // Assumes that |devices| has been retained prior to being called, and |
+ // releases it internally. |
+ void DoStartObserver(NSArray* devices); |
+ // Bottom half of OnDeviceChanged(), starts |suspend_observer_| for current |
+ // devices and composes a snapshot of them to send it to |
+ // |avfoundation_monitor_impl_|. Assumes that |devices| has been retained |
+ // prior to being called, and releases it internally. |
+ void DoOnDeviceChanged(NSArray* devices); |
- base::ThreadChecker device_thread_checker_; |
base::scoped_nsobject<CrAVFoundationDeviceObserver> suspend_observer_; |
DeviceMonitorMacImpl* avfoundation_monitor_impl_; |
}; |
-void SuspendObserverDelegate::OnDeviceChanged() { |
- DCHECK(device_thread_checker_.CalledOnValidThread()); |
- NSArray* devices = [AVCaptureDeviceGlue devices]; |
+SuspendObserverDelegate::SuspendObserverDelegate(DeviceMonitorMacImpl* monitor) |
+ : avfoundation_monitor_impl_(monitor) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+} |
+ |
+void SuspendObserverDelegate::StartObserver( |
+ const scoped_refptr<base::SingleThreadTaskRunner>& device_thread) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ |
+ base::Closure on_device_changed_callback = |
+ base::Bind(&SuspendObserverDelegate::OnDeviceChanged, |
+ this, device_thread); |
+ suspend_observer_.reset([[CrAVFoundationDeviceObserver alloc] |
+ initWithOnChangedCallback:on_device_changed_callback]); |
+ |
+ // Enumerate the devices in Device thread and post the observers start to be |
+ // done on UI thread. The devices array is retained in |device_thread| and |
+ // released in DoStartObserver(). |
+ base::PostTaskAndReplyWithResult(device_thread, FROM_HERE, |
+ base::BindBlock(^{ return [[AVCaptureDeviceGlue devices] retain]; }), |
+ base::Bind(&SuspendObserverDelegate::DoStartObserver, this)); |
+} |
+ |
+void SuspendObserverDelegate::OnDeviceChanged( |
+ const scoped_refptr<base::SingleThreadTaskRunner>& device_thread) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ // Enumerate the devices in Device thread and post the consolidation of the |
+ // new devices and the old ones to be done on UI thread. The devices array |
+ // is retained in |device_thread| and released in DoOnDeviceChanged(). |
+ PostTaskAndReplyWithResult(device_thread, FROM_HERE, |
+ base::BindBlock(^{ return [[AVCaptureDeviceGlue devices] retain]; }), |
+ base::Bind(&SuspendObserverDelegate::DoOnDeviceChanged, this)); |
+} |
+ |
+void SuspendObserverDelegate::ResetDeviceMonitor() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ avfoundation_monitor_impl_ = NULL; |
+ [suspend_observer_ clearOnDeviceChangedCallback]; |
+} |
+ |
+SuspendObserverDelegate::~SuspendObserverDelegate() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+} |
+ |
+void SuspendObserverDelegate::DoStartObserver(NSArray* devices) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ base::scoped_nsobject<NSArray> auto_release(devices); |
+ for (CrAVCaptureDevice* device in devices) { |
+ base::scoped_nsobject<CrAVCaptureDevice> device_ptr([device retain]); |
+ [suspend_observer_ startObserving:device_ptr]; |
+ } |
+} |
+ |
+void SuspendObserverDelegate::DoOnDeviceChanged(NSArray* devices) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ base::scoped_nsobject<NSArray> auto_release(devices); |
std::vector<DeviceInfo> snapshot_devices; |
for (CrAVCaptureDevice* device in devices) { |
- [suspend_observer_ startObserving:device]; |
+ base::scoped_nsobject<CrAVCaptureDevice> device_ptr([device retain]); |
+ [suspend_observer_ startObserving:device_ptr]; |
+ |
BOOL suspended = [device respondsToSelector:@selector(isSuspended)] && |
[device isSuspended]; |
DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown; |
@@ -282,28 +355,7 @@ void SuspendObserverDelegate::OnDeviceChanged() { |
snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String], |
device_type)); |
} |
- // Post the consolidation of enumerated devices to be done on UI thread. |
- content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
- base::Bind(&SuspendObserverDelegate::OnDeviceChangedOnUIThread, |
- this, snapshot_devices)); |
-} |
- |
-void SuspendObserverDelegate::StartObserver() { |
- DCHECK(device_thread_checker_.CalledOnValidThread()); |
- suspend_observer_.reset([[CrAVFoundationDeviceObserver alloc] |
- initWithChangeReceiver:this]); |
- for (CrAVCaptureDevice* device in [AVCaptureDeviceGlue devices]) |
- [suspend_observer_ startObserving:device]; |
-} |
-void SuspendObserverDelegate::ResetDeviceMonitorOnUIThread() { |
- DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
- avfoundation_monitor_impl_ = NULL; |
-} |
- |
-void SuspendObserverDelegate::OnDeviceChangedOnUIThread( |
- const std::vector<DeviceInfo>& snapshot_devices) { |
- DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
// |avfoundation_monitor_impl_| might have been NULLed asynchronously before |
// arriving at this line. |
if (avfoundation_monitor_impl_) { |
@@ -314,9 +366,8 @@ void SuspendObserverDelegate::OnDeviceChangedOnUIThread( |
// AVFoundation implementation of the Mac Device Monitor, registers as a global |
// device connect/disconnect observer and plugs suspend/wake up device observers |
-// per device. Owns a SuspendObserverDelegate living in |device_task_runner_| |
-// and gets notified when a device is suspended/resumed. This class is created |
-// and lives in UI thread; |
+// per device. This class is created and lives in UI thread. Owns a |
+// SuspendObserverDelegate that notifies when a device is suspended/resumed. |
class AVFoundationMonitorImpl : public DeviceMonitorMacImpl { |
public: |
AVFoundationMonitorImpl( |
@@ -327,8 +378,6 @@ class AVFoundationMonitorImpl : public DeviceMonitorMacImpl { |
virtual void OnDeviceChanged() OVERRIDE; |
private: |
- base::ThreadChecker thread_checker_; |
- |
// {Video,AudioInput}DeviceManager's "Device" thread task runner used for |
// posting tasks to |suspend_observer_delegate_|; valid after |
// MediaStreamManager calls StartMonitoring(). |
@@ -345,6 +394,7 @@ AVFoundationMonitorImpl::AVFoundationMonitorImpl( |
: DeviceMonitorMacImpl(monitor), |
device_task_runner_(device_task_runner), |
suspend_observer_delegate_(new SuspendObserverDelegate(this)) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; |
device_arrival_ = |
[nc addObserverForName:AVFoundationGlue:: |
@@ -360,86 +410,97 @@ AVFoundationMonitorImpl::AVFoundationMonitorImpl( |
queue:nil |
usingBlock:^(NSNotification* notification) { |
OnDeviceChanged();}]; |
- device_task_runner_->PostTask(FROM_HERE, |
- base::Bind(&SuspendObserverDelegate::StartObserver, |
- suspend_observer_delegate_)); |
+ suspend_observer_delegate_->StartObserver(device_task_runner_); |
} |
AVFoundationMonitorImpl::~AVFoundationMonitorImpl() { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
- suspend_observer_delegate_->ResetDeviceMonitorOnUIThread(); |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ suspend_observer_delegate_->ResetDeviceMonitor(); |
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; |
[nc removeObserver:device_arrival_]; |
[nc removeObserver:device_removal_]; |
} |
void AVFoundationMonitorImpl::OnDeviceChanged() { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
- device_task_runner_->PostTask(FROM_HERE, |
- base::Bind(&SuspendObserverDelegate::OnDeviceChanged, |
- suspend_observer_delegate_)); |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ suspend_observer_delegate_->OnDeviceChanged(device_task_runner_); |
} |
} // namespace |
@implementation CrAVFoundationDeviceObserver |
-- (id)initWithChangeReceiver:(SuspendObserverDelegate*)receiver { |
+- (id)initWithOnChangedCallback:(const base::Closure&)callback { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
if ((self = [super init])) { |
- DCHECK(receiver != NULL); |
- receiver_ = receiver; |
+ DCHECK(!callback.is_null()); |
+ onDeviceChangedCallback_ = callback; |
} |
return self; |
} |
- (void)dealloc { |
- std::set<CrAVCaptureDevice*>::iterator it = monitoredDevices_.begin(); |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ std::set<base::scoped_nsobject<CrAVCaptureDevice> >::iterator it = |
+ monitoredDevices_.begin(); |
while (it != monitoredDevices_.end()) |
- [self stopObserving:*it++]; |
+ [self removeObservers:*(it++)]; |
[super dealloc]; |
} |
-- (void)startObserving:(CrAVCaptureDevice*)device { |
+- (void)startObserving:(base::scoped_nsobject<CrAVCaptureDevice>)device { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
DCHECK(device != nil); |
// Skip this device if there are already observers connected to it. |
if (std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device) != |
- monitoredDevices_.end()) { |
+ monitoredDevices_.end()) { |
return; |
} |
[device addObserver:self |
forKeyPath:@"suspended" |
options:0 |
- context:device]; |
+ context:device.get()]; |
[device addObserver:self |
forKeyPath:@"connected" |
options:0 |
- context:device]; |
+ context:device.get()]; |
monitoredDevices_.insert(device); |
} |
- (void)stopObserving:(CrAVCaptureDevice*)device { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
DCHECK(device != nil); |
- std::set<CrAVCaptureDevice*>::iterator found = |
+ |
+ std::set<base::scoped_nsobject<CrAVCaptureDevice> >::iterator found = |
std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device); |
DCHECK(found != monitoredDevices_.end()); |
- // Every so seldom, |device| might be gone when getting here, in that case |
- // removing the observer causes a crash. Try to avoid it by checking sanity of |
- // the |device| via its -observationInfo. http://crbug.com/371271. |
+ [self removeObservers:*found]; |
+ monitoredDevices_.erase(found); |
+} |
+ |
+- (void)clearOnDeviceChangedCallback { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ onDeviceChangedCallback_.Reset(); |
+} |
+ |
+- (void)removeObservers:(CrAVCaptureDevice*)device { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ // Check sanity of |device| via its -observationInfo. http://crbug.com/371271. |
if ([device observationInfo]) { |
[device removeObserver:self |
forKeyPath:@"suspended"]; |
[device removeObserver:self |
forKeyPath:@"connected"]; |
} |
- monitoredDevices_.erase(found); |
} |
- (void)observeValueForKeyPath:(NSString*)keyPath |
ofObject:(id)object |
change:(NSDictionary*)change |
context:(void*)context { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
if ([keyPath isEqual:@"suspended"]) |
- receiver_->OnDeviceChanged(); |
+ onDeviceChangedCallback_.Run(); |
if ([keyPath isEqual:@"connected"]) |
[self stopObserving:static_cast<CrAVCaptureDevice*>(context)]; |
} |