Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
7 #include "MediaManager.h"
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"
33 #include "Latency.h"
35 // For PR_snprintf
36 #include "prprf.h"
38 #include "nsJSUtils.h"
39 #include "nsDOMFile.h"
40 #include "nsGlobalWindow.h"
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
48 #ifdef MOZ_B2G
49 #include "MediaPermissionGonk.h"
50 #endif
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
58 namespace mozilla {
60 #ifdef LOG
61 #undef LOG
62 #endif
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
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;
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 }
99 ErrorCallbackRunnable::~ErrorCallbackRunnable()
100 {
101 MOZ_ASSERT(!mSuccess && !mError);
102 }
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");
110 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget();
111 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
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 }
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 }
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");
149 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget();
150 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
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 }
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 };
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 }
190 NS_IMETHOD
191 Run()
192 {
193 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
195 // Only run if window is still on our active list.
196 if (!mManager->IsWindowStillActive(mWindowID)) {
197 return NS_OK;
198 }
200 nsCOMPtr<nsIWritableVariant> devices =
201 do_CreateInstance("@mozilla.org/variant;1");
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 }
213 nsTArray<nsIMediaDevice*> tmp(len);
214 for (int32_t i = 0; i < len; i++) {
215 tmp.AppendElement(mDevices->ElementAt(i));
216 }
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 ));
225 mSuccess->OnSuccess(devices);
226 return NS_OK;
227 }
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 };
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) {}
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 }
255 protected:
256 uint64_t mWindowID;
257 nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
258 };
260 /**
261 * nsIMediaDevice implementation.
262 */
263 NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
265 MediaDevice* MediaDevice::Create(MediaEngineVideoSource* source) {
266 return new VideoDevice(source);
267 }
269 MediaDevice* MediaDevice::Create(MediaEngineAudioSource* source) {
270 return new AudioDevice(source);
271 }
273 MediaDevice::MediaDevice(MediaEngineSource* aSource)
274 : mHasFacingMode(false)
275 , mSource(aSource) {
276 mSource->GetName(mName);
277 mSource->GetUUID(mID);
278 }
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
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 }
299 AudioDevice::AudioDevice(MediaEngineAudioSource* aSource)
300 : MediaDevice(aSource) {}
302 NS_IMETHODIMP
303 MediaDevice::GetName(nsAString& aName)
304 {
305 aName.Assign(mName);
306 return NS_OK;
307 }
309 NS_IMETHODIMP
310 MediaDevice::GetType(nsAString& aType)
311 {
312 return NS_OK;
313 }
315 NS_IMETHODIMP
316 VideoDevice::GetType(nsAString& aType)
317 {
318 aType.Assign(NS_LITERAL_STRING("video"));
319 return NS_OK;
320 }
322 NS_IMETHODIMP
323 AudioDevice::GetType(nsAString& aType)
324 {
325 aType.Assign(NS_LITERAL_STRING("audio"));
326 return NS_OK;
327 }
329 NS_IMETHODIMP
330 MediaDevice::GetId(nsAString& aID)
331 {
332 aID.Assign(mID);
333 return NS_OK;
334 }
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 }
348 MediaEngineVideoSource*
349 VideoDevice::GetSource()
350 {
351 return static_cast<MediaEngineVideoSource*>(&*mSource);
352 }
354 MediaEngineAudioSource*
355 AudioDevice::GetSource()
356 {
357 return static_cast<MediaEngineAudioSource*>(&*mSource);
358 }
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);
376 nsRefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aAudioSource);
377 stream->InitTrackUnionStream(aWindow, hints);
378 return stream.forget();
379 }
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 {}
398 virtual ~nsDOMUserMediaStream()
399 {
400 Stop();
402 if (mPort) {
403 mPort->Destroy();
404 }
405 if (mSourceStream) {
406 mSourceStream->Destroy();
407 }
408 }
410 virtual void Stop()
411 {
412 if (mSourceStream) {
413 mSourceStream->EndAllTrackAndFinish();
414 }
415 }
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 }
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 }
442 virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) MOZ_OVERRIDE
443 {
444 if (mSourceStream) {
445 mSourceStream->RemoveDirectListener(aListener);
446 }
447 }
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
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 }
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 };
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 }
508 ~GetUserMediaStreamRunnable() {}
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 }
526 // Start currentTime from the point where this stream was successfully
527 // returned.
528 aStream->SetLogicalStreamStartTime(aStream->GetStream()->GetCurrentTime());
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 };
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;
562 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
563 nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
564 (nsGlobalWindow::GetInnerWindowWithId(mWindowID));
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 }
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);
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);
608 MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
609 nsRefPtr<SourceMediaStream> stream = gm->CreateSourceStream(nullptr);
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()));
624 trackunion->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal());
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);
632 // Note: includes JS callbacks; must be released on MainThread
633 TracksAvailableCallback* tracksAvailableCallback =
634 new TracksAvailableCallback(mManager, mSuccess, mWindowID, trackunion);
636 mListener->AudioConfig(aec_on, (uint32_t) aec,
637 agc_on, (uint32_t) agc,
638 noise_on, (uint32_t) noise,
639 playout_delay);
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);
653 // We won't need mError now.
654 mError = nullptr;
655 return NS_OK;
656 }
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 };
668 static bool
669 IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) {
670 return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
671 }
673 static const MediaTrackConstraints&
674 GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) {
675 static const MediaTrackConstraints empty;
676 return aUnion.IsMediaTrackConstraints() ?
677 aUnion.GetAsMediaTrackConstraints() : empty;
678 }
680 /**
681 * Helper functions that implement the constraints algorithm from
682 * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
683 */
685 // Reminder: add handling for new constraints both here and in GetSources below!
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 }
703 static bool SatisfyConstraintSet(const MediaEngineAudioSource *,
704 const MediaTrackConstraintSet &aConstraints,
705 nsIMediaDevice &aCandidate)
706 {
707 // TODO: Add audio-specific constraints
708 return true;
709 }
711 typedef nsTArray<nsCOMPtr<nsIMediaDevice> > SourceSet;
713 // Source getter that constrains list returned
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);
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 }
754 // Apply constraints to the list of sources.
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 }
767 // Now on to the actual algorithm: First apply required constraints.
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 }
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 }
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.
804 SourceSet tailSet;
806 if (c.mAdvanced.WasPassed()) {
807 auto &array = c.mAdvanced.Value();
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 }
823 // TODO: Proper non-ordered handling of nonrequired constraints (Bug 907352)
825 result->MoveElementsFrom(candidateSet);
826 result->MoveElementsFrom(tailSet);
827 return result.forget();
828 }
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 {}
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 {}
881 ~GetUserMediaRunnable() {
882 }
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);
892 NS_DispatchToMainThread(runnable);
893 }
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);
902 MediaEngine* backend = mBackend;
903 // Was a backend provided?
904 if (!backend) {
905 backend = mManager->GetBackend(mWindowID);
906 }
908 // Was a device provided?
909 if (!mDeviceChosen) {
910 nsresult rv = SelectDevice(backend);
911 if (rv != NS_OK) {
912 return rv;
913 }
914 }
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 }
923 if (mConstraints.mPicture) {
924 ProcessGetUserMediaSnapshot(mVideoDevice->GetSource(), 0);
925 return NS_OK;
926 }
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 }
936 nsresult
937 Denied(const nsAString& aErrorMsg)
938 {
939 MOZ_ASSERT(mSuccess);
940 MOZ_ASSERT(mError);
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);
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);
959 // MUST happen after ErrorCallbackRunnable Run()s, as it checks the active window list
960 NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, mListener));
961 }
963 MOZ_ASSERT(!mSuccess);
964 MOZ_ASSERT(!mError);
966 return NS_OK;
967 }
969 nsresult
970 SetContraints(const MediaStreamConstraints& aConstraints)
971 {
972 mConstraints = aConstraints;
973 return NS_OK;
974 }
976 nsresult
977 SetAudioDevice(AudioDevice* aAudioDevice)
978 {
979 mAudioDevice = aAudioDevice;
980 mDeviceChosen = true;
981 return NS_OK;
982 }
984 nsresult
985 SetVideoDevice(VideoDevice* aVideoDevice)
986 {
987 mVideoDevice = aVideoDevice;
988 mDeviceChosen = true;
989 return NS_OK;
990 }
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));
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 }
1011 if (IsOn(mConstraints.mAudio)) {
1012 AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio));
1013 ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints,
1014 &MediaEngine::EnumerateAudioDevices));
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 }
1025 return NS_OK;
1026 }
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 }
1059 NS_DispatchToMainThread(new GetUserMediaStreamRunnable(
1060 mSuccess, mError, mWindowID, mListener, aAudioSource, aVideoSource
1061 ));
1063 MOZ_ASSERT(!mSuccess);
1064 MOZ_ASSERT(!mError);
1066 return;
1067 }
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 }
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();
1091 NS_DispatchToMainThread(new SuccessCallbackRunnable(
1092 mSuccess, mError, file, mWindowID
1093 ));
1095 MOZ_ASSERT(!mSuccess);
1096 MOZ_ASSERT(!mError);
1098 return;
1099 }
1101 private:
1102 MediaStreamConstraints mConstraints;
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;
1112 bool mDeviceChosen;
1114 RefPtr<MediaEngine> mBackend;
1115 nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
1116 };
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) {}
1140 NS_IMETHOD
1141 Run()
1142 {
1143 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1145 nsRefPtr<MediaEngine> backend;
1146 if (mConstraints.mFake)
1147 backend = new MediaEngineDefault();
1148 else
1149 backend = mManager->GetBackend(mWindowId);
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 }
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 };
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;
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 }
1209 NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIObserver)
1211 /* static */ StaticRefPtr<MediaManager> MediaManager::sSingleton;
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();
1221 NS_NewNamedThread("MediaManager", getter_AddRefs(sSingleton->mMediaThread));
1222 LOG(("New Media thread for gum"));
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 }
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 }
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);
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 }
1267 nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
1268 props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
1269 props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
1271 bool isApp = false;
1272 nsString requestURL;
1274 if (nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell()) {
1275 nsresult rv = docShell->GetIsApp(&isApp);
1276 NS_ENSURE_SUCCESS(rv, rv);
1278 if (isApp) {
1279 rv = docShell->GetAppManifestURL(requestURL);
1280 NS_ENSURE_SUCCESS(rv, rv);
1281 }
1282 }
1284 if (!isApp) {
1285 nsCString pageURL;
1286 nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
1287 NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE);
1289 nsresult rv = docURI->GetSpec(pageURL);
1290 NS_ENSURE_SUCCESS(rv, rv);
1292 requestURL = NS_ConvertUTF8toUTF16(pageURL);
1293 }
1295 props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp);
1296 props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
1298 obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
1299 "recording-device-events",
1300 aMsg.get());
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 }
1312 return NS_OK;
1313 }
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");
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);
1332 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess);
1333 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError);
1335 MediaStreamConstraints c(aConstraints); // copy
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
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 }
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 }
1387 // Ensure there's a thread for gum to proxy to off main thread
1388 nsIThread *mediaThread = MediaManager::GetThread();
1390 // Create a disabled listener to act as a placeholder
1391 GetUserMediaCallbackMediaStreamListener* listener =
1392 new GetUserMediaCallbackMediaStreamListener(mediaThread, windowID);
1394 // No need for locking because we always do this in the main thread.
1395 listeners->AppendElement(listener);
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 }
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
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 }
1439 #ifdef MOZ_B2G_CAMERA
1440 if (mCameraManager == nullptr) {
1441 mCameraManager = nsDOMCameraManager::CreateInstance(aWindow);
1442 }
1443 #endif
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 }
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);
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 }
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 }
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 }
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);
1495 // Generate a call ID.
1496 nsID id;
1497 rv = uuidgen->GenerateUUIDInPlace(&id);
1498 NS_ENSURE_SUCCESS(rv, rv);
1500 char buffer[NSID_LENGTH];
1501 id.ToProvidedString(buffer);
1502 NS_ConvertUTF8toUTF16 callID(buffer);
1504 // Store the current unarmed runnable w/callbacks.
1505 mActiveCallbacks.Put(callID, runnable);
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 }
1522 return NS_OK;
1523 }
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");
1534 NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER);
1535 NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER);
1537 nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
1538 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError);
1539 char* loopbackAudioDevice = nullptr;
1540 char* loopbackVideoDevice = nullptr;
1542 #ifdef DEBUG
1543 nsresult rv;
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
1556 nsCOMPtr<nsIRunnable> gUMDRunnable = new GetUserMediaDevicesRunnable(
1557 aConstraints, onSuccess.forget(), onError.forget(),
1558 (aInnerWindowID ? aInnerWindowID : aWindow->WindowID()),
1559 loopbackAudioDevice, loopbackVideoDevice);
1561 mMediaThread->Dispatch(gUMDRunnable, NS_DISPATCH_NORMAL);
1562 return NS_OK;
1563 }
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 }
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);
1593 if (listener->Stream()) { // aka HasBeenActivate()ed
1594 listener->Invalidate();
1595 }
1596 listener->Remove();
1597 }
1598 aListeners->Clear();
1599 aThis->RemoveWindowID(aWindowID);
1600 }
1601 }
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));
1610 // Invalidate this window. The runnables check this value before making
1611 // a call to content.
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 }
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 }
1632 void
1633 MediaManager::RemoveFromWindowList(uint64_t aWindowID,
1634 GetUserMediaCallbackMediaStreamListener *aListener)
1635 {
1636 NS_ASSERTION(NS_IsMainThread(), "RemoveFromWindowList called off main thread");
1638 // This is defined as safe on an inactive GUMCMSListener
1639 aListener->Remove(); // really queues the remove
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
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();
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));
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 }
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 }
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 }
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 }
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();
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");
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 }
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 }
1750 return NS_OK;
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);
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 }
1792 // Reuse the same thread to save memory.
1793 mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
1794 return NS_OK;
1796 } else if (!strcmp(aTopic, "getUserMedia:response:deny")) {
1797 nsString errorMessage(NS_LITERAL_STRING("PERMISSION_DENIED"));
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 }
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;
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 }
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
1837 return NS_OK;
1838 }
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));
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 }
1868 if (capturing)
1869 array->AppendElement(window);
1870 }
1871 return PL_DHASH_NEXT;
1872 }
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;
1884 mActiveWindows.EnumerateRead(WindowsHashToArrayFunc, array);
1886 *aArray = array;
1887 return NS_OK;
1888 }
1890 // XXX flags might be better...
1891 struct CaptureWindowStateData {
1892 bool *mVideo;
1893 bool *mAudio;
1894 };
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;
1904 if (aListeners) {
1905 auto length = aListeners->Length();
1906 for (size_t i = 0; i < length; ++i) {
1907 GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
1909 if (listener->CapturingVideo()) {
1910 *data->mVideo = true;
1911 }
1912 if (listener->CapturingAudio()) {
1913 *data->mAudio = true;
1914 }
1915 }
1916 }
1917 }
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;
1929 *aVideo = false;
1930 *aAudio = false;
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 }
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 }
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);
1975 if (win) {
1976 IterateWindowListeners(win, aCallback, aData);
1977 }
1978 }
1979 }
1980 }
1981 }
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 }
2000 // Can be invoked from EITHER MainThread or MSG thread
2001 void
2002 GetUserMediaCallbackMediaStreamListener::Invalidate()
2003 {
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 }
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 }
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 }
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();
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 }
2066 nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
2067 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
2069 return MediaManager::NotifyRecordingStatusChange(window, msg, mIsAudio, mIsVideo);
2070 }
2072 } // namespace mozilla