|
1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ |
|
2 /* vim: set ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "MediaManager.h" |
|
8 |
|
9 #include "MediaStreamGraph.h" |
|
10 #include "GetUserMediaRequest.h" |
|
11 #include "nsHashPropertyBag.h" |
|
12 #ifdef MOZ_WIDGET_GONK |
|
13 #include "nsIAudioManager.h" |
|
14 #endif |
|
15 #include "nsIDOMFile.h" |
|
16 #include "nsIEventTarget.h" |
|
17 #include "nsIUUIDGenerator.h" |
|
18 #include "nsIScriptGlobalObject.h" |
|
19 #include "nsIPermissionManager.h" |
|
20 #include "nsIPopupWindowManager.h" |
|
21 #include "nsISupportsArray.h" |
|
22 #include "nsIDocShell.h" |
|
23 #include "nsIDocument.h" |
|
24 #include "nsISupportsPrimitives.h" |
|
25 #include "nsIInterfaceRequestorUtils.h" |
|
26 #include "mozilla/Types.h" |
|
27 #include "mozilla/dom/ContentChild.h" |
|
28 #include "mozilla/dom/MediaStreamBinding.h" |
|
29 #include "mozilla/dom/MediaStreamTrackBinding.h" |
|
30 #include "mozilla/dom/GetUserMediaRequestBinding.h" |
|
31 #include "MediaTrackConstraints.h" |
|
32 |
|
33 #include "Latency.h" |
|
34 |
|
35 // For PR_snprintf |
|
36 #include "prprf.h" |
|
37 |
|
38 #include "nsJSUtils.h" |
|
39 #include "nsDOMFile.h" |
|
40 #include "nsGlobalWindow.h" |
|
41 |
|
42 /* Using WebRTC backend on Desktops (Mac, Windows, Linux), otherwise default */ |
|
43 #include "MediaEngineDefault.h" |
|
44 #if defined(MOZ_WEBRTC) |
|
45 #include "MediaEngineWebRTC.h" |
|
46 #endif |
|
47 |
|
48 #ifdef MOZ_B2G |
|
49 #include "MediaPermissionGonk.h" |
|
50 #endif |
|
51 |
|
52 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to |
|
53 // GetTickCount() and conflicts with MediaStream::GetCurrentTime. |
|
54 #ifdef GetCurrentTime |
|
55 #undef GetCurrentTime |
|
56 #endif |
|
57 |
|
58 namespace mozilla { |
|
59 |
|
60 #ifdef LOG |
|
61 #undef LOG |
|
62 #endif |
|
63 |
|
64 #ifdef PR_LOGGING |
|
65 PRLogModuleInfo* |
|
66 GetMediaManagerLog() |
|
67 { |
|
68 static PRLogModuleInfo *sLog; |
|
69 if (!sLog) |
|
70 sLog = PR_NewLogModule("MediaManager"); |
|
71 return sLog; |
|
72 } |
|
73 #define LOG(msg) PR_LOG(GetMediaManagerLog(), PR_LOG_DEBUG, msg) |
|
74 #else |
|
75 #define LOG(msg) |
|
76 #endif |
|
77 |
|
78 using dom::MediaStreamConstraints; // Outside API (contains JSObject) |
|
79 using dom::MediaTrackConstraintSet; // Mandatory or optional constraints |
|
80 using dom::MediaTrackConstraints; // Raw mMandatory (as JSObject) |
|
81 using dom::GetUserMediaRequest; |
|
82 using dom::Sequence; |
|
83 using dom::OwningBooleanOrMediaTrackConstraints; |
|
84 using dom::SupportedAudioConstraints; |
|
85 using dom::SupportedVideoConstraints; |
|
86 |
|
87 ErrorCallbackRunnable::ErrorCallbackRunnable( |
|
88 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess, |
|
89 nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError, |
|
90 const nsAString& aErrorMsg, uint64_t aWindowID) |
|
91 : mErrorMsg(aErrorMsg) |
|
92 , mWindowID(aWindowID) |
|
93 , mManager(MediaManager::GetInstance()) |
|
94 { |
|
95 mSuccess.swap(aSuccess); |
|
96 mError.swap(aError); |
|
97 } |
|
98 |
|
99 ErrorCallbackRunnable::~ErrorCallbackRunnable() |
|
100 { |
|
101 MOZ_ASSERT(!mSuccess && !mError); |
|
102 } |
|
103 |
|
104 NS_IMETHODIMP |
|
105 ErrorCallbackRunnable::Run() |
|
106 { |
|
107 // Only run if the window is still active. |
|
108 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
|
109 |
|
110 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget(); |
|
111 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget(); |
|
112 |
|
113 if (!(mManager->IsWindowStillActive(mWindowID))) { |
|
114 return NS_OK; |
|
115 } |
|
116 // This is safe since we're on main-thread, and the windowlist can only |
|
117 // be invalidated from the main-thread (see OnNavigation) |
|
118 error->OnError(mErrorMsg); |
|
119 return NS_OK; |
|
120 } |
|
121 |
|
122 /** |
|
123 * Invoke the "onSuccess" callback in content. The callback will take a |
|
124 * DOMBlob in the case of {picture:true}, and a MediaStream in the case of |
|
125 * {audio:true} or {video:true}. There is a constructor available for each |
|
126 * form. Do this only on the main thread. |
|
127 */ |
|
128 class SuccessCallbackRunnable : public nsRunnable |
|
129 { |
|
130 public: |
|
131 SuccessCallbackRunnable( |
|
132 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess, |
|
133 nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError, |
|
134 nsIDOMFile* aFile, uint64_t aWindowID) |
|
135 : mFile(aFile) |
|
136 , mWindowID(aWindowID) |
|
137 , mManager(MediaManager::GetInstance()) |
|
138 { |
|
139 mSuccess.swap(aSuccess); |
|
140 mError.swap(aError); |
|
141 } |
|
142 |
|
143 NS_IMETHOD |
|
144 Run() |
|
145 { |
|
146 // Only run if the window is still active. |
|
147 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
|
148 |
|
149 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget(); |
|
150 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget(); |
|
151 |
|
152 if (!(mManager->IsWindowStillActive(mWindowID))) { |
|
153 return NS_OK; |
|
154 } |
|
155 // This is safe since we're on main-thread, and the windowlist can only |
|
156 // be invalidated from the main-thread (see OnNavigation) |
|
157 success->OnSuccess(mFile); |
|
158 return NS_OK; |
|
159 } |
|
160 |
|
161 private: |
|
162 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess; |
|
163 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError; |
|
164 nsCOMPtr<nsIDOMFile> mFile; |
|
165 uint64_t mWindowID; |
|
166 nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable |
|
167 }; |
|
168 |
|
169 /** |
|
170 * Invoke the GetUserMediaDevices success callback. Wrapped in a runnable |
|
171 * so that it may be called on the main thread. The error callback is also |
|
172 * passed so it can be released correctly. |
|
173 */ |
|
174 class DeviceSuccessCallbackRunnable: public nsRunnable |
|
175 { |
|
176 public: |
|
177 DeviceSuccessCallbackRunnable( |
|
178 uint64_t aWindowID, |
|
179 nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback>& aSuccess, |
|
180 nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError, |
|
181 nsTArray<nsCOMPtr<nsIMediaDevice> >* aDevices) |
|
182 : mDevices(aDevices) |
|
183 , mWindowID(aWindowID) |
|
184 , mManager(MediaManager::GetInstance()) |
|
185 { |
|
186 mSuccess.swap(aSuccess); |
|
187 mError.swap(aError); |
|
188 } |
|
189 |
|
190 NS_IMETHOD |
|
191 Run() |
|
192 { |
|
193 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
|
194 |
|
195 // Only run if window is still on our active list. |
|
196 if (!mManager->IsWindowStillActive(mWindowID)) { |
|
197 return NS_OK; |
|
198 } |
|
199 |
|
200 nsCOMPtr<nsIWritableVariant> devices = |
|
201 do_CreateInstance("@mozilla.org/variant;1"); |
|
202 |
|
203 int32_t len = mDevices->Length(); |
|
204 if (len == 0) { |
|
205 // XXX |
|
206 // We should in the future return an empty array, and dynamically add |
|
207 // devices to the dropdowns if things are hotplugged while the |
|
208 // requester is up. |
|
209 mError->OnError(NS_LITERAL_STRING("NO_DEVICES_FOUND")); |
|
210 return NS_OK; |
|
211 } |
|
212 |
|
213 nsTArray<nsIMediaDevice*> tmp(len); |
|
214 for (int32_t i = 0; i < len; i++) { |
|
215 tmp.AppendElement(mDevices->ElementAt(i)); |
|
216 } |
|
217 |
|
218 devices->SetAsArray(nsIDataType::VTYPE_INTERFACE, |
|
219 &NS_GET_IID(nsIMediaDevice), |
|
220 mDevices->Length(), |
|
221 const_cast<void*>( |
|
222 static_cast<const void*>(tmp.Elements()) |
|
223 )); |
|
224 |
|
225 mSuccess->OnSuccess(devices); |
|
226 return NS_OK; |
|
227 } |
|
228 |
|
229 private: |
|
230 nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mSuccess; |
|
231 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError; |
|
232 nsAutoPtr<nsTArray<nsCOMPtr<nsIMediaDevice> > > mDevices; |
|
233 uint64_t mWindowID; |
|
234 nsRefPtr<MediaManager> mManager; |
|
235 }; |
|
236 |
|
237 // Handle removing GetUserMediaCallbackMediaStreamListener from main thread |
|
238 class GetUserMediaListenerRemove: public nsRunnable |
|
239 { |
|
240 public: |
|
241 GetUserMediaListenerRemove(uint64_t aWindowID, |
|
242 GetUserMediaCallbackMediaStreamListener *aListener) |
|
243 : mWindowID(aWindowID) |
|
244 , mListener(aListener) {} |
|
245 |
|
246 NS_IMETHOD |
|
247 Run() |
|
248 { |
|
249 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
|
250 nsRefPtr<MediaManager> manager(MediaManager::GetInstance()); |
|
251 manager->RemoveFromWindowList(mWindowID, mListener); |
|
252 return NS_OK; |
|
253 } |
|
254 |
|
255 protected: |
|
256 uint64_t mWindowID; |
|
257 nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; |
|
258 }; |
|
259 |
|
260 /** |
|
261 * nsIMediaDevice implementation. |
|
262 */ |
|
263 NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice) |
|
264 |
|
265 MediaDevice* MediaDevice::Create(MediaEngineVideoSource* source) { |
|
266 return new VideoDevice(source); |
|
267 } |
|
268 |
|
269 MediaDevice* MediaDevice::Create(MediaEngineAudioSource* source) { |
|
270 return new AudioDevice(source); |
|
271 } |
|
272 |
|
273 MediaDevice::MediaDevice(MediaEngineSource* aSource) |
|
274 : mHasFacingMode(false) |
|
275 , mSource(aSource) { |
|
276 mSource->GetName(mName); |
|
277 mSource->GetUUID(mID); |
|
278 } |
|
279 |
|
280 VideoDevice::VideoDevice(MediaEngineVideoSource* aSource) |
|
281 : MediaDevice(aSource) { |
|
282 #ifdef MOZ_B2G_CAMERA |
|
283 if (mName.EqualsLiteral("back")) { |
|
284 mHasFacingMode = true; |
|
285 mFacingMode = dom::VideoFacingModeEnum::Environment; |
|
286 } else if (mName.EqualsLiteral("front")) { |
|
287 mHasFacingMode = true; |
|
288 mFacingMode = dom::VideoFacingModeEnum::User; |
|
289 } |
|
290 #endif // MOZ_B2G_CAMERA |
|
291 |
|
292 // Kludge to test user-facing cameras on OSX. |
|
293 if (mName.Find(NS_LITERAL_STRING("Face")) != -1) { |
|
294 mHasFacingMode = true; |
|
295 mFacingMode = dom::VideoFacingModeEnum::User; |
|
296 } |
|
297 } |
|
298 |
|
299 AudioDevice::AudioDevice(MediaEngineAudioSource* aSource) |
|
300 : MediaDevice(aSource) {} |
|
301 |
|
302 NS_IMETHODIMP |
|
303 MediaDevice::GetName(nsAString& aName) |
|
304 { |
|
305 aName.Assign(mName); |
|
306 return NS_OK; |
|
307 } |
|
308 |
|
309 NS_IMETHODIMP |
|
310 MediaDevice::GetType(nsAString& aType) |
|
311 { |
|
312 return NS_OK; |
|
313 } |
|
314 |
|
315 NS_IMETHODIMP |
|
316 VideoDevice::GetType(nsAString& aType) |
|
317 { |
|
318 aType.Assign(NS_LITERAL_STRING("video")); |
|
319 return NS_OK; |
|
320 } |
|
321 |
|
322 NS_IMETHODIMP |
|
323 AudioDevice::GetType(nsAString& aType) |
|
324 { |
|
325 aType.Assign(NS_LITERAL_STRING("audio")); |
|
326 return NS_OK; |
|
327 } |
|
328 |
|
329 NS_IMETHODIMP |
|
330 MediaDevice::GetId(nsAString& aID) |
|
331 { |
|
332 aID.Assign(mID); |
|
333 return NS_OK; |
|
334 } |
|
335 |
|
336 NS_IMETHODIMP |
|
337 MediaDevice::GetFacingMode(nsAString& aFacingMode) |
|
338 { |
|
339 if (mHasFacingMode) { |
|
340 aFacingMode.Assign(NS_ConvertUTF8toUTF16( |
|
341 dom::VideoFacingModeEnumValues::strings[uint32_t(mFacingMode)].value)); |
|
342 } else { |
|
343 aFacingMode.Truncate(0); |
|
344 } |
|
345 return NS_OK; |
|
346 } |
|
347 |
|
348 MediaEngineVideoSource* |
|
349 VideoDevice::GetSource() |
|
350 { |
|
351 return static_cast<MediaEngineVideoSource*>(&*mSource); |
|
352 } |
|
353 |
|
354 MediaEngineAudioSource* |
|
355 AudioDevice::GetSource() |
|
356 { |
|
357 return static_cast<MediaEngineAudioSource*>(&*mSource); |
|
358 } |
|
359 |
|
360 /** |
|
361 * A subclass that we only use to stash internal pointers to MediaStreamGraph objects |
|
362 * that need to be cleaned up. |
|
363 */ |
|
364 class nsDOMUserMediaStream : public DOMLocalMediaStream |
|
365 { |
|
366 public: |
|
367 static already_AddRefed<nsDOMUserMediaStream> |
|
368 CreateTrackUnionStream(nsIDOMWindow* aWindow, |
|
369 MediaEngineSource *aAudioSource, |
|
370 MediaEngineSource *aVideoSource) |
|
371 { |
|
372 DOMMediaStream::TrackTypeHints hints = |
|
373 (aAudioSource ? DOMMediaStream::HINT_CONTENTS_AUDIO : 0) | |
|
374 (aVideoSource ? DOMMediaStream::HINT_CONTENTS_VIDEO : 0); |
|
375 |
|
376 nsRefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aAudioSource); |
|
377 stream->InitTrackUnionStream(aWindow, hints); |
|
378 return stream.forget(); |
|
379 } |
|
380 |
|
381 nsDOMUserMediaStream(MediaEngineSource *aAudioSource) : |
|
382 mAudioSource(aAudioSource), |
|
383 mEchoOn(true), |
|
384 mAgcOn(false), |
|
385 mNoiseOn(true), |
|
386 #ifdef MOZ_WEBRTC |
|
387 mEcho(webrtc::kEcDefault), |
|
388 mAgc(webrtc::kAgcDefault), |
|
389 mNoise(webrtc::kNsDefault), |
|
390 #else |
|
391 mEcho(0), |
|
392 mAgc(0), |
|
393 mNoise(0), |
|
394 #endif |
|
395 mPlayoutDelay(20) |
|
396 {} |
|
397 |
|
398 virtual ~nsDOMUserMediaStream() |
|
399 { |
|
400 Stop(); |
|
401 |
|
402 if (mPort) { |
|
403 mPort->Destroy(); |
|
404 } |
|
405 if (mSourceStream) { |
|
406 mSourceStream->Destroy(); |
|
407 } |
|
408 } |
|
409 |
|
410 virtual void Stop() |
|
411 { |
|
412 if (mSourceStream) { |
|
413 mSourceStream->EndAllTrackAndFinish(); |
|
414 } |
|
415 } |
|
416 |
|
417 // Allow getUserMedia to pass input data directly to PeerConnection/MediaPipeline |
|
418 virtual bool AddDirectListener(MediaStreamDirectListener *aListener) MOZ_OVERRIDE |
|
419 { |
|
420 if (mSourceStream) { |
|
421 mSourceStream->AddDirectListener(aListener); |
|
422 return true; // application should ignore NotifyQueuedTrackData |
|
423 } |
|
424 return false; |
|
425 } |
|
426 |
|
427 virtual void |
|
428 AudioConfig(bool aEchoOn, uint32_t aEcho, |
|
429 bool aAgcOn, uint32_t aAgc, |
|
430 bool aNoiseOn, uint32_t aNoise, |
|
431 int32_t aPlayoutDelay) |
|
432 { |
|
433 mEchoOn = aEchoOn; |
|
434 mEcho = aEcho; |
|
435 mAgcOn = aAgcOn; |
|
436 mAgc = aAgc; |
|
437 mNoiseOn = aNoiseOn; |
|
438 mNoise = aNoise; |
|
439 mPlayoutDelay = aPlayoutDelay; |
|
440 } |
|
441 |
|
442 virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) MOZ_OVERRIDE |
|
443 { |
|
444 if (mSourceStream) { |
|
445 mSourceStream->RemoveDirectListener(aListener); |
|
446 } |
|
447 } |
|
448 |
|
449 // let us intervene for direct listeners when someone does track.enabled = false |
|
450 virtual void SetTrackEnabled(TrackID aID, bool aEnabled) MOZ_OVERRIDE |
|
451 { |
|
452 // We encapsulate the SourceMediaStream and TrackUnion into one entity, so |
|
453 // we can handle the disabling at the SourceMediaStream |
|
454 |
|
455 // We need to find the input track ID for output ID aID, so we let the TrackUnion |
|
456 // forward the request to the source and translate the ID |
|
457 GetStream()->AsProcessedStream()->ForwardTrackEnabled(aID, aEnabled); |
|
458 } |
|
459 |
|
460 // The actual MediaStream is a TrackUnionStream. But these resources need to be |
|
461 // explicitly destroyed too. |
|
462 nsRefPtr<SourceMediaStream> mSourceStream; |
|
463 nsRefPtr<MediaInputPort> mPort; |
|
464 nsRefPtr<MediaEngineSource> mAudioSource; // so we can turn on AEC |
|
465 bool mEchoOn; |
|
466 bool mAgcOn; |
|
467 bool mNoiseOn; |
|
468 uint32_t mEcho; |
|
469 uint32_t mAgc; |
|
470 uint32_t mNoise; |
|
471 uint32_t mPlayoutDelay; |
|
472 }; |
|
473 |
|
474 /** |
|
475 * Creates a MediaStream, attaches a listener and fires off a success callback |
|
476 * to the DOM with the stream. We also pass in the error callback so it can |
|
477 * be released correctly. |
|
478 * |
|
479 * All of this must be done on the main thread! |
|
480 * |
|
481 * Note that the various GetUserMedia Runnable classes currently allow for |
|
482 * two streams. If we ever need to support getting more than two streams |
|
483 * at once, we could convert everything to nsTArray<nsRefPtr<blah> >'s, |
|
484 * though that would complicate the constructors some. Currently the |
|
485 * GetUserMedia spec does not allow for more than 2 streams to be obtained in |
|
486 * one call, to simplify handling of constraints. |
|
487 */ |
|
488 class GetUserMediaStreamRunnable : public nsRunnable |
|
489 { |
|
490 public: |
|
491 GetUserMediaStreamRunnable( |
|
492 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess, |
|
493 nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError, |
|
494 uint64_t aWindowID, |
|
495 GetUserMediaCallbackMediaStreamListener* aListener, |
|
496 MediaEngineSource* aAudioSource, |
|
497 MediaEngineSource* aVideoSource) |
|
498 : mAudioSource(aAudioSource) |
|
499 , mVideoSource(aVideoSource) |
|
500 , mWindowID(aWindowID) |
|
501 , mListener(aListener) |
|
502 , mManager(MediaManager::GetInstance()) |
|
503 { |
|
504 mSuccess.swap(aSuccess); |
|
505 mError.swap(aError); |
|
506 } |
|
507 |
|
508 ~GetUserMediaStreamRunnable() {} |
|
509 |
|
510 class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback |
|
511 { |
|
512 public: |
|
513 TracksAvailableCallback(MediaManager* aManager, |
|
514 nsIDOMGetUserMediaSuccessCallback* aSuccess, |
|
515 uint64_t aWindowID, |
|
516 DOMMediaStream* aStream) |
|
517 : mWindowID(aWindowID), mSuccess(aSuccess), mManager(aManager), |
|
518 mStream(aStream) {} |
|
519 virtual void NotifyTracksAvailable(DOMMediaStream* aStream) MOZ_OVERRIDE |
|
520 { |
|
521 // We're in the main thread, so no worries here. |
|
522 if (!(mManager->IsWindowStillActive(mWindowID))) { |
|
523 return; |
|
524 } |
|
525 |
|
526 // Start currentTime from the point where this stream was successfully |
|
527 // returned. |
|
528 aStream->SetLogicalStreamStartTime(aStream->GetStream()->GetCurrentTime()); |
|
529 |
|
530 // This is safe since we're on main-thread, and the windowlist can only |
|
531 // be invalidated from the main-thread (see OnNavigation) |
|
532 LOG(("Returning success for getUserMedia()")); |
|
533 mSuccess->OnSuccess(aStream); |
|
534 } |
|
535 uint64_t mWindowID; |
|
536 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess; |
|
537 nsRefPtr<MediaManager> mManager; |
|
538 // Keep the DOMMediaStream alive until the NotifyTracksAvailable callback |
|
539 // has fired, otherwise we might immediately destroy the DOMMediaStream and |
|
540 // shut down the underlying MediaStream prematurely. |
|
541 // This creates a cycle which is broken when NotifyTracksAvailable |
|
542 // is fired (which will happen unless the browser shuts down, |
|
543 // since we only add this callback when we've successfully appended |
|
544 // the desired tracks in the MediaStreamGraph) or when |
|
545 // DOMMediaStream::NotifyMediaStreamGraphShutdown is called. |
|
546 nsRefPtr<DOMMediaStream> mStream; |
|
547 }; |
|
548 |
|
549 NS_IMETHOD |
|
550 Run() |
|
551 { |
|
552 #ifdef MOZ_WEBRTC |
|
553 int32_t aec = (int32_t) webrtc::kEcUnchanged; |
|
554 int32_t agc = (int32_t) webrtc::kAgcUnchanged; |
|
555 int32_t noise = (int32_t) webrtc::kNsUnchanged; |
|
556 #else |
|
557 int32_t aec = 0, agc = 0, noise = 0; |
|
558 #endif |
|
559 bool aec_on = false, agc_on = false, noise_on = false; |
|
560 int32_t playout_delay = 0; |
|
561 |
|
562 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
|
563 nsPIDOMWindow *window = static_cast<nsPIDOMWindow*> |
|
564 (nsGlobalWindow::GetInnerWindowWithId(mWindowID)); |
|
565 |
|
566 // We're on main-thread, and the windowlist can only |
|
567 // be invalidated from the main-thread (see OnNavigation) |
|
568 StreamListeners* listeners = mManager->GetWindowListeners(mWindowID); |
|
569 if (!listeners || !window || !window->GetExtantDoc()) { |
|
570 // This window is no longer live. mListener has already been removed |
|
571 return NS_OK; |
|
572 } |
|
573 |
|
574 #ifdef MOZ_WEBRTC |
|
575 // Right now these configs are only of use if webrtc is available |
|
576 nsresult rv; |
|
577 nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); |
|
578 if (NS_SUCCEEDED(rv)) { |
|
579 nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs); |
|
580 |
|
581 if (branch) { |
|
582 branch->GetBoolPref("media.getusermedia.aec_enabled", &aec_on); |
|
583 branch->GetIntPref("media.getusermedia.aec", &aec); |
|
584 branch->GetBoolPref("media.getusermedia.agc_enabled", &agc_on); |
|
585 branch->GetIntPref("media.getusermedia.agc", &agc); |
|
586 branch->GetBoolPref("media.getusermedia.noise_enabled", &noise_on); |
|
587 branch->GetIntPref("media.getusermedia.noise", &noise); |
|
588 branch->GetIntPref("media.getusermedia.playout_delay", &playout_delay); |
|
589 } |
|
590 } |
|
591 #endif |
|
592 // Create a media stream. |
|
593 nsRefPtr<nsDOMUserMediaStream> trackunion = |
|
594 nsDOMUserMediaStream::CreateTrackUnionStream(window, mAudioSource, |
|
595 mVideoSource); |
|
596 if (!trackunion) { |
|
597 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget(); |
|
598 LOG(("Returning error for getUserMedia() - no stream")); |
|
599 error->OnError(NS_LITERAL_STRING("NO_STREAM")); |
|
600 return NS_OK; |
|
601 } |
|
602 trackunion->AudioConfig(aec_on, (uint32_t) aec, |
|
603 agc_on, (uint32_t) agc, |
|
604 noise_on, (uint32_t) noise, |
|
605 playout_delay); |
|
606 |
|
607 |
|
608 MediaStreamGraph* gm = MediaStreamGraph::GetInstance(); |
|
609 nsRefPtr<SourceMediaStream> stream = gm->CreateSourceStream(nullptr); |
|
610 |
|
611 // connect the source stream to the track union stream to avoid us blocking |
|
612 trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true); |
|
613 nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()-> |
|
614 AllocateInputPort(stream, MediaInputPort::FLAG_BLOCK_OUTPUT); |
|
615 trackunion->mSourceStream = stream; |
|
616 trackunion->mPort = port.forget(); |
|
617 // Log the relationship between SourceMediaStream and TrackUnion stream |
|
618 // Make sure logger starts before capture |
|
619 AsyncLatencyLogger::Get(true); |
|
620 LogLatency(AsyncLatencyLogger::MediaStreamCreate, |
|
621 reinterpret_cast<uint64_t>(stream.get()), |
|
622 reinterpret_cast<int64_t>(trackunion->GetStream())); |
|
623 |
|
624 trackunion->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal()); |
|
625 |
|
626 // The listener was added at the begining in an inactive state. |
|
627 // Activate our listener. We'll call Start() on the source when get a callback |
|
628 // that the MediaStream has started consuming. The listener is freed |
|
629 // when the page is invalidated (on navigation or close). |
|
630 mListener->Activate(stream.forget(), mAudioSource, mVideoSource); |
|
631 |
|
632 // Note: includes JS callbacks; must be released on MainThread |
|
633 TracksAvailableCallback* tracksAvailableCallback = |
|
634 new TracksAvailableCallback(mManager, mSuccess, mWindowID, trackunion); |
|
635 |
|
636 mListener->AudioConfig(aec_on, (uint32_t) aec, |
|
637 agc_on, (uint32_t) agc, |
|
638 noise_on, (uint32_t) noise, |
|
639 playout_delay); |
|
640 |
|
641 // Dispatch to the media thread to ask it to start the sources, |
|
642 // because that can take a while. |
|
643 // Pass ownership of trackunion to the MediaOperationRunnable |
|
644 // to ensure it's kept alive until the MediaOperationRunnable runs (at least). |
|
645 nsIThread *mediaThread = MediaManager::GetThread(); |
|
646 nsRefPtr<MediaOperationRunnable> runnable( |
|
647 new MediaOperationRunnable(MEDIA_START, mListener, trackunion, |
|
648 tracksAvailableCallback, |
|
649 mAudioSource, mVideoSource, false, mWindowID, |
|
650 mError.forget())); |
|
651 mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); |
|
652 |
|
653 // We won't need mError now. |
|
654 mError = nullptr; |
|
655 return NS_OK; |
|
656 } |
|
657 |
|
658 private: |
|
659 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess; |
|
660 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError; |
|
661 nsRefPtr<MediaEngineSource> mAudioSource; |
|
662 nsRefPtr<MediaEngineSource> mVideoSource; |
|
663 uint64_t mWindowID; |
|
664 nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; |
|
665 nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable |
|
666 }; |
|
667 |
|
668 static bool |
|
669 IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) { |
|
670 return !aUnion.IsBoolean() || aUnion.GetAsBoolean(); |
|
671 } |
|
672 |
|
673 static const MediaTrackConstraints& |
|
674 GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) { |
|
675 static const MediaTrackConstraints empty; |
|
676 return aUnion.IsMediaTrackConstraints() ? |
|
677 aUnion.GetAsMediaTrackConstraints() : empty; |
|
678 } |
|
679 |
|
680 /** |
|
681 * Helper functions that implement the constraints algorithm from |
|
682 * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5 |
|
683 */ |
|
684 |
|
685 // Reminder: add handling for new constraints both here and in GetSources below! |
|
686 |
|
687 static bool SatisfyConstraintSet(const MediaEngineVideoSource *, |
|
688 const MediaTrackConstraintSet &aConstraints, |
|
689 nsIMediaDevice &aCandidate) |
|
690 { |
|
691 if (aConstraints.mFacingMode.WasPassed()) { |
|
692 nsString s; |
|
693 aCandidate.GetFacingMode(s); |
|
694 if (!s.EqualsASCII(dom::VideoFacingModeEnumValues::strings[ |
|
695 uint32_t(aConstraints.mFacingMode.Value())].value)) { |
|
696 return false; |
|
697 } |
|
698 } |
|
699 // TODO: Add more video-specific constraints |
|
700 return true; |
|
701 } |
|
702 |
|
703 static bool SatisfyConstraintSet(const MediaEngineAudioSource *, |
|
704 const MediaTrackConstraintSet &aConstraints, |
|
705 nsIMediaDevice &aCandidate) |
|
706 { |
|
707 // TODO: Add audio-specific constraints |
|
708 return true; |
|
709 } |
|
710 |
|
711 typedef nsTArray<nsCOMPtr<nsIMediaDevice> > SourceSet; |
|
712 |
|
713 // Source getter that constrains list returned |
|
714 |
|
715 template<class SourceType, class ConstraintsType> |
|
716 static SourceSet * |
|
717 GetSources(MediaEngine *engine, |
|
718 ConstraintsType &aConstraints, |
|
719 void (MediaEngine::* aEnumerate)(nsTArray<nsRefPtr<SourceType> >*), |
|
720 char* media_device_name = nullptr) |
|
721 { |
|
722 ScopedDeletePtr<SourceSet> result(new SourceSet); |
|
723 |
|
724 const SourceType * const type = nullptr; |
|
725 nsString deviceName; |
|
726 // First collect sources |
|
727 SourceSet candidateSet; |
|
728 { |
|
729 nsTArray<nsRefPtr<SourceType> > sources; |
|
730 (engine->*aEnumerate)(&sources); |
|
731 /** |
|
732 * We're allowing multiple tabs to access the same camera for parity |
|
733 * with Chrome. See bug 811757 for some of the issues surrounding |
|
734 * this decision. To disallow, we'd filter by IsAvailable() as we used |
|
735 * to. |
|
736 */ |
|
737 for (uint32_t len = sources.Length(), i = 0; i < len; i++) { |
|
738 #ifdef DEBUG |
|
739 sources[i]->GetName(deviceName); |
|
740 if (media_device_name && strlen(media_device_name) > 0) { |
|
741 if (deviceName.EqualsASCII(media_device_name)) { |
|
742 candidateSet.AppendElement(MediaDevice::Create(sources[i])); |
|
743 break; |
|
744 } |
|
745 } else { |
|
746 #endif |
|
747 candidateSet.AppendElement(MediaDevice::Create(sources[i])); |
|
748 #ifdef DEBUG |
|
749 } |
|
750 #endif |
|
751 } |
|
752 } |
|
753 |
|
754 // Apply constraints to the list of sources. |
|
755 |
|
756 auto& c = aConstraints; |
|
757 if (c.mUnsupportedRequirement) { |
|
758 // Check upfront the names of required constraints that are unsupported for |
|
759 // this media-type. The spec requires these to fail, so getting them out of |
|
760 // the way early provides a necessary invariant for the remaining algorithm |
|
761 // which maximizes code-reuse by ignoring constraints of the other type |
|
762 // (specifically, SatisfyConstraintSet is reused for the advanced algorithm |
|
763 // where the spec requires it to ignore constraints of the other type) |
|
764 return result.forget(); |
|
765 } |
|
766 |
|
767 // Now on to the actual algorithm: First apply required constraints. |
|
768 |
|
769 for (uint32_t i = 0; i < candidateSet.Length();) { |
|
770 // Overloading instead of template specialization keeps things local |
|
771 if (!SatisfyConstraintSet(type, c.mRequired, *candidateSet[i])) { |
|
772 candidateSet.RemoveElementAt(i); |
|
773 } else { |
|
774 ++i; |
|
775 } |
|
776 } |
|
777 |
|
778 // TODO(jib): Proper non-ordered handling of nonrequired constraints (907352) |
|
779 // |
|
780 // For now, put nonrequired constraints at tail of Advanced list. |
|
781 // This isn't entirely accurate, as order will matter, but few will notice |
|
782 // the difference until we get camera selection and a few more constraints. |
|
783 if (c.mNonrequired.Length()) { |
|
784 if (!c.mAdvanced.WasPassed()) { |
|
785 c.mAdvanced.Construct(); |
|
786 } |
|
787 c.mAdvanced.Value().MoveElementsFrom(c.mNonrequired); |
|
788 } |
|
789 |
|
790 // Then apply advanced (formerly known as optional) constraints. |
|
791 // |
|
792 // These are only effective when there are multiple sources to pick from. |
|
793 // Spec as-of-this-writing says to run algorithm on "all possible tracks |
|
794 // of media type T that the browser COULD RETURN" (emphasis added). |
|
795 // |
|
796 // We think users ultimately control which devices we could return, so after |
|
797 // determining the webpage's preferred list, we add the remaining choices |
|
798 // to the tail, reasoning that they would all have passed individually, |
|
799 // i.e. if the user had any one of them as their sole device (enabled). |
|
800 // |
|
801 // This avoids users having to unplug/disable devices should a webpage pick |
|
802 // the wrong one (UX-fail). Webpage-preferred devices will be listed first. |
|
803 |
|
804 SourceSet tailSet; |
|
805 |
|
806 if (c.mAdvanced.WasPassed()) { |
|
807 auto &array = c.mAdvanced.Value(); |
|
808 |
|
809 for (int i = 0; i < int(array.Length()); i++) { |
|
810 SourceSet rejects; |
|
811 for (uint32_t j = 0; j < candidateSet.Length();) { |
|
812 if (!SatisfyConstraintSet(type, array[i], *candidateSet[j])) { |
|
813 rejects.AppendElement(candidateSet[j]); |
|
814 candidateSet.RemoveElementAt(j); |
|
815 } else { |
|
816 ++j; |
|
817 } |
|
818 } |
|
819 (candidateSet.Length()? tailSet : candidateSet).MoveElementsFrom(rejects); |
|
820 } |
|
821 } |
|
822 |
|
823 // TODO: Proper non-ordered handling of nonrequired constraints (Bug 907352) |
|
824 |
|
825 result->MoveElementsFrom(candidateSet); |
|
826 result->MoveElementsFrom(tailSet); |
|
827 return result.forget(); |
|
828 } |
|
829 |
|
830 /** |
|
831 * Runs on a seperate thread and is responsible for enumerating devices. |
|
832 * Depending on whether a picture or stream was asked for, either |
|
833 * ProcessGetUserMedia or ProcessGetUserMediaSnapshot is called, and the results |
|
834 * are sent back to the DOM. |
|
835 * |
|
836 * Do not run this on the main thread. The success and error callbacks *MUST* |
|
837 * be dispatched on the main thread! |
|
838 */ |
|
839 class GetUserMediaRunnable : public nsRunnable |
|
840 { |
|
841 public: |
|
842 GetUserMediaRunnable( |
|
843 const MediaStreamConstraints& aConstraints, |
|
844 already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess, |
|
845 already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError, |
|
846 uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener, |
|
847 MediaEnginePrefs &aPrefs) |
|
848 : mConstraints(aConstraints) |
|
849 , mSuccess(aSuccess) |
|
850 , mError(aError) |
|
851 , mWindowID(aWindowID) |
|
852 , mListener(aListener) |
|
853 , mPrefs(aPrefs) |
|
854 , mDeviceChosen(false) |
|
855 , mBackend(nullptr) |
|
856 , mManager(MediaManager::GetInstance()) |
|
857 {} |
|
858 |
|
859 /** |
|
860 * The caller can also choose to provide their own backend instead of |
|
861 * using the one provided by MediaManager::GetBackend. |
|
862 */ |
|
863 GetUserMediaRunnable( |
|
864 const MediaStreamConstraints& aConstraints, |
|
865 already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess, |
|
866 already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError, |
|
867 uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener, |
|
868 MediaEnginePrefs &aPrefs, |
|
869 MediaEngine* aBackend) |
|
870 : mConstraints(aConstraints) |
|
871 , mSuccess(aSuccess) |
|
872 , mError(aError) |
|
873 , mWindowID(aWindowID) |
|
874 , mListener(aListener) |
|
875 , mPrefs(aPrefs) |
|
876 , mDeviceChosen(false) |
|
877 , mBackend(aBackend) |
|
878 , mManager(MediaManager::GetInstance()) |
|
879 {} |
|
880 |
|
881 ~GetUserMediaRunnable() { |
|
882 } |
|
883 |
|
884 void |
|
885 Fail(const nsAString& aMessage) { |
|
886 nsRefPtr<ErrorCallbackRunnable> runnable = |
|
887 new ErrorCallbackRunnable(mSuccess, mError, aMessage, mWindowID); |
|
888 // These should be empty now |
|
889 MOZ_ASSERT(!mSuccess); |
|
890 MOZ_ASSERT(!mError); |
|
891 |
|
892 NS_DispatchToMainThread(runnable); |
|
893 } |
|
894 |
|
895 NS_IMETHOD |
|
896 Run() |
|
897 { |
|
898 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); |
|
899 MOZ_ASSERT(mSuccess); |
|
900 MOZ_ASSERT(mError); |
|
901 |
|
902 MediaEngine* backend = mBackend; |
|
903 // Was a backend provided? |
|
904 if (!backend) { |
|
905 backend = mManager->GetBackend(mWindowID); |
|
906 } |
|
907 |
|
908 // Was a device provided? |
|
909 if (!mDeviceChosen) { |
|
910 nsresult rv = SelectDevice(backend); |
|
911 if (rv != NS_OK) { |
|
912 return rv; |
|
913 } |
|
914 } |
|
915 |
|
916 // It is an error if audio or video are requested along with picture. |
|
917 if (mConstraints.mPicture && |
|
918 (IsOn(mConstraints.mAudio) || IsOn(mConstraints.mVideo))) { |
|
919 Fail(NS_LITERAL_STRING("NOT_SUPPORTED_ERR")); |
|
920 return NS_OK; |
|
921 } |
|
922 |
|
923 if (mConstraints.mPicture) { |
|
924 ProcessGetUserMediaSnapshot(mVideoDevice->GetSource(), 0); |
|
925 return NS_OK; |
|
926 } |
|
927 |
|
928 // There's a bug in the permission code that can leave us with mAudio but no audio device |
|
929 ProcessGetUserMedia(((IsOn(mConstraints.mAudio) && mAudioDevice) ? |
|
930 mAudioDevice->GetSource() : nullptr), |
|
931 ((IsOn(mConstraints.mVideo) && mVideoDevice) ? |
|
932 mVideoDevice->GetSource() : nullptr)); |
|
933 return NS_OK; |
|
934 } |
|
935 |
|
936 nsresult |
|
937 Denied(const nsAString& aErrorMsg) |
|
938 { |
|
939 MOZ_ASSERT(mSuccess); |
|
940 MOZ_ASSERT(mError); |
|
941 |
|
942 // We add a disabled listener to the StreamListeners array until accepted |
|
943 // If this was the only active MediaStream, remove the window from the list. |
|
944 if (NS_IsMainThread()) { |
|
945 // This is safe since we're on main-thread, and the window can only |
|
946 // be invalidated from the main-thread (see OnNavigation) |
|
947 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget(); |
|
948 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget(); |
|
949 error->OnError(aErrorMsg); |
|
950 |
|
951 // Should happen *after* error runs for consistency, but may not matter |
|
952 nsRefPtr<MediaManager> manager(MediaManager::GetInstance()); |
|
953 manager->RemoveFromWindowList(mWindowID, mListener); |
|
954 } else { |
|
955 // This will re-check the window being alive on main-thread |
|
956 // Note: we must remove the listener on MainThread as well |
|
957 Fail(aErrorMsg); |
|
958 |
|
959 // MUST happen after ErrorCallbackRunnable Run()s, as it checks the active window list |
|
960 NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, mListener)); |
|
961 } |
|
962 |
|
963 MOZ_ASSERT(!mSuccess); |
|
964 MOZ_ASSERT(!mError); |
|
965 |
|
966 return NS_OK; |
|
967 } |
|
968 |
|
969 nsresult |
|
970 SetContraints(const MediaStreamConstraints& aConstraints) |
|
971 { |
|
972 mConstraints = aConstraints; |
|
973 return NS_OK; |
|
974 } |
|
975 |
|
976 nsresult |
|
977 SetAudioDevice(AudioDevice* aAudioDevice) |
|
978 { |
|
979 mAudioDevice = aAudioDevice; |
|
980 mDeviceChosen = true; |
|
981 return NS_OK; |
|
982 } |
|
983 |
|
984 nsresult |
|
985 SetVideoDevice(VideoDevice* aVideoDevice) |
|
986 { |
|
987 mVideoDevice = aVideoDevice; |
|
988 mDeviceChosen = true; |
|
989 return NS_OK; |
|
990 } |
|
991 |
|
992 nsresult |
|
993 SelectDevice(MediaEngine* backend) |
|
994 { |
|
995 MOZ_ASSERT(mSuccess); |
|
996 MOZ_ASSERT(mError); |
|
997 if (mConstraints.mPicture || IsOn(mConstraints.mVideo)) { |
|
998 VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo)); |
|
999 ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints, |
|
1000 &MediaEngine::EnumerateVideoDevices)); |
|
1001 |
|
1002 if (!sources->Length()) { |
|
1003 Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND")); |
|
1004 return NS_ERROR_FAILURE; |
|
1005 } |
|
1006 // Pick the first available device. |
|
1007 mVideoDevice = do_QueryObject((*sources)[0]); |
|
1008 LOG(("Selected video device")); |
|
1009 } |
|
1010 |
|
1011 if (IsOn(mConstraints.mAudio)) { |
|
1012 AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio)); |
|
1013 ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints, |
|
1014 &MediaEngine::EnumerateAudioDevices)); |
|
1015 |
|
1016 if (!sources->Length()) { |
|
1017 Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND")); |
|
1018 return NS_ERROR_FAILURE; |
|
1019 } |
|
1020 // Pick the first available device. |
|
1021 mAudioDevice = do_QueryObject((*sources)[0]); |
|
1022 LOG(("Selected audio device")); |
|
1023 } |
|
1024 |
|
1025 return NS_OK; |
|
1026 } |
|
1027 |
|
1028 /** |
|
1029 * Allocates a video or audio device and returns a MediaStream via |
|
1030 * a GetUserMediaStreamRunnable. Runs off the main thread. |
|
1031 */ |
|
1032 void |
|
1033 ProcessGetUserMedia(MediaEngineAudioSource* aAudioSource, |
|
1034 MediaEngineVideoSource* aVideoSource) |
|
1035 { |
|
1036 MOZ_ASSERT(mSuccess); |
|
1037 MOZ_ASSERT(mError); |
|
1038 nsresult rv; |
|
1039 if (aAudioSource) { |
|
1040 rv = aAudioSource->Allocate(GetInvariant(mConstraints.mAudio), mPrefs); |
|
1041 if (NS_FAILED(rv)) { |
|
1042 LOG(("Failed to allocate audiosource %d",rv)); |
|
1043 Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE")); |
|
1044 return; |
|
1045 } |
|
1046 } |
|
1047 if (aVideoSource) { |
|
1048 rv = aVideoSource->Allocate(GetInvariant(mConstraints.mVideo), mPrefs); |
|
1049 if (NS_FAILED(rv)) { |
|
1050 LOG(("Failed to allocate videosource %d\n",rv)); |
|
1051 if (aAudioSource) { |
|
1052 aAudioSource->Deallocate(); |
|
1053 } |
|
1054 Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE")); |
|
1055 return; |
|
1056 } |
|
1057 } |
|
1058 |
|
1059 NS_DispatchToMainThread(new GetUserMediaStreamRunnable( |
|
1060 mSuccess, mError, mWindowID, mListener, aAudioSource, aVideoSource |
|
1061 )); |
|
1062 |
|
1063 MOZ_ASSERT(!mSuccess); |
|
1064 MOZ_ASSERT(!mError); |
|
1065 |
|
1066 return; |
|
1067 } |
|
1068 |
|
1069 /** |
|
1070 * Allocates a video device, takes a snapshot and returns a DOMFile via |
|
1071 * a SuccessRunnable or an error via the ErrorRunnable. Off the main thread. |
|
1072 */ |
|
1073 void |
|
1074 ProcessGetUserMediaSnapshot(MediaEngineVideoSource* aSource, int aDuration) |
|
1075 { |
|
1076 MOZ_ASSERT(mSuccess); |
|
1077 MOZ_ASSERT(mError); |
|
1078 nsresult rv = aSource->Allocate(GetInvariant(mConstraints.mVideo), mPrefs); |
|
1079 if (NS_FAILED(rv)) { |
|
1080 Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE")); |
|
1081 return; |
|
1082 } |
|
1083 |
|
1084 /** |
|
1085 * Display picture capture UI here before calling Snapshot() - Bug 748835. |
|
1086 */ |
|
1087 nsCOMPtr<nsIDOMFile> file; |
|
1088 aSource->Snapshot(aDuration, getter_AddRefs(file)); |
|
1089 aSource->Deallocate(); |
|
1090 |
|
1091 NS_DispatchToMainThread(new SuccessCallbackRunnable( |
|
1092 mSuccess, mError, file, mWindowID |
|
1093 )); |
|
1094 |
|
1095 MOZ_ASSERT(!mSuccess); |
|
1096 MOZ_ASSERT(!mError); |
|
1097 |
|
1098 return; |
|
1099 } |
|
1100 |
|
1101 private: |
|
1102 MediaStreamConstraints mConstraints; |
|
1103 |
|
1104 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess; |
|
1105 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError; |
|
1106 uint64_t mWindowID; |
|
1107 nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; |
|
1108 nsRefPtr<AudioDevice> mAudioDevice; |
|
1109 nsRefPtr<VideoDevice> mVideoDevice; |
|
1110 MediaEnginePrefs mPrefs; |
|
1111 |
|
1112 bool mDeviceChosen; |
|
1113 |
|
1114 RefPtr<MediaEngine> mBackend; |
|
1115 nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable |
|
1116 }; |
|
1117 |
|
1118 /** |
|
1119 * Similar to GetUserMediaRunnable, but used for the chrome-only |
|
1120 * GetUserMediaDevices function. Enumerates a list of audio & video devices, |
|
1121 * wraps them up in nsIMediaDevice objects and returns it to the success |
|
1122 * callback. |
|
1123 */ |
|
1124 class GetUserMediaDevicesRunnable : public nsRunnable |
|
1125 { |
|
1126 public: |
|
1127 GetUserMediaDevicesRunnable( |
|
1128 const MediaStreamConstraints& aConstraints, |
|
1129 already_AddRefed<nsIGetUserMediaDevicesSuccessCallback> aSuccess, |
|
1130 already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError, |
|
1131 uint64_t aWindowId, char* aAudioLoopbackDev, char* aVideoLoopbackDev) |
|
1132 : mConstraints(aConstraints) |
|
1133 , mSuccess(aSuccess) |
|
1134 , mError(aError) |
|
1135 , mManager(MediaManager::GetInstance()) |
|
1136 , mWindowId(aWindowId) |
|
1137 , mLoopbackAudioDevice(aAudioLoopbackDev) |
|
1138 , mLoopbackVideoDevice(aVideoLoopbackDev) {} |
|
1139 |
|
1140 NS_IMETHOD |
|
1141 Run() |
|
1142 { |
|
1143 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); |
|
1144 |
|
1145 nsRefPtr<MediaEngine> backend; |
|
1146 if (mConstraints.mFake) |
|
1147 backend = new MediaEngineDefault(); |
|
1148 else |
|
1149 backend = mManager->GetBackend(mWindowId); |
|
1150 |
|
1151 ScopedDeletePtr<SourceSet> final(new SourceSet); |
|
1152 if (IsOn(mConstraints.mVideo)) { |
|
1153 VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo)); |
|
1154 ScopedDeletePtr<SourceSet> s(GetSources(backend, constraints, |
|
1155 &MediaEngine::EnumerateVideoDevices, |
|
1156 mLoopbackVideoDevice)); |
|
1157 final->MoveElementsFrom(*s); |
|
1158 } |
|
1159 if (IsOn(mConstraints.mAudio)) { |
|
1160 AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio)); |
|
1161 ScopedDeletePtr<SourceSet> s (GetSources(backend, constraints, |
|
1162 &MediaEngine::EnumerateAudioDevices, |
|
1163 mLoopbackAudioDevice)); |
|
1164 final->MoveElementsFrom(*s); |
|
1165 } |
|
1166 NS_DispatchToMainThread(new DeviceSuccessCallbackRunnable(mWindowId, |
|
1167 mSuccess, mError, |
|
1168 final.forget())); |
|
1169 // DeviceSuccessCallbackRunnable should have taken these. |
|
1170 MOZ_ASSERT(!mSuccess && !mError); |
|
1171 return NS_OK; |
|
1172 } |
|
1173 |
|
1174 private: |
|
1175 MediaStreamConstraints mConstraints; |
|
1176 nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mSuccess; |
|
1177 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError; |
|
1178 nsRefPtr<MediaManager> mManager; |
|
1179 uint64_t mWindowId; |
|
1180 const nsString mCallId; |
|
1181 // Audio & Video loopback devices to be used based on |
|
1182 // the preference settings. This is currently used for |
|
1183 // automated media tests only. |
|
1184 char* mLoopbackAudioDevice; |
|
1185 char* mLoopbackVideoDevice; |
|
1186 }; |
|
1187 |
|
1188 MediaManager::MediaManager() |
|
1189 : mMediaThread(nullptr) |
|
1190 , mMutex("mozilla::MediaManager") |
|
1191 , mBackend(nullptr) { |
|
1192 mPrefs.mWidth = 0; // adaptive default |
|
1193 mPrefs.mHeight = 0; // adaptive default |
|
1194 mPrefs.mFPS = MediaEngine::DEFAULT_VIDEO_FPS; |
|
1195 mPrefs.mMinFPS = MediaEngine::DEFAULT_VIDEO_MIN_FPS; |
|
1196 |
|
1197 nsresult rv; |
|
1198 nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); |
|
1199 if (NS_SUCCEEDED(rv)) { |
|
1200 nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs); |
|
1201 if (branch) { |
|
1202 GetPrefs(branch, nullptr); |
|
1203 } |
|
1204 } |
|
1205 LOG(("%s: default prefs: %dx%d @%dfps (min %d)", __FUNCTION__, |
|
1206 mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS)); |
|
1207 } |
|
1208 |
|
1209 NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIObserver) |
|
1210 |
|
1211 /* static */ StaticRefPtr<MediaManager> MediaManager::sSingleton; |
|
1212 |
|
1213 // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager |
|
1214 // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread |
|
1215 // from MediaManager thread. |
|
1216 /* static */ MediaManager* |
|
1217 MediaManager::Get() { |
|
1218 if (!sSingleton) { |
|
1219 sSingleton = new MediaManager(); |
|
1220 |
|
1221 NS_NewNamedThread("MediaManager", getter_AddRefs(sSingleton->mMediaThread)); |
|
1222 LOG(("New Media thread for gum")); |
|
1223 |
|
1224 NS_ASSERTION(NS_IsMainThread(), "Only create MediaManager on main thread"); |
|
1225 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); |
|
1226 if (obs) { |
|
1227 obs->AddObserver(sSingleton, "xpcom-shutdown", false); |
|
1228 obs->AddObserver(sSingleton, "getUserMedia:response:allow", false); |
|
1229 obs->AddObserver(sSingleton, "getUserMedia:response:deny", false); |
|
1230 obs->AddObserver(sSingleton, "getUserMedia:revoke", false); |
|
1231 obs->AddObserver(sSingleton, "phone-state-changed", false); |
|
1232 } |
|
1233 // else MediaManager won't work properly and will leak (see bug 837874) |
|
1234 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); |
|
1235 if (prefs) { |
|
1236 prefs->AddObserver("media.navigator.video.default_width", sSingleton, false); |
|
1237 prefs->AddObserver("media.navigator.video.default_height", sSingleton, false); |
|
1238 prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false); |
|
1239 prefs->AddObserver("media.navigator.video.default_minfps", sSingleton, false); |
|
1240 } |
|
1241 } |
|
1242 return sSingleton; |
|
1243 } |
|
1244 |
|
1245 /* static */ already_AddRefed<MediaManager> |
|
1246 MediaManager::GetInstance() |
|
1247 { |
|
1248 // so we can have non-refcounted getters |
|
1249 nsRefPtr<MediaManager> service = MediaManager::Get(); |
|
1250 return service.forget(); |
|
1251 } |
|
1252 |
|
1253 /* static */ nsresult |
|
1254 MediaManager::NotifyRecordingStatusChange(nsPIDOMWindow* aWindow, |
|
1255 const nsString& aMsg, |
|
1256 const bool& aIsAudio, |
|
1257 const bool& aIsVideo) |
|
1258 { |
|
1259 NS_ENSURE_ARG(aWindow); |
|
1260 |
|
1261 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); |
|
1262 if (!obs) { |
|
1263 NS_WARNING("Could not get the Observer service for GetUserMedia recording notification."); |
|
1264 return NS_ERROR_FAILURE; |
|
1265 } |
|
1266 |
|
1267 nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag(); |
|
1268 props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio); |
|
1269 props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo); |
|
1270 |
|
1271 bool isApp = false; |
|
1272 nsString requestURL; |
|
1273 |
|
1274 if (nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell()) { |
|
1275 nsresult rv = docShell->GetIsApp(&isApp); |
|
1276 NS_ENSURE_SUCCESS(rv, rv); |
|
1277 |
|
1278 if (isApp) { |
|
1279 rv = docShell->GetAppManifestURL(requestURL); |
|
1280 NS_ENSURE_SUCCESS(rv, rv); |
|
1281 } |
|
1282 } |
|
1283 |
|
1284 if (!isApp) { |
|
1285 nsCString pageURL; |
|
1286 nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI(); |
|
1287 NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE); |
|
1288 |
|
1289 nsresult rv = docURI->GetSpec(pageURL); |
|
1290 NS_ENSURE_SUCCESS(rv, rv); |
|
1291 |
|
1292 requestURL = NS_ConvertUTF8toUTF16(pageURL); |
|
1293 } |
|
1294 |
|
1295 props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp); |
|
1296 props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL); |
|
1297 |
|
1298 obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props), |
|
1299 "recording-device-events", |
|
1300 aMsg.get()); |
|
1301 |
|
1302 // Forward recording events to parent process. |
|
1303 // The events are gathered in chrome process and used for recording indicator |
|
1304 if (XRE_GetProcessType() != GeckoProcessType_Default) { |
|
1305 unused << |
|
1306 dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(aMsg, |
|
1307 requestURL, |
|
1308 aIsAudio, |
|
1309 aIsVideo); |
|
1310 } |
|
1311 |
|
1312 return NS_OK; |
|
1313 } |
|
1314 |
|
1315 /** |
|
1316 * The entry point for this file. A call from Navigator::mozGetUserMedia |
|
1317 * will end up here. MediaManager is a singleton that is responsible |
|
1318 * for handling all incoming getUserMedia calls from every window. |
|
1319 */ |
|
1320 nsresult |
|
1321 MediaManager::GetUserMedia(bool aPrivileged, |
|
1322 nsPIDOMWindow* aWindow, const MediaStreamConstraints& aConstraints, |
|
1323 nsIDOMGetUserMediaSuccessCallback* aOnSuccess, |
|
1324 nsIDOMGetUserMediaErrorCallback* aOnError) |
|
1325 { |
|
1326 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
|
1327 |
|
1328 NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER); |
|
1329 NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER); |
|
1330 NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER); |
|
1331 |
|
1332 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess); |
|
1333 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError); |
|
1334 |
|
1335 MediaStreamConstraints c(aConstraints); // copy |
|
1336 |
|
1337 /** |
|
1338 * If we were asked to get a picture, before getting a snapshot, we check if |
|
1339 * the calling page is allowed to open a popup. We do this because |
|
1340 * {picture:true} will open a new "window" to let the user preview or select |
|
1341 * an image, on Android. The desktop UI for {picture:true} is TBD, at which |
|
1342 * may point we can decide whether to extend this test there as well. |
|
1343 */ |
|
1344 #if !defined(MOZ_WEBRTC) |
|
1345 if (c.mPicture && !aPrivileged) { |
|
1346 if (aWindow->GetPopupControlState() > openControlled) { |
|
1347 nsCOMPtr<nsIPopupWindowManager> pm = |
|
1348 do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID); |
|
1349 if (!pm) { |
|
1350 return NS_OK; |
|
1351 } |
|
1352 uint32_t permission; |
|
1353 nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc(); |
|
1354 pm->TestPermission(doc->NodePrincipal(), &permission); |
|
1355 if (permission == nsIPopupWindowManager::DENY_POPUP) { |
|
1356 nsGlobalWindow::FirePopupBlockedEvent( |
|
1357 doc, aWindow, nullptr, EmptyString(), EmptyString() |
|
1358 ); |
|
1359 return NS_OK; |
|
1360 } |
|
1361 } |
|
1362 } |
|
1363 #endif |
|
1364 |
|
1365 static bool created = false; |
|
1366 if (!created) { |
|
1367 // Force MediaManager to startup before we try to access it from other threads |
|
1368 // Hack: should init singleton earlier unless it's expensive (mem or CPU) |
|
1369 (void) MediaManager::Get(); |
|
1370 #ifdef MOZ_B2G |
|
1371 // Initialize MediaPermissionManager before send out any permission request. |
|
1372 (void) MediaPermissionManager::GetInstance(); |
|
1373 #endif //MOZ_B2G |
|
1374 } |
|
1375 |
|
1376 // Store the WindowID in a hash table and mark as active. The entry is removed |
|
1377 // when this window is closed or navigated away from. |
|
1378 uint64_t windowID = aWindow->WindowID(); |
|
1379 // This is safe since we're on main-thread, and the windowlist can only |
|
1380 // be invalidated from the main-thread (see OnNavigation) |
|
1381 StreamListeners* listeners = GetActiveWindows()->Get(windowID); |
|
1382 if (!listeners) { |
|
1383 listeners = new StreamListeners; |
|
1384 GetActiveWindows()->Put(windowID, listeners); |
|
1385 } |
|
1386 |
|
1387 // Ensure there's a thread for gum to proxy to off main thread |
|
1388 nsIThread *mediaThread = MediaManager::GetThread(); |
|
1389 |
|
1390 // Create a disabled listener to act as a placeholder |
|
1391 GetUserMediaCallbackMediaStreamListener* listener = |
|
1392 new GetUserMediaCallbackMediaStreamListener(mediaThread, windowID); |
|
1393 |
|
1394 // No need for locking because we always do this in the main thread. |
|
1395 listeners->AppendElement(listener); |
|
1396 |
|
1397 // Developer preference for turning off permission check. |
|
1398 if (Preferences::GetBool("media.navigator.permission.disabled", false)) { |
|
1399 aPrivileged = true; |
|
1400 } |
|
1401 if (!Preferences::GetBool("media.navigator.video.enabled", true)) { |
|
1402 c.mVideo.SetAsBoolean() = false; |
|
1403 } |
|
1404 |
|
1405 #if defined(ANDROID) || defined(MOZ_WIDGET_GONK) |
|
1406 // Be backwards compatible only on mobile and only for facingMode. |
|
1407 if (c.mVideo.IsMediaTrackConstraints()) { |
|
1408 auto& tc = c.mVideo.GetAsMediaTrackConstraints(); |
|
1409 if (!tc.mRequire.WasPassed() && |
|
1410 tc.mMandatory.mFacingMode.WasPassed() && !tc.mFacingMode.WasPassed()) { |
|
1411 tc.mFacingMode.Construct(tc.mMandatory.mFacingMode.Value()); |
|
1412 tc.mRequire.Construct().AppendElement(NS_LITERAL_STRING("facingMode")); |
|
1413 } |
|
1414 if (tc.mOptional.WasPassed() && !tc.mAdvanced.WasPassed()) { |
|
1415 tc.mAdvanced.Construct(); |
|
1416 for (uint32_t i = 0; i < tc.mOptional.Value().Length(); i++) { |
|
1417 if (tc.mOptional.Value()[i].mFacingMode.WasPassed()) { |
|
1418 MediaTrackConstraintSet n; |
|
1419 n.mFacingMode.Construct(tc.mOptional.Value()[i].mFacingMode.Value()); |
|
1420 tc.mAdvanced.Value().AppendElement(n); |
|
1421 } |
|
1422 } |
|
1423 } |
|
1424 } |
|
1425 #endif |
|
1426 |
|
1427 // Pass callbacks and MediaStreamListener along to GetUserMediaRunnable. |
|
1428 nsRefPtr<GetUserMediaRunnable> runnable; |
|
1429 if (c.mFake) { |
|
1430 // Fake stream from default backend. |
|
1431 runnable = new GetUserMediaRunnable(c, onSuccess.forget(), |
|
1432 onError.forget(), windowID, listener, mPrefs, new MediaEngineDefault()); |
|
1433 } else { |
|
1434 // Stream from default device from WebRTC backend. |
|
1435 runnable = new GetUserMediaRunnable(c, onSuccess.forget(), |
|
1436 onError.forget(), windowID, listener, mPrefs); |
|
1437 } |
|
1438 |
|
1439 #ifdef MOZ_B2G_CAMERA |
|
1440 if (mCameraManager == nullptr) { |
|
1441 mCameraManager = nsDOMCameraManager::CreateInstance(aWindow); |
|
1442 } |
|
1443 #endif |
|
1444 |
|
1445 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK) |
|
1446 if (c.mPicture) { |
|
1447 // ShowFilePickerForMimeType() must run on the Main Thread! (on Android) |
|
1448 NS_DispatchToMainThread(runnable); |
|
1449 return NS_OK; |
|
1450 } |
|
1451 #endif |
|
1452 // XXX No full support for picture in Desktop yet (needs proper UI) |
|
1453 if (aPrivileged || |
|
1454 (c.mFake && !Preferences::GetBool("media.navigator.permission.fake"))) { |
|
1455 mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); |
|
1456 } else { |
|
1457 bool isHTTPS = false; |
|
1458 nsIURI* docURI = aWindow->GetDocumentURI(); |
|
1459 if (docURI) { |
|
1460 docURI->SchemeIs("https", &isHTTPS); |
|
1461 } |
|
1462 |
|
1463 // Check if this site has persistent permissions. |
|
1464 nsresult rv; |
|
1465 nsCOMPtr<nsIPermissionManager> permManager = |
|
1466 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); |
|
1467 NS_ENSURE_SUCCESS(rv, rv); |
|
1468 |
|
1469 uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION; |
|
1470 if (IsOn(c.mAudio)) { |
|
1471 rv = permManager->TestExactPermissionFromPrincipal( |
|
1472 aWindow->GetExtantDoc()->NodePrincipal(), "microphone", &audioPerm); |
|
1473 NS_ENSURE_SUCCESS(rv, rv); |
|
1474 } |
|
1475 |
|
1476 uint32_t videoPerm = nsIPermissionManager::UNKNOWN_ACTION; |
|
1477 if (IsOn(c.mVideo)) { |
|
1478 rv = permManager->TestExactPermissionFromPrincipal( |
|
1479 aWindow->GetExtantDoc()->NodePrincipal(), "camera", &videoPerm); |
|
1480 NS_ENSURE_SUCCESS(rv, rv); |
|
1481 } |
|
1482 |
|
1483 if ((!IsOn(c.mAudio) || audioPerm == nsIPermissionManager::DENY_ACTION) && |
|
1484 (!IsOn(c.mVideo) || videoPerm == nsIPermissionManager::DENY_ACTION)) { |
|
1485 return runnable->Denied(NS_LITERAL_STRING("PERMISSION_DENIED")); |
|
1486 } |
|
1487 |
|
1488 // Ask for user permission, and dispatch runnable (or not) when a response |
|
1489 // is received via an observer notification. Each call is paired with its |
|
1490 // runnable by a GUID. |
|
1491 nsCOMPtr<nsIUUIDGenerator> uuidgen = |
|
1492 do_GetService("@mozilla.org/uuid-generator;1", &rv); |
|
1493 NS_ENSURE_SUCCESS(rv, rv); |
|
1494 |
|
1495 // Generate a call ID. |
|
1496 nsID id; |
|
1497 rv = uuidgen->GenerateUUIDInPlace(&id); |
|
1498 NS_ENSURE_SUCCESS(rv, rv); |
|
1499 |
|
1500 char buffer[NSID_LENGTH]; |
|
1501 id.ToProvidedString(buffer); |
|
1502 NS_ConvertUTF8toUTF16 callID(buffer); |
|
1503 |
|
1504 // Store the current unarmed runnable w/callbacks. |
|
1505 mActiveCallbacks.Put(callID, runnable); |
|
1506 |
|
1507 // Add a WindowID cross-reference so OnNavigation can tear things down |
|
1508 nsTArray<nsString>* array; |
|
1509 if (!mCallIds.Get(windowID, &array)) { |
|
1510 array = new nsTArray<nsString>(); |
|
1511 array->AppendElement(callID); |
|
1512 mCallIds.Put(windowID, array); |
|
1513 } else { |
|
1514 array->AppendElement(callID); |
|
1515 } |
|
1516 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); |
|
1517 nsRefPtr<GetUserMediaRequest> req = new GetUserMediaRequest(aWindow, |
|
1518 callID, c, isHTTPS); |
|
1519 obs->NotifyObservers(req, "getUserMedia:request", nullptr); |
|
1520 } |
|
1521 |
|
1522 return NS_OK; |
|
1523 } |
|
1524 |
|
1525 nsresult |
|
1526 MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow, |
|
1527 const MediaStreamConstraints& aConstraints, |
|
1528 nsIGetUserMediaDevicesSuccessCallback* aOnSuccess, |
|
1529 nsIDOMGetUserMediaErrorCallback* aOnError, |
|
1530 uint64_t aInnerWindowID) |
|
1531 { |
|
1532 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
|
1533 |
|
1534 NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER); |
|
1535 NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER); |
|
1536 |
|
1537 nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess); |
|
1538 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError); |
|
1539 char* loopbackAudioDevice = nullptr; |
|
1540 char* loopbackVideoDevice = nullptr; |
|
1541 |
|
1542 #ifdef DEBUG |
|
1543 nsresult rv; |
|
1544 |
|
1545 // Check if the preference for using loopback devices is enabled. |
|
1546 nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); |
|
1547 if (NS_SUCCEEDED(rv)) { |
|
1548 nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs); |
|
1549 if (branch) { |
|
1550 branch->GetCharPref("media.audio_loopback_dev", &loopbackAudioDevice); |
|
1551 branch->GetCharPref("media.video_loopback_dev", &loopbackVideoDevice); |
|
1552 } |
|
1553 } |
|
1554 #endif |
|
1555 |
|
1556 nsCOMPtr<nsIRunnable> gUMDRunnable = new GetUserMediaDevicesRunnable( |
|
1557 aConstraints, onSuccess.forget(), onError.forget(), |
|
1558 (aInnerWindowID ? aInnerWindowID : aWindow->WindowID()), |
|
1559 loopbackAudioDevice, loopbackVideoDevice); |
|
1560 |
|
1561 mMediaThread->Dispatch(gUMDRunnable, NS_DISPATCH_NORMAL); |
|
1562 return NS_OK; |
|
1563 } |
|
1564 |
|
1565 MediaEngine* |
|
1566 MediaManager::GetBackend(uint64_t aWindowId) |
|
1567 { |
|
1568 // Plugin backends as appropriate. The default engine also currently |
|
1569 // includes picture support for Android. |
|
1570 // This IS called off main-thread. |
|
1571 MutexAutoLock lock(mMutex); |
|
1572 if (!mBackend) { |
|
1573 #if defined(MOZ_WEBRTC) |
|
1574 mBackend = new MediaEngineWebRTC(mPrefs); |
|
1575 #else |
|
1576 mBackend = new MediaEngineDefault(); |
|
1577 #endif |
|
1578 } |
|
1579 return mBackend; |
|
1580 } |
|
1581 |
|
1582 static void |
|
1583 StopSharingCallback(MediaManager *aThis, |
|
1584 uint64_t aWindowID, |
|
1585 StreamListeners *aListeners, |
|
1586 void *aData) |
|
1587 { |
|
1588 if (aListeners) { |
|
1589 auto length = aListeners->Length(); |
|
1590 for (size_t i = 0; i < length; ++i) { |
|
1591 GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i); |
|
1592 |
|
1593 if (listener->Stream()) { // aka HasBeenActivate()ed |
|
1594 listener->Invalidate(); |
|
1595 } |
|
1596 listener->Remove(); |
|
1597 } |
|
1598 aListeners->Clear(); |
|
1599 aThis->RemoveWindowID(aWindowID); |
|
1600 } |
|
1601 } |
|
1602 |
|
1603 |
|
1604 void |
|
1605 MediaManager::OnNavigation(uint64_t aWindowID) |
|
1606 { |
|
1607 NS_ASSERTION(NS_IsMainThread(), "OnNavigation called off main thread"); |
|
1608 LOG(("OnNavigation for %llu", aWindowID)); |
|
1609 |
|
1610 // Invalidate this window. The runnables check this value before making |
|
1611 // a call to content. |
|
1612 |
|
1613 nsTArray<nsString>* callIds; |
|
1614 if (mCallIds.Get(aWindowID, &callIds)) { |
|
1615 for (int i = 0, len = callIds->Length(); i < len; ++i) { |
|
1616 mActiveCallbacks.Remove((*callIds)[i]); |
|
1617 } |
|
1618 mCallIds.Remove(aWindowID); |
|
1619 } |
|
1620 |
|
1621 // This is safe since we're on main-thread, and the windowlist can only |
|
1622 // be added to from the main-thread |
|
1623 nsPIDOMWindow *window = static_cast<nsPIDOMWindow*> |
|
1624 (nsGlobalWindow::GetInnerWindowWithId(aWindowID)); |
|
1625 if (window) { |
|
1626 IterateWindowListeners(window, StopSharingCallback, nullptr); |
|
1627 } else { |
|
1628 RemoveWindowID(aWindowID); |
|
1629 } |
|
1630 } |
|
1631 |
|
1632 void |
|
1633 MediaManager::RemoveFromWindowList(uint64_t aWindowID, |
|
1634 GetUserMediaCallbackMediaStreamListener *aListener) |
|
1635 { |
|
1636 NS_ASSERTION(NS_IsMainThread(), "RemoveFromWindowList called off main thread"); |
|
1637 |
|
1638 // This is defined as safe on an inactive GUMCMSListener |
|
1639 aListener->Remove(); // really queues the remove |
|
1640 |
|
1641 StreamListeners* listeners = GetWindowListeners(aWindowID); |
|
1642 if (!listeners) { |
|
1643 return; |
|
1644 } |
|
1645 listeners->RemoveElement(aListener); |
|
1646 if (listeners->Length() == 0) { |
|
1647 RemoveWindowID(aWindowID); |
|
1648 // listeners has been deleted here |
|
1649 |
|
1650 // get outer windowID |
|
1651 nsPIDOMWindow *window = static_cast<nsPIDOMWindow*> |
|
1652 (nsGlobalWindow::GetInnerWindowWithId(aWindowID)); |
|
1653 if (window) { |
|
1654 nsPIDOMWindow *outer = window->GetOuterWindow(); |
|
1655 if (outer) { |
|
1656 uint64_t outerID = outer->WindowID(); |
|
1657 |
|
1658 // Notify the UI that this window no longer has gUM active |
|
1659 char windowBuffer[32]; |
|
1660 PR_snprintf(windowBuffer, sizeof(windowBuffer), "%llu", outerID); |
|
1661 nsAutoString data; |
|
1662 data.Append(NS_ConvertUTF8toUTF16(windowBuffer)); |
|
1663 |
|
1664 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); |
|
1665 obs->NotifyObservers(nullptr, "recording-window-ended", data.get()); |
|
1666 LOG(("Sent recording-window-ended for window %llu (outer %llu)", |
|
1667 aWindowID, outerID)); |
|
1668 } else { |
|
1669 LOG(("No outer window for inner %llu", aWindowID)); |
|
1670 } |
|
1671 } else { |
|
1672 LOG(("No inner window for %llu", aWindowID)); |
|
1673 } |
|
1674 } |
|
1675 } |
|
1676 |
|
1677 void |
|
1678 MediaManager::GetPref(nsIPrefBranch *aBranch, const char *aPref, |
|
1679 const char *aData, int32_t *aVal) |
|
1680 { |
|
1681 int32_t temp; |
|
1682 if (aData == nullptr || strcmp(aPref,aData) == 0) { |
|
1683 if (NS_SUCCEEDED(aBranch->GetIntPref(aPref, &temp))) { |
|
1684 *aVal = temp; |
|
1685 } |
|
1686 } |
|
1687 } |
|
1688 |
|
1689 void |
|
1690 MediaManager::GetPrefBool(nsIPrefBranch *aBranch, const char *aPref, |
|
1691 const char *aData, bool *aVal) |
|
1692 { |
|
1693 bool temp; |
|
1694 if (aData == nullptr || strcmp(aPref,aData) == 0) { |
|
1695 if (NS_SUCCEEDED(aBranch->GetBoolPref(aPref, &temp))) { |
|
1696 *aVal = temp; |
|
1697 } |
|
1698 } |
|
1699 } |
|
1700 |
|
1701 void |
|
1702 MediaManager::GetPrefs(nsIPrefBranch *aBranch, const char *aData) |
|
1703 { |
|
1704 GetPref(aBranch, "media.navigator.video.default_width", aData, &mPrefs.mWidth); |
|
1705 GetPref(aBranch, "media.navigator.video.default_height", aData, &mPrefs.mHeight); |
|
1706 GetPref(aBranch, "media.navigator.video.default_fps", aData, &mPrefs.mFPS); |
|
1707 GetPref(aBranch, "media.navigator.video.default_minfps", aData, &mPrefs.mMinFPS); |
|
1708 } |
|
1709 |
|
1710 nsresult |
|
1711 MediaManager::Observe(nsISupports* aSubject, const char* aTopic, |
|
1712 const char16_t* aData) |
|
1713 { |
|
1714 NS_ASSERTION(NS_IsMainThread(), "Observer invoked off the main thread"); |
|
1715 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); |
|
1716 |
|
1717 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { |
|
1718 nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) ); |
|
1719 if (branch) { |
|
1720 GetPrefs(branch,NS_ConvertUTF16toUTF8(aData).get()); |
|
1721 LOG(("%s: %dx%d @%dfps (min %d)", __FUNCTION__, |
|
1722 mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS)); |
|
1723 } |
|
1724 } else if (!strcmp(aTopic, "xpcom-shutdown")) { |
|
1725 obs->RemoveObserver(this, "xpcom-shutdown"); |
|
1726 obs->RemoveObserver(this, "getUserMedia:response:allow"); |
|
1727 obs->RemoveObserver(this, "getUserMedia:response:deny"); |
|
1728 obs->RemoveObserver(this, "getUserMedia:revoke"); |
|
1729 |
|
1730 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); |
|
1731 if (prefs) { |
|
1732 prefs->RemoveObserver("media.navigator.video.default_width", this); |
|
1733 prefs->RemoveObserver("media.navigator.video.default_height", this); |
|
1734 prefs->RemoveObserver("media.navigator.video.default_fps", this); |
|
1735 prefs->RemoveObserver("media.navigator.video.default_minfps", this); |
|
1736 } |
|
1737 |
|
1738 // Close off any remaining active windows. |
|
1739 { |
|
1740 MutexAutoLock lock(mMutex); |
|
1741 GetActiveWindows()->Clear(); |
|
1742 mActiveCallbacks.Clear(); |
|
1743 mCallIds.Clear(); |
|
1744 LOG(("Releasing MediaManager singleton and thread")); |
|
1745 // Note: won't be released immediately as the Observer has a ref to us |
|
1746 sSingleton = nullptr; |
|
1747 mBackend = nullptr; |
|
1748 } |
|
1749 |
|
1750 return NS_OK; |
|
1751 |
|
1752 } else if (!strcmp(aTopic, "getUserMedia:response:allow")) { |
|
1753 nsString key(aData); |
|
1754 nsRefPtr<GetUserMediaRunnable> runnable; |
|
1755 if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) { |
|
1756 return NS_OK; |
|
1757 } |
|
1758 mActiveCallbacks.Remove(key); |
|
1759 |
|
1760 if (aSubject) { |
|
1761 // A particular device or devices were chosen by the user. |
|
1762 // NOTE: does not allow setting a device to null; assumes nullptr |
|
1763 nsCOMPtr<nsISupportsArray> array(do_QueryInterface(aSubject)); |
|
1764 MOZ_ASSERT(array); |
|
1765 uint32_t len = 0; |
|
1766 array->Count(&len); |
|
1767 MOZ_ASSERT(len); |
|
1768 if (!len) { |
|
1769 // neither audio nor video were selected |
|
1770 runnable->Denied(NS_LITERAL_STRING("PERMISSION_DENIED")); |
|
1771 return NS_OK; |
|
1772 } |
|
1773 for (uint32_t i = 0; i < len; i++) { |
|
1774 nsCOMPtr<nsISupports> supports; |
|
1775 array->GetElementAt(i,getter_AddRefs(supports)); |
|
1776 nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supports)); |
|
1777 MOZ_ASSERT(device); // shouldn't be returning anything else... |
|
1778 if (device) { |
|
1779 nsString type; |
|
1780 device->GetType(type); |
|
1781 if (type.EqualsLiteral("video")) { |
|
1782 runnable->SetVideoDevice(static_cast<VideoDevice*>(device.get())); |
|
1783 } else if (type.EqualsLiteral("audio")) { |
|
1784 runnable->SetAudioDevice(static_cast<AudioDevice*>(device.get())); |
|
1785 } else { |
|
1786 NS_WARNING("Unknown device type in getUserMedia"); |
|
1787 } |
|
1788 } |
|
1789 } |
|
1790 } |
|
1791 |
|
1792 // Reuse the same thread to save memory. |
|
1793 mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); |
|
1794 return NS_OK; |
|
1795 |
|
1796 } else if (!strcmp(aTopic, "getUserMedia:response:deny")) { |
|
1797 nsString errorMessage(NS_LITERAL_STRING("PERMISSION_DENIED")); |
|
1798 |
|
1799 if (aSubject) { |
|
1800 nsCOMPtr<nsISupportsString> msg(do_QueryInterface(aSubject)); |
|
1801 MOZ_ASSERT(msg); |
|
1802 msg->GetData(errorMessage); |
|
1803 if (errorMessage.IsEmpty()) |
|
1804 errorMessage.Assign(NS_LITERAL_STRING("UNKNOWN_ERROR")); |
|
1805 } |
|
1806 |
|
1807 nsString key(aData); |
|
1808 nsRefPtr<GetUserMediaRunnable> runnable; |
|
1809 if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) { |
|
1810 return NS_OK; |
|
1811 } |
|
1812 mActiveCallbacks.Remove(key); |
|
1813 runnable->Denied(errorMessage); |
|
1814 return NS_OK; |
|
1815 |
|
1816 } else if (!strcmp(aTopic, "getUserMedia:revoke")) { |
|
1817 nsresult rv; |
|
1818 uint64_t windowID = nsString(aData).ToInteger64(&rv); |
|
1819 MOZ_ASSERT(NS_SUCCEEDED(rv)); |
|
1820 if (NS_SUCCEEDED(rv)) { |
|
1821 LOG(("Revoking MediaCapture access for window %llu",windowID)); |
|
1822 OnNavigation(windowID); |
|
1823 } |
|
1824 |
|
1825 return NS_OK; |
|
1826 } |
|
1827 #ifdef MOZ_WIDGET_GONK |
|
1828 else if (!strcmp(aTopic, "phone-state-changed")) { |
|
1829 nsString state(aData); |
|
1830 if (atoi((const char*)state.get()) == nsIAudioManager::PHONE_STATE_IN_CALL) { |
|
1831 StopMediaStreams(); |
|
1832 } |
|
1833 return NS_OK; |
|
1834 } |
|
1835 #endif |
|
1836 |
|
1837 return NS_OK; |
|
1838 } |
|
1839 |
|
1840 static PLDHashOperator |
|
1841 WindowsHashToArrayFunc (const uint64_t& aId, |
|
1842 StreamListeners* aData, |
|
1843 void *userArg) |
|
1844 { |
|
1845 nsISupportsArray *array = |
|
1846 static_cast<nsISupportsArray *>(userArg); |
|
1847 nsPIDOMWindow *window = static_cast<nsPIDOMWindow*> |
|
1848 (nsGlobalWindow::GetInnerWindowWithId(aId)); |
|
1849 |
|
1850 MOZ_ASSERT(window); |
|
1851 if (window) { |
|
1852 // mActiveWindows contains both windows that have requested device |
|
1853 // access and windows that are currently capturing media. We want |
|
1854 // to return only the latter. See bug 975177. |
|
1855 bool capturing = false; |
|
1856 if (aData) { |
|
1857 uint32_t length = aData->Length(); |
|
1858 for (uint32_t i = 0; i < length; ++i) { |
|
1859 nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener = |
|
1860 aData->ElementAt(i); |
|
1861 if (listener->CapturingVideo() || listener->CapturingAudio()) { |
|
1862 capturing = true; |
|
1863 break; |
|
1864 } |
|
1865 } |
|
1866 } |
|
1867 |
|
1868 if (capturing) |
|
1869 array->AppendElement(window); |
|
1870 } |
|
1871 return PL_DHASH_NEXT; |
|
1872 } |
|
1873 |
|
1874 |
|
1875 nsresult |
|
1876 MediaManager::GetActiveMediaCaptureWindows(nsISupportsArray **aArray) |
|
1877 { |
|
1878 MOZ_ASSERT(aArray); |
|
1879 nsISupportsArray *array; |
|
1880 nsresult rv = NS_NewISupportsArray(&array); // AddRefs |
|
1881 if (NS_FAILED(rv)) |
|
1882 return rv; |
|
1883 |
|
1884 mActiveWindows.EnumerateRead(WindowsHashToArrayFunc, array); |
|
1885 |
|
1886 *aArray = array; |
|
1887 return NS_OK; |
|
1888 } |
|
1889 |
|
1890 // XXX flags might be better... |
|
1891 struct CaptureWindowStateData { |
|
1892 bool *mVideo; |
|
1893 bool *mAudio; |
|
1894 }; |
|
1895 |
|
1896 static void |
|
1897 CaptureWindowStateCallback(MediaManager *aThis, |
|
1898 uint64_t aWindowID, |
|
1899 StreamListeners *aListeners, |
|
1900 void *aData) |
|
1901 { |
|
1902 struct CaptureWindowStateData *data = (struct CaptureWindowStateData *) aData; |
|
1903 |
|
1904 if (aListeners) { |
|
1905 auto length = aListeners->Length(); |
|
1906 for (size_t i = 0; i < length; ++i) { |
|
1907 GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i); |
|
1908 |
|
1909 if (listener->CapturingVideo()) { |
|
1910 *data->mVideo = true; |
|
1911 } |
|
1912 if (listener->CapturingAudio()) { |
|
1913 *data->mAudio = true; |
|
1914 } |
|
1915 } |
|
1916 } |
|
1917 } |
|
1918 |
|
1919 |
|
1920 NS_IMETHODIMP |
|
1921 MediaManager::MediaCaptureWindowState(nsIDOMWindow* aWindow, bool* aVideo, |
|
1922 bool* aAudio) |
|
1923 { |
|
1924 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
|
1925 struct CaptureWindowStateData data; |
|
1926 data.mVideo = aVideo; |
|
1927 data.mAudio = aAudio; |
|
1928 |
|
1929 *aVideo = false; |
|
1930 *aAudio = false; |
|
1931 |
|
1932 nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow); |
|
1933 if (piWin) { |
|
1934 IterateWindowListeners(piWin, CaptureWindowStateCallback, &data); |
|
1935 } |
|
1936 #ifdef DEBUG |
|
1937 LOG(("%s: window %lld capturing %s %s", __FUNCTION__, piWin ? piWin->WindowID() : -1, |
|
1938 *aVideo ? "video" : "", *aAudio ? "audio" : "")); |
|
1939 #endif |
|
1940 return NS_OK; |
|
1941 } |
|
1942 |
|
1943 // lets us do all sorts of things to the listeners |
|
1944 void |
|
1945 MediaManager::IterateWindowListeners(nsPIDOMWindow *aWindow, |
|
1946 WindowListenerCallback aCallback, |
|
1947 void *aData) |
|
1948 { |
|
1949 // Iterate the docshell tree to find all the child windows, and for each |
|
1950 // invoke the callback |
|
1951 nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow); |
|
1952 if (piWin) { |
|
1953 if (piWin->IsInnerWindow() || piWin->GetCurrentInnerWindow()) { |
|
1954 uint64_t windowID; |
|
1955 if (piWin->IsInnerWindow()) { |
|
1956 windowID = piWin->WindowID(); |
|
1957 } else { |
|
1958 windowID = piWin->GetCurrentInnerWindow()->WindowID(); |
|
1959 } |
|
1960 StreamListeners* listeners = GetActiveWindows()->Get(windowID); |
|
1961 // pass listeners so it can modify/delete the list |
|
1962 (*aCallback)(this, windowID, listeners, aData); |
|
1963 } |
|
1964 |
|
1965 // iterate any children of *this* window (iframes, etc) |
|
1966 nsCOMPtr<nsIDocShell> docShell = piWin->GetDocShell(); |
|
1967 if (docShell) { |
|
1968 int32_t i, count; |
|
1969 docShell->GetChildCount(&count); |
|
1970 for (i = 0; i < count; ++i) { |
|
1971 nsCOMPtr<nsIDocShellTreeItem> item; |
|
1972 docShell->GetChildAt(i, getter_AddRefs(item)); |
|
1973 nsCOMPtr<nsPIDOMWindow> win = do_GetInterface(item); |
|
1974 |
|
1975 if (win) { |
|
1976 IterateWindowListeners(win, aCallback, aData); |
|
1977 } |
|
1978 } |
|
1979 } |
|
1980 } |
|
1981 } |
|
1982 |
|
1983 void |
|
1984 MediaManager::StopMediaStreams() |
|
1985 { |
|
1986 nsCOMPtr<nsISupportsArray> array; |
|
1987 GetActiveMediaCaptureWindows(getter_AddRefs(array)); |
|
1988 uint32_t len; |
|
1989 array->Count(&len); |
|
1990 for (uint32_t i = 0; i < len; i++) { |
|
1991 nsCOMPtr<nsISupports> window; |
|
1992 array->GetElementAt(i, getter_AddRefs(window)); |
|
1993 nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(window)); |
|
1994 if (win) { |
|
1995 OnNavigation(win->WindowID()); |
|
1996 } |
|
1997 } |
|
1998 } |
|
1999 |
|
2000 // Can be invoked from EITHER MainThread or MSG thread |
|
2001 void |
|
2002 GetUserMediaCallbackMediaStreamListener::Invalidate() |
|
2003 { |
|
2004 |
|
2005 nsRefPtr<MediaOperationRunnable> runnable; |
|
2006 // We can't take a chance on blocking here, so proxy this to another |
|
2007 // thread. |
|
2008 // Pass a ref to us (which is threadsafe) so it can query us for the |
|
2009 // source stream info. |
|
2010 runnable = new MediaOperationRunnable(MEDIA_STOP, |
|
2011 this, nullptr, nullptr, |
|
2012 mAudioSource, mVideoSource, |
|
2013 mFinished, mWindowID, nullptr); |
|
2014 mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); |
|
2015 } |
|
2016 |
|
2017 // Called from the MediaStreamGraph thread |
|
2018 void |
|
2019 GetUserMediaCallbackMediaStreamListener::NotifyFinished(MediaStreamGraph* aGraph) |
|
2020 { |
|
2021 mFinished = true; |
|
2022 Invalidate(); // we know it's been activated |
|
2023 NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, this)); |
|
2024 } |
|
2025 |
|
2026 // Called from the MediaStreamGraph thread |
|
2027 // this can be in response to our own RemoveListener() (via ::Remove()), or |
|
2028 // because the DOM GC'd the DOMLocalMediaStream/etc we're attached to. |
|
2029 void |
|
2030 GetUserMediaCallbackMediaStreamListener::NotifyRemoved(MediaStreamGraph* aGraph) |
|
2031 { |
|
2032 { |
|
2033 MutexAutoLock lock(mLock); // protect access to mRemoved |
|
2034 MM_LOG(("Listener removed by DOM Destroy(), mFinished = %d", (int) mFinished)); |
|
2035 mRemoved = true; |
|
2036 } |
|
2037 if (!mFinished) { |
|
2038 NotifyFinished(aGraph); |
|
2039 } |
|
2040 } |
|
2041 |
|
2042 NS_IMETHODIMP |
|
2043 GetUserMediaNotificationEvent::Run() |
|
2044 { |
|
2045 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
|
2046 // Make sure mStream is cleared and our reference to the DOMMediaStream |
|
2047 // is dropped on the main thread, no matter what happens in this method. |
|
2048 // Otherwise this object might be destroyed off the main thread, |
|
2049 // releasing DOMMediaStream off the main thread, which is not allowed. |
|
2050 nsRefPtr<DOMMediaStream> stream = mStream.forget(); |
|
2051 |
|
2052 nsString msg; |
|
2053 switch (mStatus) { |
|
2054 case STARTING: |
|
2055 msg = NS_LITERAL_STRING("starting"); |
|
2056 stream->OnTracksAvailable(mOnTracksAvailableCallback.forget()); |
|
2057 break; |
|
2058 case STOPPING: |
|
2059 msg = NS_LITERAL_STRING("shutdown"); |
|
2060 if (mListener) { |
|
2061 mListener->SetStopped(); |
|
2062 } |
|
2063 break; |
|
2064 } |
|
2065 |
|
2066 nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID); |
|
2067 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); |
|
2068 |
|
2069 return MediaManager::NotifyRecordingStatusChange(window, msg, mIsAudio, mIsVideo); |
|
2070 } |
|
2071 |
|
2072 } // namespace mozilla |