Upstream version 5.34.92.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 "base/logging.h"
10 #import "media/video/capture/mac/avfoundation_glue.h"
11
12 namespace {
13
14 // This class is used to keep track of system devices names and their types.
15 class DeviceInfo {
16  public:
17   enum DeviceType {
18     kAudio,
19     kVideo,
20     kMuxed,
21     kUnknown,
22     kInvalid
23   };
24
25   DeviceInfo(std::string unique_id, DeviceType type)
26       : unique_id_(unique_id), type_(type) {}
27
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_;
33   }
34
35   const std::string& unique_id() const { return unique_id_; }
36   DeviceType type() const { return type_; }
37
38  private:
39   std::string unique_id_;
40   DeviceType type_;
41   // Allow generated copy constructor and assignment.
42 };
43
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 {
47  public:
48   explicit DeviceMonitorMacImpl(content::DeviceMonitorMac* monitor)
49       : monitor_(monitor),
50         cached_devices_(),
51         device_arrival_(nil),
52         device_removal_(nil) {
53     DCHECK(monitor);
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));
59   }
60   virtual ~DeviceMonitorMacImpl() {}
61
62   virtual void OnDeviceChanged() = 0;
63
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);
70
71  protected:
72   content::DeviceMonitorMac* monitor_;
73   std::vector<DeviceInfo> cached_devices_;
74
75   // Handles to NSNotificationCenter block observers.
76   id device_arrival_;
77   id device_removal_;
78
79  private:
80   DISALLOW_COPY_AND_ASSIGN(DeviceMonitorMacImpl);
81 };
82
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;
89
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
93   // the latter vector.
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();
104     } else {
105       cached_devices_.erase(cached_devices_iterator);
106     }
107   }
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();
117   }
118   // Update the cached devices with the current system snapshot.
119   cached_devices_ = snapshot_devices;
120
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);
125 }
126
127 class QTKitMonitorImpl : public DeviceMonitorMacImpl {
128  public:
129   explicit QTKitMonitorImpl(content::DeviceMonitorMac* monitor);
130   virtual ~QTKitMonitorImpl();
131
132   virtual void OnDeviceChanged() OVERRIDE;
133  private:
134   void CountDevices();
135   void OnAttributeChanged(NSNotification* notification);
136
137   id device_change_;
138 };
139
140 QTKitMonitorImpl::QTKitMonitorImpl(content::DeviceMonitorMac* monitor)
141     : DeviceMonitorMacImpl(monitor) {
142   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
143   device_arrival_ =
144       [nc addObserverForName:QTCaptureDeviceWasConnectedNotification
145                       object:nil
146                        queue:nil
147                   usingBlock:^(NSNotification* notification) {
148                       OnDeviceChanged();}];
149   device_removal_ =
150       [nc addObserverForName:QTCaptureDeviceWasDisconnectedNotification
151                       object:nil
152                        queue:nil
153                   usingBlock:^(NSNotification* notification) {
154                       OnDeviceChanged();}];
155   device_change_ =
156       [nc addObserverForName:QTCaptureDeviceAttributeDidChangeNotification
157                       object:nil
158                        queue:nil
159                   usingBlock:^(NSNotification* notification) {
160                       OnAttributeChanged(notification);}];
161 }
162
163 QTKitMonitorImpl::~QTKitMonitorImpl() {
164   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
165   [nc removeObserver:device_arrival_];
166   [nc removeObserver:device_removal_];
167   [nc removeObserver:device_change_];
168 }
169
170 void QTKitMonitorImpl::OnAttributeChanged(
171     NSNotification* notification) {
172   if ([[[notification userInfo]
173          objectForKey:QTCaptureDeviceChangedAttributeKey]
174       isEqualToString:QTCaptureDeviceSuspendedAttribute]) {
175     OnDeviceChanged();
176   }
177 }
178
179 void QTKitMonitorImpl::OnDeviceChanged() {
180   std::vector<DeviceInfo> snapshot_devices;
181
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]
189         boolValue]) {
190       device_type = DeviceInfo::kVideo;
191     } else if ([device hasMediaType:QTMediaTypeMuxed] &&
192         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
193         boolValue]) {
194       device_type = DeviceInfo::kMuxed;
195     } else if ([device hasMediaType:QTMediaTypeSound] &&
196         ![[device attributeForKey:QTCaptureDeviceSuspendedAttribute]
197         boolValue]) {
198       device_type = DeviceInfo::kAudio;
199     }
200     snapshot_devices.push_back(
201         DeviceInfo([[device uniqueID] UTF8String], device_type));
202   }
203   ConsolidateDevicesListAndNotify(snapshot_devices);
204 }
205
206 class AVFoundationMonitorImpl : public DeviceMonitorMacImpl {
207  public:
208   explicit AVFoundationMonitorImpl(content::DeviceMonitorMac* monitor);
209   virtual ~AVFoundationMonitorImpl();
210
211   virtual void OnDeviceChanged() OVERRIDE;
212 };
213
214 AVFoundationMonitorImpl::AVFoundationMonitorImpl(
215     content::DeviceMonitorMac* monitor)
216     : DeviceMonitorMacImpl(monitor) {
217   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
218   device_arrival_ =
219       [nc addObserverForName:AVFoundationGlue::
220           AVCaptureDeviceWasConnectedNotification()
221                       object:nil
222                        queue:nil
223                   usingBlock:^(NSNotification* notification) {
224                       OnDeviceChanged();}];
225   device_removal_ =
226       [nc addObserverForName:AVFoundationGlue::
227           AVCaptureDeviceWasDisconnectedNotification()
228                       object:nil
229                        queue:nil
230                   usingBlock:^(NSNotification* notification) {
231                       OnDeviceChanged();}];
232 }
233
234 AVFoundationMonitorImpl::~AVFoundationMonitorImpl() {
235   NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
236   [nc removeObserver:device_arrival_];
237   [nc removeObserver:device_removal_];
238 }
239
240 void AVFoundationMonitorImpl::OnDeviceChanged() {
241   std::vector<DeviceInfo> snapshot_devices;
242
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;
252     }
253     snapshot_devices.push_back(DeviceInfo([[device uniqueID] UTF8String],
254                                           device_type));
255   }
256   ConsolidateDevicesListAndNotify(snapshot_devices);
257 }
258
259 }  // namespace
260
261 namespace content {
262
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.
272   } else {
273     DVLOG(1) << "Monitoring via QTKit";
274     device_monitor_impl_.reset(new QTKitMonitorImpl(this));
275   }
276 }
277
278 DeviceMonitorMac::~DeviceMonitorMac() {}
279
280 void DeviceMonitorMac::NotifyDeviceChanged(
281     base::SystemMonitor::DeviceType type) {
282   // TODO(xians): Remove the global variable for SystemMonitor.
283   base::SystemMonitor::Get()->ProcessDevicesChanged(type);
284 }
285
286 }  // namespace content