1 | /* |
2 | * Copyright (C) 2011 Ericsson AB. All rights reserved. |
3 | * Copyright (C) 2012 Google Inc. All rights reserved. |
4 | * Copyright (C) 2013-2018 Apple Inc. All rights reserved. |
5 | * |
6 | * Redistribution and use in source and binary forms, with or without |
7 | * modification, are permitted provided that the following conditions |
8 | * are met: |
9 | * |
10 | * 1. Redistributions of source code must retain the above copyright |
11 | * notice, this list of conditions and the following disclaimer. |
12 | * 2. Redistributions in binary form must reproduce the above copyright |
13 | * notice, this list of conditions and the following disclaimer |
14 | * in the documentation and/or other materials provided with the |
15 | * distribution. |
16 | * 3. Neither the name of Ericsson nor the names of its contributors |
17 | * may be used to endorse or promote products derived from this |
18 | * software without specific prior written permission. |
19 | * |
20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
24 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 | */ |
32 | |
33 | #include "config.h" |
34 | #include "RealtimeMediaSourceCenter.h" |
35 | |
36 | #if ENABLE(MEDIA_STREAM) |
37 | |
38 | #include "CaptureDeviceManager.h" |
39 | #include "Logging.h" |
40 | #include "MediaStreamPrivate.h" |
41 | #include <wtf/SHA1.h> |
42 | |
43 | namespace WebCore { |
44 | |
45 | RealtimeMediaSourceCenter& RealtimeMediaSourceCenter::singleton() |
46 | { |
47 | ASSERT(isMainThread()); |
48 | static NeverDestroyed<RealtimeMediaSourceCenter> center; |
49 | return center; |
50 | } |
51 | |
52 | RealtimeMediaSourceCenter::RealtimeMediaSourceCenter() |
53 | { |
54 | m_supportedConstraints.setSupportsWidth(true); |
55 | m_supportedConstraints.setSupportsHeight(true); |
56 | m_supportedConstraints.setSupportsAspectRatio(true); |
57 | m_supportedConstraints.setSupportsFrameRate(true); |
58 | m_supportedConstraints.setSupportsFacingMode(true); |
59 | m_supportedConstraints.setSupportsVolume(true); |
60 | m_supportedConstraints.setSupportsDeviceId(true); |
61 | } |
62 | |
63 | RealtimeMediaSourceCenter::~RealtimeMediaSourceCenter() = default; |
64 | |
65 | void RealtimeMediaSourceCenter::createMediaStream(NewMediaStreamHandler&& completionHandler, String&& hashSalt, CaptureDevice&& audioDevice, CaptureDevice&& videoDevice, const MediaStreamRequest& request) |
66 | { |
67 | Vector<Ref<RealtimeMediaSource>> audioSources; |
68 | Vector<Ref<RealtimeMediaSource>> videoSources; |
69 | String invalidConstraint; |
70 | |
71 | if (audioDevice) { |
72 | auto audioSource = audioCaptureFactory().createAudioCaptureSource(WTFMove(audioDevice), String { hashSalt }, &request.audioConstraints); |
73 | if (audioSource) |
74 | audioSources.append(audioSource.source()); |
75 | else { |
76 | #if !LOG_DISABLED |
77 | if (!audioSource.errorMessage.isEmpty()) |
78 | LOG(Media, "RealtimeMediaSourceCenter::createMediaStream(%p), audio constraints failed to apply: %s" , this, audioSource.errorMessage.utf8().data()); |
79 | #endif |
80 | completionHandler(nullptr); |
81 | return; |
82 | } |
83 | } |
84 | |
85 | if (videoDevice) { |
86 | CaptureSourceOrError videoSource; |
87 | if (videoDevice.type() == CaptureDevice::DeviceType::Camera) |
88 | videoSource = videoCaptureFactory().createVideoCaptureSource(WTFMove(videoDevice), WTFMove(hashSalt), &request.videoConstraints); |
89 | else |
90 | videoSource = displayCaptureFactory().createDisplayCaptureSource(WTFMove(videoDevice), &request.videoConstraints); |
91 | |
92 | if (videoSource) |
93 | videoSources.append(videoSource.source()); |
94 | else { |
95 | #if !LOG_DISABLED |
96 | if (!videoSource.errorMessage.isEmpty()) |
97 | LOG(Media, "RealtimeMediaSourceCenter::createMediaStream(%p), video constraints failed to apply: %s" , this, videoSource.errorMessage.utf8().data()); |
98 | #endif |
99 | completionHandler(nullptr); |
100 | return; |
101 | } |
102 | } |
103 | |
104 | completionHandler(MediaStreamPrivate::create(audioSources, videoSources)); |
105 | } |
106 | |
107 | Vector<CaptureDevice> RealtimeMediaSourceCenter::getMediaStreamDevices() |
108 | { |
109 | Vector<CaptureDevice> result; |
110 | for (auto& device : audioCaptureFactory().audioCaptureDeviceManager().captureDevices()) { |
111 | if (device.enabled()) |
112 | result.append(device); |
113 | } |
114 | for (auto& device : videoCaptureFactory().videoCaptureDeviceManager().captureDevices()) { |
115 | if (device.enabled()) |
116 | result.append(device); |
117 | } |
118 | for (auto& device : displayCaptureFactory().displayCaptureDeviceManager().captureDevices()) { |
119 | if (device.enabled()) |
120 | result.append(device); |
121 | } |
122 | |
123 | return result; |
124 | } |
125 | |
126 | static void addStringToSHA1(SHA1& sha1, const String& string) |
127 | { |
128 | if (string.isEmpty()) |
129 | return; |
130 | |
131 | if (string.is8Bit() && string.isAllASCII()) { |
132 | const uint8_t nullByte = 0; |
133 | sha1.addBytes(string.characters8(), string.length()); |
134 | sha1.addBytes(&nullByte, 1); |
135 | return; |
136 | } |
137 | |
138 | auto utf8 = string.utf8(); |
139 | sha1.addBytes(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length() + 1); // Include terminating null byte. |
140 | } |
141 | |
142 | String RealtimeMediaSourceCenter::hashStringWithSalt(const String& id, const String& hashSalt) |
143 | { |
144 | if (id.isEmpty() || hashSalt.isEmpty()) |
145 | return emptyString(); |
146 | |
147 | SHA1 sha1; |
148 | |
149 | addStringToSHA1(sha1, id); |
150 | addStringToSHA1(sha1, hashSalt); |
151 | |
152 | SHA1::Digest digest; |
153 | sha1.computeHash(digest); |
154 | |
155 | return SHA1::hexDigest(digest).data(); |
156 | } |
157 | |
158 | void RealtimeMediaSourceCenter::setDevicesChangedObserver(std::function<void()>&& observer) |
159 | { |
160 | ASSERT(isMainThread()); |
161 | ASSERT(!m_deviceChangedObserver); |
162 | m_deviceChangedObserver = WTFMove(observer); |
163 | } |
164 | |
165 | void RealtimeMediaSourceCenter::captureDevicesChanged() |
166 | { |
167 | ASSERT(isMainThread()); |
168 | if (m_deviceChangedObserver) |
169 | m_deviceChangedObserver(); |
170 | } |
171 | |
172 | void RealtimeMediaSourceCenter::getDisplayMediaDevices(const MediaStreamRequest& request, Vector<DeviceInfo>& diaplayDeviceInfo, String& firstInvalidConstraint) |
173 | { |
174 | if (!request.videoConstraints.isValid) |
175 | return; |
176 | |
177 | String invalidConstraint; |
178 | for (auto& device : displayCaptureFactory().displayCaptureDeviceManager().captureDevices()) { |
179 | if (!device.enabled()) |
180 | return; |
181 | |
182 | auto sourceOrError = displayCaptureFactory().createDisplayCaptureSource(device, { }); |
183 | if (sourceOrError && sourceOrError.captureSource->supportsConstraints(request.videoConstraints, invalidConstraint)) |
184 | diaplayDeviceInfo.append({sourceOrError.captureSource->fitnessScore(), device}); |
185 | |
186 | if (!invalidConstraint.isEmpty() && firstInvalidConstraint.isEmpty()) |
187 | firstInvalidConstraint = invalidConstraint; |
188 | } |
189 | } |
190 | |
191 | void RealtimeMediaSourceCenter::getUserMediaDevices(const MediaStreamRequest& request, String&& hashSalt, Vector<DeviceInfo>& audioDeviceInfo, Vector<DeviceInfo>& videoDeviceInfo, String& firstInvalidConstraint) |
192 | { |
193 | String invalidConstraint; |
194 | if (request.audioConstraints.isValid) { |
195 | for (auto& device : audioCaptureFactory().audioCaptureDeviceManager().captureDevices()) { |
196 | if (!device.enabled()) |
197 | continue; |
198 | |
199 | auto sourceOrError = audioCaptureFactory().createAudioCaptureSource(device, String { hashSalt }, { }); |
200 | if (sourceOrError && sourceOrError.captureSource->supportsConstraints(request.audioConstraints, invalidConstraint)) |
201 | audioDeviceInfo.append({sourceOrError.captureSource->fitnessScore(), device}); |
202 | |
203 | if (!invalidConstraint.isEmpty() && firstInvalidConstraint.isEmpty()) |
204 | firstInvalidConstraint = invalidConstraint; |
205 | } |
206 | } |
207 | |
208 | if (request.videoConstraints.isValid) { |
209 | for (auto& device : videoCaptureFactory().videoCaptureDeviceManager().captureDevices()) { |
210 | if (!device.enabled()) |
211 | continue; |
212 | |
213 | auto sourceOrError = videoCaptureFactory().createVideoCaptureSource(device, String { hashSalt }, { }); |
214 | if (sourceOrError && sourceOrError.captureSource->supportsConstraints(request.videoConstraints, invalidConstraint)) |
215 | videoDeviceInfo.append({sourceOrError.captureSource->fitnessScore(), device}); |
216 | |
217 | if (!invalidConstraint.isEmpty() && firstInvalidConstraint.isEmpty()) |
218 | firstInvalidConstraint = invalidConstraint; |
219 | } |
220 | } |
221 | } |
222 | |
223 | void RealtimeMediaSourceCenter::validateRequestConstraints(ValidConstraintsHandler&& validHandler, InvalidConstraintsHandler&& invalidHandler, const MediaStreamRequest& request, String&& deviceIdentifierHashSalt) |
224 | { |
225 | struct { |
226 | bool operator()(const DeviceInfo& a, const DeviceInfo& b) |
227 | { |
228 | return a.fitnessScore > b.fitnessScore; |
229 | } |
230 | } sortBasedOnFitnessScore; |
231 | |
232 | Vector<DeviceInfo> audioDeviceInfo; |
233 | Vector<DeviceInfo> videoDeviceInfo; |
234 | String firstInvalidConstraint; |
235 | |
236 | if (request.type == MediaStreamRequest::Type::DisplayMedia) |
237 | getDisplayMediaDevices(request, videoDeviceInfo, firstInvalidConstraint); |
238 | else |
239 | getUserMediaDevices(request, String { deviceIdentifierHashSalt }, audioDeviceInfo, videoDeviceInfo, firstInvalidConstraint); |
240 | |
241 | if ((request.audioConstraints.isValid && audioDeviceInfo.isEmpty()) || (request.videoConstraints.isValid && videoDeviceInfo.isEmpty())) { |
242 | invalidHandler(firstInvalidConstraint); |
243 | return; |
244 | } |
245 | |
246 | Vector<CaptureDevice> audioDevices; |
247 | if (!audioDeviceInfo.isEmpty()) { |
248 | std::sort(audioDeviceInfo.begin(), audioDeviceInfo.end(), sortBasedOnFitnessScore); |
249 | audioDevices = WTF::map(audioDeviceInfo, [] (auto& info) { |
250 | return info.device; |
251 | }); |
252 | } |
253 | |
254 | Vector<CaptureDevice> videoDevices; |
255 | if (!videoDeviceInfo.isEmpty()) { |
256 | std::sort(videoDeviceInfo.begin(), videoDeviceInfo.end(), sortBasedOnFitnessScore); |
257 | videoDevices = WTF::map(videoDeviceInfo, [] (auto& info) { |
258 | return info.device; |
259 | }); |
260 | } |
261 | |
262 | validHandler(WTFMove(audioDevices), WTFMove(videoDevices), WTFMove(deviceIdentifierHashSalt)); |
263 | } |
264 | |
265 | void RealtimeMediaSourceCenter::setVideoCapturePageState(bool interrupted, bool pageMuted) |
266 | { |
267 | videoCaptureFactory().setVideoCapturePageState(interrupted, pageMuted); |
268 | } |
269 | |
270 | void RealtimeMediaSourceCenter::setAudioCaptureFactory(AudioCaptureFactory& factory) |
271 | { |
272 | m_audioCaptureFactoryOverride = &factory; |
273 | } |
274 | |
275 | void RealtimeMediaSourceCenter::unsetAudioCaptureFactory(AudioCaptureFactory& oldOverride) |
276 | { |
277 | ASSERT_UNUSED(oldOverride, m_audioCaptureFactoryOverride == &oldOverride); |
278 | if (&oldOverride == m_audioCaptureFactoryOverride) |
279 | m_audioCaptureFactoryOverride = nullptr; |
280 | } |
281 | |
282 | AudioCaptureFactory& RealtimeMediaSourceCenter::audioCaptureFactory() |
283 | { |
284 | return m_audioCaptureFactoryOverride ? *m_audioCaptureFactoryOverride : defaultAudioCaptureFactory(); |
285 | } |
286 | |
287 | void RealtimeMediaSourceCenter::setVideoCaptureFactory(VideoCaptureFactory& factory) |
288 | { |
289 | m_videoCaptureFactoryOverride = &factory; |
290 | } |
291 | void RealtimeMediaSourceCenter::unsetVideoCaptureFactory(VideoCaptureFactory& oldOverride) |
292 | { |
293 | ASSERT_UNUSED(oldOverride, m_videoCaptureFactoryOverride == &oldOverride); |
294 | if (&oldOverride == m_videoCaptureFactoryOverride) |
295 | m_videoCaptureFactoryOverride = nullptr; |
296 | } |
297 | |
298 | VideoCaptureFactory& RealtimeMediaSourceCenter::videoCaptureFactory() |
299 | { |
300 | return m_videoCaptureFactoryOverride ? *m_videoCaptureFactoryOverride : defaultVideoCaptureFactory(); |
301 | } |
302 | |
303 | void RealtimeMediaSourceCenter::setDisplayCaptureFactory(DisplayCaptureFactory& factory) |
304 | { |
305 | m_displayCaptureFactoryOverride = &factory; |
306 | } |
307 | |
308 | void RealtimeMediaSourceCenter::unsetDisplayCaptureFactory(DisplayCaptureFactory& oldOverride) |
309 | { |
310 | ASSERT_UNUSED(oldOverride, m_displayCaptureFactoryOverride == &oldOverride); |
311 | if (&oldOverride == m_displayCaptureFactoryOverride) |
312 | m_displayCaptureFactoryOverride = nullptr; |
313 | } |
314 | |
315 | DisplayCaptureFactory& RealtimeMediaSourceCenter::displayCaptureFactory() |
316 | { |
317 | return m_displayCaptureFactoryOverride ? *m_displayCaptureFactoryOverride : defaultDisplayCaptureFactory(); |
318 | } |
319 | |
320 | } // namespace WebCore |
321 | |
322 | #endif // ENABLE(MEDIA_STREAM) |
323 | |