Upstream version 6.35.121.0
[platform/framework/web/crosswalk.git] / src / content / browser / device_monitor_mac.mm
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.
4
5 #include "content/browser/device_monitor_mac.h"
6
7 #import <QTKit/QTKit.h>
8
9 #include <set>
10
11 #include "base/logging.h"
12 #include "base/mac/scoped_nsobject.h"
13 #import "media/video/capture/mac/avfoundation_glue.h"
14
15 namespace {
16
17 // This class is used to keep track of system devices names and their types.
18 class DeviceInfo {
19  public:
20   enum DeviceType {
21     kAudio,
22     kVideo,
23     kMuxed,
24     kUnknown,
25     kInvalid
26   };
27
28   DeviceInfo(std::string unique_id, DeviceType type)
29       : unique_id_(unique_id), type_(type) {}
30
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_;
36   }
37
38   const std::string& unique_id() const { return unique_id_; }
39   DeviceType type() const { return type_; }
40
41  private:
42   std::string unique_id_;
43   DeviceType type_;
44   // Allow generated copy constructor and assignment.
45 };
46
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 {
50  public:
51   explicit DeviceMonitorMacImpl(content::DeviceMonitorMac* monitor)
52       : monitor_(monitor),
53         cached_devices_(),
54         device_arrival_(nil),
55         device_removal_(nil) {
56     DCHECK(monitor);
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));
62   }
63   virtual ~DeviceMonitorMacImpl() {}
64
65   virtual void OnDeviceChanged() = 0;
66
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);
73
74  protected:
75   content::DeviceMonitorMac* monitor_;
76   std::vector<DeviceInfo> cached_devices_;
77
78   // Handles to NSNotificationCenter block observers.
79   id device_arrival_;
80   id device_removal_;
81
82  private:
83   DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMacImpl);
84 };
85
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;
92
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
96   // the latter vector.
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();
107     } else {
108       cached_devices_.erase(cached_devices_iterator);
109     }
110   }
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();
120   }
121   // Update the cached devices with the current system snapshot.
122   cached_devices_ = snapshot_devices;
123
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);
128 }
129
130 class QTKitMonitorImpl : public DeviceMonitorMacImpl {
131  public:
132   explicit QTKitMonitorImpl(content::DeviceMonitorMac* monitor);
133   virtual ~QTKitMonitorImpl();
134
135   virtual void OnDeviceChanged() OVERRIDE;
136  private:
137   void CountDevices();
138   void OnAttributeChanged(NSNotification* notification);
139
140   id device_change_;
141 };
142
143 QTKitMonitorImpl::QTKitMonitorImpl(content::DeviceMonitorMac* monitor)
144     : DeviceMonitorMacImpl(monitor) {
145   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
146   device_arrival_ =
147       [nc addObserverForName:QTCaptureDeviceWasConnectedNotification
148                       object:nil
149                        queue:nil
150                   usingBlock:^(NSNotification* notification) {
151                       OnDeviceChanged();}];
152   device_removal_ =
153       [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification
154                       object:nil
155                        queue:nil
156                   usingBlock:^(NSNotification* notification) {
157                       OnDeviceChanged();}];
158   device_change_ =
159       [nc addObserverForName:QTCaptureDeviceAttributeDidChangeNotification
160                       object:nil
161                        queue:nil
162                   usingBlock:^(NSNotification* notification) {
163                       OnAttributeChanged(notification);}];
164 }
165
166 QTKitMonitorImpl::~QTKitMonitorImpl() {
167   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
168   [nc removeObserver:device_arrival_];
169   [nc removeObserver:device_removal_];
170   [nc removeObserver:device_change_];
171 }
172
173 void QTKitMonitorImpl::OnAttributeChanged(
174     NSNotification* notification) {
175   if ([[[notification userInfo]
176          objectForKey:QTCaptureDeviceChangedAttributeKey]
177       isEqualToString:QTCaptureDeviceSuspendedAttribute]) {
178     OnDeviceChanged();
179   }
180 }
181
182 void QTKitMonitorImpl::OnDeviceChanged() {
183   std::vector<DeviceInfo> snapshot_devices;
184
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]
192         boolValue]) {
193       device_type = DeviceInfo::kVideo;
194     } else if ([device hasMediaType:QTMediaTypeMuxed] &&
195         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
196         boolValue]) {
197       device_type = DeviceInfo::kMuxed;
198     } else if ([device hasMediaType:QTMediaTypeSound] &&
199         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
200         boolValue]) {
201       device_type = DeviceInfo::kAudio;
202     }
203     snapshot_devices.push_back(
204         DeviceInfo([[device uniqueID] UTF8String], device_type));
205   }
206   ConsolidateDevicesListAndNotify(snapshot_devices);
207 }
208
209 // Forward declaration for use by CrAVFoundationDeviceObserver.
210 class AVFoundationMonitorImpl;
211
212 }  // namespace
213
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 {
217  @private
218   AVFoundationMonitorImpl* receiver_;
219   // Member to keep track of the devices we are already monitoring.
220   std::set<CrAVCaptureDevice*> monitoredDevices_;
221 }
222
223 - (id)initWithChangeReceiver:(AVFoundationMonitorImpl*)receiver;
224 - (void)startObserving:(CrAVCaptureDevice*)device;
225 - (void)stopObserving:(CrAVCaptureDevice*)device;
226
227 @end
228
229 namespace {
230
231 class AVFoundationMonitorImpl : public DeviceMonitorMacImpl {
232  public:
233   explicit AVFoundationMonitorImpl(content::DeviceMonitorMac* monitor);
234   virtual ~AVFoundationMonitorImpl();
235
236   virtual void OnDeviceChanged() OVERRIDE;
237
238  private:
239   base::scoped_nsobject<CrAVFoundationDeviceObserver> suspend_observer_;
240   DISALLOW_COPY_AND_ASSIGN(AVFoundationMonitorImpl);
241 };
242
243 AVFoundationMonitorImpl::AVFoundationMonitorImpl(
244     content::DeviceMonitorMac* monitor)
245     : DeviceMonitorMacImpl(monitor) {
246   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
247   device_arrival_ =
248       [nc addObserverForName:AVFoundationGlue::
249           AVCaptureDeviceWasConnectedNotification()
250                       object:nil
251                        queue:nil
252                   usingBlock:^(NSNotification* notification) {
253                       OnDeviceChanged();}];
254   device_removal_ =
255       [nc addObserverForName:AVFoundationGlue::
256           AVCaptureDeviceWasDisconnectedNotification()
257                       object:nil
258                        queue:nil
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];
265 }
266
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];
273 }
274
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()]) {
283       if (suspended)
284         continue;
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;
290     }
291     snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String],
292                                           device_type));
293   }
294   ConsolidateDevicesListAndNotify(snapshot_devices);
295 }
296
297 }  // namespace
298
299 @implementation CrAVFoundationDeviceObserver
300
301 - (id)initWithChangeReceiver:(AVFoundationMonitorImpl*)receiver {
302   if ((self = [super init])) {
303     DCHECK(receiver != NULL);
304     receiver_ = receiver;
305   }
306   return self;
307 }
308
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()) {
314     return;
315   }
316   [device addObserver:self
317            forKeyPath:@"suspended"
318               options:0
319               context:device];
320   [device addObserver:self
321            forKeyPath:@"connected"
322               options:0
323               context:device];
324   monitoredDevices_.insert(device);
325 }
326
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);
337 }
338
339 - (void)observeValueForKeyPath:(NSString*)keyPath
340                       ofObject:(id)object
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)];
347 }
348
349 @end  // @implementation CrAVFoundationDeviceObserver
350
351 namespace content {
352
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().
358 }
359
360 DeviceMonitorMac::~DeviceMonitorMac() {}
361
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));
367   } else {
368     DVLOG(1) << "Monitoring via QTKit";
369     device_monitor_impl_.reset(new QTKitMonitorImpl(this));
370   }
371 }
372
373 void DeviceMonitorMac::NotifyDeviceChanged(
374     base::SystemMonitor::DeviceType type) {
375   // TODO(xians): Remove the global variable for SystemMonitor.
376   base::SystemMonitor::Get()->ProcessDevicesChanged(type);
377 }
378
379 }  // namespace content