1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/device_monitor_mac.h"
7 #import <QTKit/QTKit.h>
11 #include "base/logging.h"
12 #include "base/mac/scoped_nsobject.h"
13 #import "media/video/capture/mac/avfoundation_glue.h"
17 // This class is used to keep track of system devices names and their types.
28 DeviceInfo(std::string unique_id, DeviceType type)
29 : unique_id_(unique_id), type_(type) {}
31 // Operator== is needed here to use this class in a std::find. A given
32 // |unique_id_| always has the same |type_| so for comparison purposes the
33 // latter can be safely ignored.
34 bool operator==(const DeviceInfo& device) const {
35 return unique_id_ == device.unique_id_;
38 const std::string& unique_id() const { return unique_id_; }
39 DeviceType type() const { return type_; }
42 std::string unique_id_;
44 // Allow generated copy constructor and assignment.
47 // Base abstract class used by DeviceMonitorMac to interact with either a QTKit
48 // or an AVFoundation implementation of events and notifications.
49 class DeviceMonitorMacImpl {
51 explicit DeviceMonitorMacImpl(content::DeviceMonitorMac* monitor)
55 device_removal_(nil) {
57 // Initialise the devices_cache_ with a not-valid entry. For the case in
58 // which there is one single device in the system and we get notified when
59 // it gets removed, this will prevent the system from thinking that no
60 // devices were added nor removed and not notifying the |monitor_|.
61 cached_devices_.push_back(DeviceInfo("invalid", DeviceInfo::kInvalid));
63 virtual ~DeviceMonitorMacImpl() {}
65 virtual void OnDeviceChanged() = 0;
67 // Method called by the default notification center when a device is removed
68 // or added to the system. It will compare the |cached_devices_| with the
69 // current situation, update it, and, if there's an update, signal to
70 // |monitor_| with the appropriate device type.
71 void ConsolidateDevicesListAndNotify(
72 const std::vector<DeviceInfo>& snapshot_devices);
75 content::DeviceMonitorMac* monitor_;
76 std::vector<DeviceInfo> cached_devices_;
78 // Handles to NSNotificationCenter block observers.
83 DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMacImpl);
86 void DeviceMonitorMacImpl::ConsolidateDevicesListAndNotify(
87 const std::vector<DeviceInfo>& snapshot_devices) {
88 bool video_device_added = false;
89 bool audio_device_added = false;
90 bool video_device_removed = false;
91 bool audio_device_removed = false;
93 // Compare the current system devices snapshot with the ones cached to detect
94 // additions, present in the former but not in the latter. If we find a device
95 // in snapshot_devices entry also present in cached_devices, we remove it from
97 std::vector<DeviceInfo>::const_iterator it;
98 for (it = snapshot_devices.begin(); it != snapshot_devices.end(); ++it) {
99 std::vector<DeviceInfo>::iterator cached_devices_iterator =
100 std::find(cached_devices_.begin(), cached_devices_.end(), *it);
101 if (cached_devices_iterator == cached_devices_.end()) {
102 video_device_added |= ((it->type() == DeviceInfo::kVideo) ||
103 (it->type() == DeviceInfo::kMuxed));
104 audio_device_added |= ((it->type() == DeviceInfo::kAudio) ||
105 (it->type() == DeviceInfo::kMuxed));
106 DVLOG(1) << "Device has been added, id: " << it->unique_id();
108 cached_devices_.erase(cached_devices_iterator);
111 // All the remaining entries in cached_devices are removed devices.
112 for (it = cached_devices_.begin(); it != cached_devices_.end(); ++it) {
113 video_device_removed |= ((it->type() == DeviceInfo::kVideo) ||
114 (it->type() == DeviceInfo::kMuxed) ||
115 (it->type() == DeviceInfo::kInvalid));
116 audio_device_removed |= ((it->type() == DeviceInfo::kAudio) ||
117 (it->type() == DeviceInfo::kMuxed) ||
118 (it->type() == DeviceInfo::kInvalid));
119 DVLOG(1) << "Device has been removed, id: " << it->unique_id();
121 // Update the cached devices with the current system snapshot.
122 cached_devices_ = snapshot_devices;
124 if (video_device_added || video_device_removed)
125 monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
126 if (audio_device_added || audio_device_removed)
127 monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
130 class QTKitMonitorImpl : public DeviceMonitorMacImpl {
132 explicit QTKitMonitorImpl(content::DeviceMonitorMac* monitor);
133 virtual ~QTKitMonitorImpl();
135 virtual void OnDeviceChanged() OVERRIDE;
138 void OnAttributeChanged(NSNotification* notification);
143 QTKitMonitorImpl::QTKitMonitorImpl(content::DeviceMonitorMac* monitor)
144 : DeviceMonitorMacImpl(monitor) {
145 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
147 [nc addObserverForName:QTCaptureDeviceWasConnectedNotification
150 usingBlock:^(NSNotification* notification) {
151 OnDeviceChanged();}];
153 [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification
156 usingBlock:^(NSNotification* notification) {
157 OnDeviceChanged();}];
159 [nc addObserverForName:QTCaptureDeviceAttributeDidChangeNotification
162 usingBlock:^(NSNotification* notification) {
163 OnAttributeChanged(notification);}];
166 QTKitMonitorImpl::~QTKitMonitorImpl() {
167 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
168 [nc removeObserver:device_arrival_];
169 [nc removeObserver:device_removal_];
170 [nc removeObserver:device_change_];
173 void QTKitMonitorImpl::OnAttributeChanged(
174 NSNotification* notification) {
175 if ([[[notification userInfo]
176 objectForKey:QTCaptureDeviceChangedAttributeKey]
177 isEqualToString:QTCaptureDeviceSuspendedAttribute]) {
182 void QTKitMonitorImpl::OnDeviceChanged() {
183 std::vector<DeviceInfo> snapshot_devices;
185 NSArray* devices = [QTCaptureDevice inputDevices];
186 for (QTCaptureDevice* device in devices) {
187 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
188 // Act as if suspended video capture devices are not attached. For
189 // example, a laptop's internal webcam is suspended when the lid is closed.
190 if ([device hasMediaType:QTMediaTypeVideo] &&
191 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
193 device_type = DeviceInfo::kVideo;
194 } else if ([device hasMediaType:QTMediaTypeMuxed] &&
195 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
197 device_type = DeviceInfo::kMuxed;
198 } else if ([device hasMediaType:QTMediaTypeSound] &&
199 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
201 device_type = DeviceInfo::kAudio;
203 snapshot_devices.push_back(
204 DeviceInfo([[device uniqueID] UTF8String], device_type));
206 ConsolidateDevicesListAndNotify(snapshot_devices);
209 // Forward declaration for use by CrAVFoundationDeviceObserver.
210 class AVFoundationMonitorImpl;
214 // This class is a Key-Value Observer (KVO) shim. It is needed because C++
215 // classes cannot observe Key-Values directly.
216 @interface CrAVFoundationDeviceObserver : NSObject {
218 AVFoundationMonitorImpl* receiver_;
219 // Member to keep track of the devices we are already monitoring.
220 std::set<CrAVCaptureDevice*> monitoredDevices_;
223 - (id)initWithChangeReceiver:(AVFoundationMonitorImpl*)receiver;
224 - (void)startObserving:(CrAVCaptureDevice*)device;
225 - (void)stopObserving:(CrAVCaptureDevice*)device;
231 class AVFoundationMonitorImpl : public DeviceMonitorMacImpl {
233 explicit AVFoundationMonitorImpl(content::DeviceMonitorMac* monitor);
234 virtual ~AVFoundationMonitorImpl();
236 virtual void OnDeviceChanged() OVERRIDE;
239 base::scoped_nsobject<CrAVFoundationDeviceObserver> suspend_observer_;
240 DISALLOW_COPY_AND_ASSIGN(AVFoundationMonitorImpl);
243 AVFoundationMonitorImpl::AVFoundationMonitorImpl(
244 content::DeviceMonitorMac* monitor)
245 : DeviceMonitorMacImpl(monitor) {
246 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
248 [nc addObserverForName:AVFoundationGlue::
249 AVCaptureDeviceWasConnectedNotification()
252 usingBlock:^(NSNotification* notification) {
253 OnDeviceChanged();}];
255 [nc addObserverForName:AVFoundationGlue::
256 AVCaptureDeviceWasDisconnectedNotification()
259 usingBlock:^(NSNotification* notification) {
260 OnDeviceChanged();}];
261 suspend_observer_.reset(
262 [[CrAVFoundationDeviceObserver alloc] initWithChangeReceiver:this]);
263 for (CrAVCaptureDevice* device in [AVCaptureDeviceGlue devices])
264 [suspend_observer_ startObserving:device];
267 AVFoundationMonitorImpl::~AVFoundationMonitorImpl() {
268 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
269 [nc removeObserver:device_arrival_];
270 [nc removeObserver:device_removal_];
271 for (CrAVCaptureDevice* device in [AVCaptureDeviceGlue devices])
272 [suspend_observer_ stopObserving:device];
275 void AVFoundationMonitorImpl::OnDeviceChanged() {
276 std::vector<DeviceInfo> snapshot_devices;
277 for (CrAVCaptureDevice* device in [AVCaptureDeviceGlue devices]) {
278 [suspend_observer_ startObserving:device];
279 BOOL suspended = [device respondsToSelector:@selector(isSuspended)] &&
280 [device isSuspended];
281 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
282 if ([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()]) {
285 device_type = DeviceInfo::kVideo;
286 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeMuxed()]) {
287 device_type = suspended ? DeviceInfo::kAudio : DeviceInfo::kMuxed;
288 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeAudio()]) {
289 device_type = DeviceInfo::kAudio;
291 snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String],
294 ConsolidateDevicesListAndNotify(snapshot_devices);
299 @implementation CrAVFoundationDeviceObserver
301 - (id)initWithChangeReceiver:(AVFoundationMonitorImpl*)receiver {
302 if ((self = [super init])) {
303 DCHECK(receiver != NULL);
304 receiver_ = receiver;
309 - (void)startObserving:(CrAVCaptureDevice*)device {
310 DCHECK(device != nil);
311 // Skip this device if there are already observers connected to it.
312 if (std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device) !=
313 monitoredDevices_.end()) {
316 [device addObserver:self
317 forKeyPath:@"suspended"
320 [device addObserver:self
321 forKeyPath:@"connected"
324 monitoredDevices_.insert(device);
327 - (void)stopObserving:(CrAVCaptureDevice*)device {
328 DCHECK(device != nil);
329 std::set<CrAVCaptureDevice*>::iterator found =
330 std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device);
331 DCHECK(found != monitoredDevices_.end());
332 [device removeObserver:self
333 forKeyPath:@"suspended"];
334 [device removeObserver:self
335 forKeyPath:@"connected"];
336 monitoredDevices_.erase(found);
339 - (void)observeValueForKeyPath:(NSString*)keyPath
341 change:(NSDictionary*)change
342 context:(void*)context {
343 if ([keyPath isEqual:@"suspended"])
344 receiver_->OnDeviceChanged();
345 if ([keyPath isEqual:@"connected"])
346 [self stopObserving:static_cast<CrAVCaptureDevice*>(context)];
349 @end // @implementation CrAVFoundationDeviceObserver
353 DeviceMonitorMac::DeviceMonitorMac() {
354 // Both QTKit and AVFoundation do not need to be fired up until the user
355 // exercises a GetUserMedia. Bringing up either library and enumerating the
356 // devices in the system is an operation taking in the range of hundred of ms,
357 // so it is triggered explicitly from MediaStreamManager::StartMonitoring().
360 DeviceMonitorMac::~DeviceMonitorMac() {}
362 void DeviceMonitorMac::StartMonitoring() {
363 DCHECK(thread_checker_.CalledOnValidThread());
364 if (AVFoundationGlue::IsAVFoundationSupported()) {
365 DVLOG(1) << "Monitoring via AVFoundation";
366 device_monitor_impl_.reset(new AVFoundationMonitorImpl(this));
368 DVLOG(1) << "Monitoring via QTKit";
369 device_monitor_impl_.reset(new QTKitMonitorImpl(this));
373 void DeviceMonitorMac::NotifyDeviceChanged(
374 base::SystemMonitor::DeviceType type) {
375 // TODO(xians): Remove the global variable for SystemMonitor.
376 base::SystemMonitor::Get()->ProcessDevicesChanged(type);
379 } // namespace content