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>
9 #include "base/logging.h"
10 #import "media/video/capture/mac/avfoundation_glue.h"
14 // This class is used to keep track of system devices names and their types.
25 DeviceInfo(std::string unique_id, DeviceType type)
26 : unique_id_(unique_id), type_(type) {}
28 // Operator== is needed here to use this class in a std::find. A given
29 // |unique_id_| always has the same |type_| so for comparison purposes the
30 // latter can be safely ignored.
31 bool operator==(const DeviceInfo& device) const {
32 return unique_id_ == device.unique_id_;
35 const std::string& unique_id() const { return unique_id_; }
36 DeviceType type() const { return type_; }
39 std::string unique_id_;
41 // Allow generated copy constructor and assignment.
44 // Base abstract class used by DeviceMonitorMac to interact with either a QTKit
45 // or an AVFoundation implementation of events and notifications.
46 class DeviceMonitorMacImpl {
48 explicit DeviceMonitorMacImpl(content::DeviceMonitorMac* monitor)
52 device_removal_(nil) {
54 // Initialise the devices_cache_ with a not-valid entry. For the case in
55 // which there is one single device in the system and we get notified when
56 // it gets removed, this will prevent the system from thinking that no
57 // devices were added nor removed and not notifying the |monitor_|.
58 cached_devices_.push_back(DeviceInfo("invalid", DeviceInfo::kInvalid));
60 virtual ~DeviceMonitorMacImpl() {}
62 virtual void OnDeviceChanged() = 0;
64 // Method called by the default notification center when a device is removed
65 // or added to the system. It will compare the |cached_devices_| with the
66 // current situation, update it, and, if there's an update, signal to
67 // |monitor_| with the appropriate device type.
68 void ConsolidateDevicesListAndNotify(
69 const std::vector<DeviceInfo>& snapshot_devices);
72 content::DeviceMonitorMac* monitor_;
73 std::vector<DeviceInfo> cached_devices_;
75 // Handles to NSNotificationCenter block observers.
80 DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMacImpl);
83 void DeviceMonitorMacImpl::ConsolidateDevicesListAndNotify(
84 const std::vector<DeviceInfo>& snapshot_devices) {
85 bool video_device_added = false;
86 bool audio_device_added = false;
87 bool video_device_removed = false;
88 bool audio_device_removed = false;
90 // Compare the current system devices snapshot with the ones cached to detect
91 // additions, present in the former but not in the latter. If we find a device
92 // in snapshot_devices entry also present in cached_devices, we remove it from
94 std::vector<DeviceInfo>::const_iterator it;
95 for (it = snapshot_devices.begin(); it != snapshot_devices.end(); ++it) {
96 std::vector<DeviceInfo>::iterator cached_devices_iterator =
97 std::find(cached_devices_.begin(), cached_devices_.end(), *it);
98 if (cached_devices_iterator == cached_devices_.end()) {
99 video_device_added |= ((it->type() == DeviceInfo::kVideo) ||
100 (it->type() == DeviceInfo::kMuxed));
101 audio_device_added |= ((it->type() == DeviceInfo::kAudio) ||
102 (it->type() == DeviceInfo::kMuxed));
103 DVLOG(1) << "Device has been added, id: " << it->unique_id();
105 cached_devices_.erase(cached_devices_iterator);
108 // All the remaining entries in cached_devices are removed devices.
109 for (it = cached_devices_.begin(); it != cached_devices_.end(); ++it) {
110 video_device_removed |= ((it->type() == DeviceInfo::kVideo) ||
111 (it->type() == DeviceInfo::kMuxed) ||
112 (it->type() == DeviceInfo::kInvalid));
113 audio_device_removed |= ((it->type() == DeviceInfo::kAudio) ||
114 (it->type() == DeviceInfo::kMuxed) ||
115 (it->type() == DeviceInfo::kInvalid));
116 DVLOG(1) << "Device has been removed, id: " << it->unique_id();
118 // Update the cached devices with the current system snapshot.
119 cached_devices_ = snapshot_devices;
121 if (video_device_added || video_device_removed)
122 monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
123 if (audio_device_added || audio_device_removed)
124 monitor_->NotifyDeviceChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
127 class QTKitMonitorImpl : public DeviceMonitorMacImpl {
129 explicit QTKitMonitorImpl(content::DeviceMonitorMac* monitor);
130 virtual ~QTKitMonitorImpl();
132 virtual void OnDeviceChanged() OVERRIDE;
135 void OnAttributeChanged(NSNotification* notification);
140 QTKitMonitorImpl::QTKitMonitorImpl(content::DeviceMonitorMac* monitor)
141 : DeviceMonitorMacImpl(monitor) {
142 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
144 [nc addObserverForName:QTCaptureDeviceWasConnectedNotification
147 usingBlock:^(NSNotification* notification) {
148 OnDeviceChanged();}];
150 [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification
153 usingBlock:^(NSNotification* notification) {
154 OnDeviceChanged();}];
156 [nc addObserverForName:QTCaptureDeviceAttributeDidChangeNotification
159 usingBlock:^(NSNotification* notification) {
160 OnAttributeChanged(notification);}];
163 QTKitMonitorImpl::~QTKitMonitorImpl() {
164 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
165 [nc removeObserver:device_arrival_];
166 [nc removeObserver:device_removal_];
167 [nc removeObserver:device_change_];
170 void QTKitMonitorImpl::OnAttributeChanged(
171 NSNotification* notification) {
172 if ([[[notification userInfo]
173 objectForKey:QTCaptureDeviceChangedAttributeKey]
174 isEqualToString:QTCaptureDeviceSuspendedAttribute]) {
179 void QTKitMonitorImpl::OnDeviceChanged() {
180 std::vector<DeviceInfo> snapshot_devices;
182 NSArray* devices = [QTCaptureDevice inputDevices];
183 for (QTCaptureDevice* device in devices) {
184 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
185 // Act as if suspended video capture devices are not attached. For
186 // example, a laptop's internal webcam is suspended when the lid is closed.
187 if ([device hasMediaType:QTMediaTypeVideo] &&
188 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
190 device_type = DeviceInfo::kVideo;
191 } else if ([device hasMediaType:QTMediaTypeMuxed] &&
192 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
194 device_type = DeviceInfo::kMuxed;
195 } else if ([device hasMediaType:QTMediaTypeSound] &&
196 ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
198 device_type = DeviceInfo::kAudio;
200 snapshot_devices.push_back(
201 DeviceInfo([[device uniqueID] UTF8String], device_type));
203 ConsolidateDevicesListAndNotify(snapshot_devices);
206 class AVFoundationMonitorImpl : public DeviceMonitorMacImpl {
208 explicit AVFoundationMonitorImpl(content::DeviceMonitorMac* monitor);
209 virtual ~AVFoundationMonitorImpl();
211 virtual void OnDeviceChanged() OVERRIDE;
214 AVFoundationMonitorImpl::AVFoundationMonitorImpl(
215 content::DeviceMonitorMac* monitor)
216 : DeviceMonitorMacImpl(monitor) {
217 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
219 [nc addObserverForName:AVFoundationGlue::
220 AVCaptureDeviceWasConnectedNotification()
223 usingBlock:^(NSNotification* notification) {
224 OnDeviceChanged();}];
226 [nc addObserverForName:AVFoundationGlue::
227 AVCaptureDeviceWasDisconnectedNotification()
230 usingBlock:^(NSNotification* notification) {
231 OnDeviceChanged();}];
234 AVFoundationMonitorImpl::~AVFoundationMonitorImpl() {
235 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
236 [nc removeObserver:device_arrival_];
237 [nc removeObserver:device_removal_];
240 void AVFoundationMonitorImpl::OnDeviceChanged() {
241 std::vector<DeviceInfo> snapshot_devices;
243 NSArray* devices = [AVCaptureDeviceGlue devices];
244 for (CrAVCaptureDevice* device in devices) {
245 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
246 if ([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()]) {
247 device_type = DeviceInfo::kVideo;
248 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeMuxed()]) {
249 device_type = DeviceInfo::kMuxed;
250 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeAudio()]) {
251 device_type = DeviceInfo::kAudio;
253 snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String],
256 ConsolidateDevicesListAndNotify(snapshot_devices);
263 DeviceMonitorMac::DeviceMonitorMac() {
264 if (AVFoundationGlue::IsAVFoundationSupported()) {
265 DVLOG(1) << "Monitoring via AVFoundation";
266 device_monitor_impl_.reset(new AVFoundationMonitorImpl(this));
267 // For the AVFoundation to start sending connect/disconnect notifications,
268 // the AVFoundation NSBundle has to be loaded and the devices enumerated.
269 // This operation seems to take in the range of hundred of ms. so should be
270 // moved to the point when is needed, and that is during
271 // DeviceVideoCaptureMac +getDeviceNames.
273 DVLOG(1) << "Monitoring via QTKit";
274 device_monitor_impl_.reset(new QTKitMonitorImpl(this));
278 DeviceMonitorMac::~DeviceMonitorMac() {}
280 void DeviceMonitorMac::NotifyDeviceChanged(
281 base::SystemMonitor::DeviceType type) {
282 // TODO(xians): Remove the global variable for SystemMonitor.
283 base::SystemMonitor::Get()->ProcessDevicesChanged(type);
286 } // namespace content