Bumping manifests a=b2g-bump
[gecko.git] / dom / media / MediaManager.cpp
blobbe6a6c334f3833010294b234fb57c16f600b7f32
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 "mozilla/dom/MediaStreamTrack.h"
11 #include "GetUserMediaRequest.h"
12 #include "nsHashPropertyBag.h"
13 #ifdef MOZ_WIDGET_GONK
14 #include "nsIAudioManager.h"
15 #endif
16 #include "nsIDOMFile.h"
17 #include "nsIEventTarget.h"
18 #include "nsIUUIDGenerator.h"
19 #include "nsIScriptGlobalObject.h"
20 #include "nsIPermissionManager.h"
21 #include "nsIPopupWindowManager.h"
22 #include "nsISupportsArray.h"
23 #include "nsIDocShell.h"
24 #include "nsIDocument.h"
25 #include "nsISupportsPrimitives.h"
26 #include "nsIInterfaceRequestorUtils.h"
27 #include "nsIIDNService.h"
28 #include "nsNetUtil.h"
29 #include "mozilla/Types.h"
30 #include "mozilla/PeerIdentity.h"
31 #include "mozilla/dom/ContentChild.h"
32 #include "mozilla/dom/MediaStreamBinding.h"
33 #include "mozilla/dom/MediaStreamTrackBinding.h"
34 #include "mozilla/dom/GetUserMediaRequestBinding.h"
35 #include "mozilla/Preferences.h"
36 #include "MediaTrackConstraints.h"
38 #include "Latency.h"
40 // For PR_snprintf
41 #include "prprf.h"
43 #include "nsJSUtils.h"
44 #include "nsDOMFile.h"
45 #include "nsGlobalWindow.h"
47 /* Using WebRTC backend on Desktops (Mac, Windows, Linux), otherwise default */
48 #include "MediaEngineDefault.h"
49 #if defined(MOZ_WEBRTC)
50 #include "MediaEngineWebRTC.h"
51 #include "browser_logging/WebRtcLog.h"
52 #endif
54 #ifdef MOZ_B2G
55 #include "MediaPermissionGonk.h"
56 #endif
58 #if defined(XP_MACOSX)
59 #include "nsCocoaFeatures.h"
60 #endif
61 #if defined (XP_WIN)
62 #include "mozilla/WindowsVersion.h"
63 #endif
65 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
66 // GetTickCount() and conflicts with MediaStream::GetCurrentTime.
67 #ifdef GetCurrentTime
68 #undef GetCurrentTime
69 #endif
71 // XXX Workaround for bug 986974 to maintain the existing broken semantics
72 template<>
73 struct nsIMediaDevice::COMTypeInfo<mozilla::VideoDevice, void> {
74 static const nsIID kIID;
76 const nsIID nsIMediaDevice::COMTypeInfo<mozilla::VideoDevice, void>::kIID = NS_IMEDIADEVICE_IID;
77 template<>
78 struct nsIMediaDevice::COMTypeInfo<mozilla::AudioDevice, void> {
79 static const nsIID kIID;
81 const nsIID nsIMediaDevice::COMTypeInfo<mozilla::AudioDevice, void>::kIID = NS_IMEDIADEVICE_IID;
83 namespace mozilla {
85 #ifdef LOG
86 #undef LOG
87 #endif
89 #ifdef PR_LOGGING
90 PRLogModuleInfo*
91 GetMediaManagerLog()
93 static PRLogModuleInfo *sLog;
94 if (!sLog)
95 sLog = PR_NewLogModule("MediaManager");
96 return sLog;
98 #define LOG(msg) PR_LOG(GetMediaManagerLog(), PR_LOG_DEBUG, msg)
99 #else
100 #define LOG(msg)
101 #endif
103 using dom::MediaStreamConstraints; // Outside API (contains JSObject)
104 using dom::MediaTrackConstraintSet; // Mandatory or optional constraints
105 using dom::MediaTrackConstraints; // Raw mMandatory (as JSObject)
106 using dom::GetUserMediaRequest;
107 using dom::Sequence;
108 using dom::OwningBooleanOrMediaTrackConstraints;
109 using dom::SupportedAudioConstraints;
110 using dom::SupportedVideoConstraints;
112 static bool
113 HostInDomain(const nsCString &aHost, const nsCString &aPattern)
115 PRInt32 patternOffset = 0;
116 PRInt32 hostOffset = 0;
118 // Act on '*.' wildcard in the left-most position in a domain pattern.
119 if (aPattern.Length() > 2 && aPattern[0] == '*' && aPattern[1] == '.') {
120 patternOffset = 2;
122 // Ignore the lowest level sub-domain for the hostname.
123 hostOffset = aHost.FindChar('.') + 1;
125 if (hostOffset <= 1) {
126 // Reject a match between a wildcard and a TLD or '.foo' form.
127 return false;
131 nsDependentCString hostRoot(aHost, hostOffset);
132 return hostRoot.EqualsIgnoreCase(aPattern.BeginReading() + patternOffset);
135 static bool
136 HostHasPermission(nsIURI &docURI)
138 nsresult rv;
140 bool isHttps;
141 rv = docURI.SchemeIs("https",&isHttps);
142 if (NS_WARN_IF(NS_FAILED(rv))) {
143 return false;
145 if (!isHttps) {
146 return false;
149 nsAdoptingCString hostName;
150 docURI.GetAsciiHost(hostName); //normalize UTF8 to ASCII equivalent
151 nsAdoptingCString domainWhiteList =
152 Preferences::GetCString("media.getusermedia.screensharing.allowed_domains");
153 domainWhiteList.StripWhitespace();
155 if (domainWhiteList.IsEmpty() || hostName.IsEmpty()) {
156 return false;
159 // Get UTF8 to ASCII domain name normalization service
160 nsCOMPtr<nsIIDNService> idnService
161 = do_GetService("@mozilla.org/network/idn-service;1", &rv);
162 if (NS_WARN_IF(NS_FAILED(rv))) {
163 return false;
166 uint32_t begin = 0;
167 uint32_t end = 0;
168 nsCString domainName;
170 Test each domain name in the comma separated list
171 after converting from UTF8 to ASCII. Each domain
172 must match exactly: no wildcards are used.
174 do {
175 end = domainWhiteList.FindChar(',', begin);
176 if (end == (uint32_t)-1) {
177 // Last or only domain name in the comma separated list
178 end = domainWhiteList.Length();
181 rv = idnService->ConvertUTF8toACE(Substring(domainWhiteList, begin, end - begin),
182 domainName);
183 if (NS_SUCCEEDED(rv)) {
184 if (HostInDomain(hostName, domainName)) {
185 return true;
187 } else {
188 NS_WARNING("Failed to convert UTF-8 host to ASCII");
191 begin = end + 1;
192 } while (end < domainWhiteList.Length());
194 return false;
197 ErrorCallbackRunnable::ErrorCallbackRunnable(
198 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
199 nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
200 const nsAString& aErrorMsg, uint64_t aWindowID)
201 : mErrorMsg(aErrorMsg)
202 , mWindowID(aWindowID)
203 , mManager(MediaManager::GetInstance())
205 mSuccess.swap(aSuccess);
206 mError.swap(aError);
209 ErrorCallbackRunnable::~ErrorCallbackRunnable()
211 MOZ_ASSERT(!mSuccess && !mError);
214 NS_IMETHODIMP
215 ErrorCallbackRunnable::Run()
217 // Only run if the window is still active.
218 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
220 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget();
221 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
223 if (!(mManager->IsWindowStillActive(mWindowID))) {
224 return NS_OK;
226 // This is safe since we're on main-thread, and the windowlist can only
227 // be invalidated from the main-thread (see OnNavigation)
228 error->OnError(mErrorMsg);
229 return NS_OK;
233 * Invoke the "onSuccess" callback in content. The callback will take a
234 * DOMBlob in the case of {picture:true}, and a MediaStream in the case of
235 * {audio:true} or {video:true}. There is a constructor available for each
236 * form. Do this only on the main thread.
238 class SuccessCallbackRunnable : public nsRunnable
240 public:
241 SuccessCallbackRunnable(
242 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
243 nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
244 nsIDOMFile* aFile, uint64_t aWindowID)
245 : mFile(aFile)
246 , mWindowID(aWindowID)
247 , mManager(MediaManager::GetInstance())
249 mSuccess.swap(aSuccess);
250 mError.swap(aError);
253 NS_IMETHOD
254 Run()
256 // Only run if the window is still active.
257 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
259 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget();
260 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
262 if (!(mManager->IsWindowStillActive(mWindowID))) {
263 return NS_OK;
265 // This is safe since we're on main-thread, and the windowlist can only
266 // be invalidated from the main-thread (see OnNavigation)
267 success->OnSuccess(mFile);
268 return NS_OK;
271 private:
272 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
273 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
274 nsCOMPtr<nsIDOMFile> mFile;
275 uint64_t mWindowID;
276 nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
280 * Invoke the GetUserMediaDevices success callback. Wrapped in a runnable
281 * so that it may be called on the main thread. The error callback is also
282 * passed so it can be released correctly.
284 class DeviceSuccessCallbackRunnable: public nsRunnable
286 public:
287 DeviceSuccessCallbackRunnable(
288 uint64_t aWindowID,
289 nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback>& aSuccess,
290 nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
291 nsTArray<nsCOMPtr<nsIMediaDevice> >* aDevices)
292 : mDevices(aDevices)
293 , mWindowID(aWindowID)
294 , mManager(MediaManager::GetInstance())
296 mSuccess.swap(aSuccess);
297 mError.swap(aError);
300 NS_IMETHOD
301 Run()
303 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
305 // Only run if window is still on our active list.
306 if (!mManager->IsWindowStillActive(mWindowID)) {
307 return NS_OK;
310 nsCOMPtr<nsIWritableVariant> devices =
311 do_CreateInstance("@mozilla.org/variant;1");
313 int32_t len = mDevices->Length();
314 if (len == 0) {
315 // XXX
316 // We should in the future return an empty array, and dynamically add
317 // devices to the dropdowns if things are hotplugged while the
318 // requester is up.
319 mError->OnError(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
320 return NS_OK;
323 nsTArray<nsIMediaDevice*> tmp(len);
324 for (int32_t i = 0; i < len; i++) {
325 tmp.AppendElement(mDevices->ElementAt(i));
328 devices->SetAsArray(nsIDataType::VTYPE_INTERFACE,
329 &NS_GET_IID(nsIMediaDevice),
330 mDevices->Length(),
331 const_cast<void*>(
332 static_cast<const void*>(tmp.Elements())
335 mSuccess->OnSuccess(devices);
336 return NS_OK;
339 private:
340 nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mSuccess;
341 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
342 nsAutoPtr<nsTArray<nsCOMPtr<nsIMediaDevice> > > mDevices;
343 uint64_t mWindowID;
344 nsRefPtr<MediaManager> mManager;
347 // Handle removing GetUserMediaCallbackMediaStreamListener from main thread
348 class GetUserMediaListenerRemove: public nsRunnable
350 public:
351 GetUserMediaListenerRemove(uint64_t aWindowID,
352 GetUserMediaCallbackMediaStreamListener *aListener)
353 : mWindowID(aWindowID)
354 , mListener(aListener) {}
356 NS_IMETHOD
357 Run()
359 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
360 nsRefPtr<MediaManager> manager(MediaManager::GetInstance());
361 manager->RemoveFromWindowList(mWindowID, mListener);
362 return NS_OK;
365 protected:
366 uint64_t mWindowID;
367 nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
371 * nsIMediaDevice implementation.
373 NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
375 MediaDevice* MediaDevice::Create(MediaEngineVideoSource* source) {
376 return new VideoDevice(source);
379 MediaDevice* MediaDevice::Create(MediaEngineAudioSource* source) {
380 return new AudioDevice(source);
383 MediaDevice::MediaDevice(MediaEngineSource* aSource)
384 : mHasFacingMode(false)
385 , mSource(aSource) {
386 mSource->GetName(mName);
387 mSource->GetUUID(mID);
390 VideoDevice::VideoDevice(MediaEngineVideoSource* aSource)
391 : MediaDevice(aSource) {
392 #ifdef MOZ_B2G_CAMERA
393 if (mName.EqualsLiteral("back")) {
394 mHasFacingMode = true;
395 mFacingMode = dom::VideoFacingModeEnum::Environment;
396 } else if (mName.EqualsLiteral("front")) {
397 mHasFacingMode = true;
398 mFacingMode = dom::VideoFacingModeEnum::User;
400 #endif // MOZ_B2G_CAMERA
401 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
402 // Names are generated. Example: "Camera 0, Facing back, Orientation 90"
404 // See media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/
405 // webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java
407 if (mName.Find(NS_LITERAL_STRING("Facing back")) != kNotFound) {
408 mHasFacingMode = true;
409 mFacingMode = dom::VideoFacingModeEnum::Environment;
410 } else if (mName.Find(NS_LITERAL_STRING("Facing front")) != kNotFound) {
411 mHasFacingMode = true;
412 mFacingMode = dom::VideoFacingModeEnum::User;
414 #endif // ANDROID
415 #ifdef XP_MACOSX
416 // Kludge to test user-facing cameras on OSX.
417 if (mName.Find(NS_LITERAL_STRING("Face")) != -1) {
418 mHasFacingMode = true;
419 mFacingMode = dom::VideoFacingModeEnum::User;
421 #endif
422 mMediaSource = aSource->GetMediaSource();
425 AudioDevice::AudioDevice(MediaEngineAudioSource* aSource)
426 : MediaDevice(aSource) {}
428 NS_IMETHODIMP
429 MediaDevice::GetName(nsAString& aName)
431 aName.Assign(mName);
432 return NS_OK;
435 NS_IMETHODIMP
436 MediaDevice::GetType(nsAString& aType)
438 return NS_OK;
441 NS_IMETHODIMP
442 VideoDevice::GetType(nsAString& aType)
444 aType.AssignLiteral(MOZ_UTF16("video"));
445 return NS_OK;
448 NS_IMETHODIMP
449 AudioDevice::GetType(nsAString& aType)
451 aType.AssignLiteral(MOZ_UTF16("audio"));
452 return NS_OK;
455 NS_IMETHODIMP
456 MediaDevice::GetId(nsAString& aID)
458 aID.Assign(mID);
459 return NS_OK;
462 NS_IMETHODIMP
463 MediaDevice::GetFacingMode(nsAString& aFacingMode)
465 if (mHasFacingMode) {
466 aFacingMode.Assign(NS_ConvertUTF8toUTF16(
467 dom::VideoFacingModeEnumValues::strings[uint32_t(mFacingMode)].value));
468 } else {
469 aFacingMode.Truncate(0);
471 return NS_OK;
474 NS_IMETHODIMP
475 MediaDevice::GetMediaSource(nsAString& aMediaSource)
477 if (mMediaSource == MediaSourceType::Microphone) {
478 aMediaSource.Assign(NS_LITERAL_STRING("microphone"));
479 } else if (mMediaSource == MediaSourceType::Window) { // this will go away
480 aMediaSource.Assign(NS_LITERAL_STRING("window"));
481 } else { // all the rest are shared
482 aMediaSource.Assign(NS_ConvertUTF8toUTF16(
483 dom::MediaSourceEnumValues::strings[uint32_t(mMediaSource)].value));
485 return NS_OK;
488 MediaEngineVideoSource*
489 VideoDevice::GetSource()
491 return static_cast<MediaEngineVideoSource*>(&*mSource);
494 MediaEngineAudioSource*
495 AudioDevice::GetSource()
497 return static_cast<MediaEngineAudioSource*>(&*mSource);
501 * A subclass that we only use to stash internal pointers to MediaStreamGraph objects
502 * that need to be cleaned up.
504 class nsDOMUserMediaStream : public DOMLocalMediaStream
506 public:
507 static already_AddRefed<nsDOMUserMediaStream>
508 CreateTrackUnionStream(nsIDOMWindow* aWindow,
509 GetUserMediaCallbackMediaStreamListener* aListener,
510 MediaEngineSource* aAudioSource,
511 MediaEngineSource* aVideoSource)
513 DOMMediaStream::TrackTypeHints hints =
514 (aAudioSource ? DOMMediaStream::HINT_CONTENTS_AUDIO : 0) |
515 (aVideoSource ? DOMMediaStream::HINT_CONTENTS_VIDEO : 0);
517 nsRefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aListener,
518 aAudioSource);
519 stream->InitTrackUnionStream(aWindow, hints);
520 return stream.forget();
523 nsDOMUserMediaStream(GetUserMediaCallbackMediaStreamListener* aListener,
524 MediaEngineSource *aAudioSource) :
525 mListener(aListener),
526 mAudioSource(aAudioSource),
527 mEchoOn(true),
528 mAgcOn(false),
529 mNoiseOn(true),
530 #ifdef MOZ_WEBRTC
531 mEcho(webrtc::kEcDefault),
532 mAgc(webrtc::kAgcDefault),
533 mNoise(webrtc::kNsDefault),
534 #else
535 mEcho(0),
536 mAgc(0),
537 mNoise(0),
538 #endif
539 mPlayoutDelay(20)
542 virtual ~nsDOMUserMediaStream()
544 Stop();
546 if (mPort) {
547 mPort->Destroy();
549 if (mSourceStream) {
550 mSourceStream->Destroy();
554 virtual void Stop()
556 if (mSourceStream) {
557 mSourceStream->EndAllTrackAndFinish();
561 // For gUM streams, we have a trackunion which assigns TrackIDs. However, for a
562 // single-source trackunion like we have here, the TrackUnion will assign trackids
563 // that match the source's trackids, so we can avoid needing a mapping function.
564 // XXX This will not handle more complex cases well.
565 virtual void StopTrack(TrackID aTrackID)
567 if (mSourceStream) {
568 mSourceStream->EndTrack(aTrackID);
569 // We could override NotifyMediaStreamTrackEnded(), and maybe should, but it's
570 // risky to do late in a release since that will affect all track ends, and not
571 // just StopTrack()s.
572 if (GetDOMTrackFor(aTrackID)) {
573 mListener->StopTrack(aTrackID, !!GetDOMTrackFor(aTrackID)->AsAudioStreamTrack());
574 } else {
575 LOG(("StopTrack(%d) on non-existant track", aTrackID));
580 #if 0
581 virtual void NotifyMediaStreamTrackEnded(dom::MediaStreamTrack* aTrack)
583 TrackID trackID = aTrack->GetTrackID();
584 // We override this so we can also tell the backend to stop capturing if the track ends
585 LOG(("track %d ending, type = %s",
586 trackID, aTrack->AsAudioStreamTrack() ? "audio" : "video"));
587 MOZ_ASSERT(aTrack->AsVideoStreamTrack() || aTrack->AsAudioStreamTrack());
588 mListener->StopTrack(trackID, !!aTrack->AsAudioStreamTrack());
590 // forward to superclass
591 DOMLocalMediaStream::NotifyMediaStreamTrackEnded(aTrack);
593 #endif
595 // Allow getUserMedia to pass input data directly to PeerConnection/MediaPipeline
596 virtual bool AddDirectListener(MediaStreamDirectListener *aListener) MOZ_OVERRIDE
598 if (mSourceStream) {
599 mSourceStream->AddDirectListener(aListener);
600 return true; // application should ignore NotifyQueuedTrackData
602 return false;
605 virtual void
606 AudioConfig(bool aEchoOn, uint32_t aEcho,
607 bool aAgcOn, uint32_t aAgc,
608 bool aNoiseOn, uint32_t aNoise,
609 int32_t aPlayoutDelay)
611 mEchoOn = aEchoOn;
612 mEcho = aEcho;
613 mAgcOn = aAgcOn;
614 mAgc = aAgc;
615 mNoiseOn = aNoiseOn;
616 mNoise = aNoise;
617 mPlayoutDelay = aPlayoutDelay;
620 virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) MOZ_OVERRIDE
622 if (mSourceStream) {
623 mSourceStream->RemoveDirectListener(aListener);
627 // let us intervene for direct listeners when someone does track.enabled = false
628 virtual void SetTrackEnabled(TrackID aID, bool aEnabled) MOZ_OVERRIDE
630 // We encapsulate the SourceMediaStream and TrackUnion into one entity, so
631 // we can handle the disabling at the SourceMediaStream
633 // We need to find the input track ID for output ID aID, so we let the TrackUnion
634 // forward the request to the source and translate the ID
635 GetStream()->AsProcessedStream()->ForwardTrackEnabled(aID, aEnabled);
638 // The actual MediaStream is a TrackUnionStream. But these resources need to be
639 // explicitly destroyed too.
640 nsRefPtr<SourceMediaStream> mSourceStream;
641 nsRefPtr<MediaInputPort> mPort;
642 nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
643 nsRefPtr<MediaEngineSource> mAudioSource; // so we can turn on AEC
644 bool mEchoOn;
645 bool mAgcOn;
646 bool mNoiseOn;
647 uint32_t mEcho;
648 uint32_t mAgc;
649 uint32_t mNoise;
650 uint32_t mPlayoutDelay;
654 * Creates a MediaStream, attaches a listener and fires off a success callback
655 * to the DOM with the stream. We also pass in the error callback so it can
656 * be released correctly.
658 * All of this must be done on the main thread!
660 * Note that the various GetUserMedia Runnable classes currently allow for
661 * two streams. If we ever need to support getting more than two streams
662 * at once, we could convert everything to nsTArray<nsRefPtr<blah> >'s,
663 * though that would complicate the constructors some. Currently the
664 * GetUserMedia spec does not allow for more than 2 streams to be obtained in
665 * one call, to simplify handling of constraints.
667 class GetUserMediaStreamRunnable : public nsRunnable
669 public:
670 GetUserMediaStreamRunnable(
671 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
672 nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
673 uint64_t aWindowID,
674 GetUserMediaCallbackMediaStreamListener* aListener,
675 MediaEngineSource* aAudioSource,
676 MediaEngineSource* aVideoSource,
677 PeerIdentity* aPeerIdentity)
678 : mAudioSource(aAudioSource)
679 , mVideoSource(aVideoSource)
680 , mWindowID(aWindowID)
681 , mListener(aListener)
682 , mPeerIdentity(aPeerIdentity)
683 , mManager(MediaManager::GetInstance())
685 mSuccess.swap(aSuccess);
686 mError.swap(aError);
689 ~GetUserMediaStreamRunnable() {}
691 class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback
693 public:
694 TracksAvailableCallback(MediaManager* aManager,
695 nsIDOMGetUserMediaSuccessCallback* aSuccess,
696 uint64_t aWindowID,
697 DOMMediaStream* aStream)
698 : mWindowID(aWindowID), mSuccess(aSuccess), mManager(aManager),
699 mStream(aStream) {}
700 virtual void NotifyTracksAvailable(DOMMediaStream* aStream) MOZ_OVERRIDE
702 // We're in the main thread, so no worries here.
703 if (!(mManager->IsWindowStillActive(mWindowID))) {
704 return;
707 // Start currentTime from the point where this stream was successfully
708 // returned.
709 aStream->SetLogicalStreamStartTime(aStream->GetStream()->GetCurrentTime());
711 // This is safe since we're on main-thread, and the windowlist can only
712 // be invalidated from the main-thread (see OnNavigation)
713 LOG(("Returning success for getUserMedia()"));
714 mSuccess->OnSuccess(aStream);
716 uint64_t mWindowID;
717 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
718 nsRefPtr<MediaManager> mManager;
719 // Keep the DOMMediaStream alive until the NotifyTracksAvailable callback
720 // has fired, otherwise we might immediately destroy the DOMMediaStream and
721 // shut down the underlying MediaStream prematurely.
722 // This creates a cycle which is broken when NotifyTracksAvailable
723 // is fired (which will happen unless the browser shuts down,
724 // since we only add this callback when we've successfully appended
725 // the desired tracks in the MediaStreamGraph) or when
726 // DOMMediaStream::NotifyMediaStreamGraphShutdown is called.
727 nsRefPtr<DOMMediaStream> mStream;
730 NS_IMETHOD
731 Run()
733 #ifdef MOZ_WEBRTC
734 int32_t aec = (int32_t) webrtc::kEcUnchanged;
735 int32_t agc = (int32_t) webrtc::kAgcUnchanged;
736 int32_t noise = (int32_t) webrtc::kNsUnchanged;
737 #else
738 int32_t aec = 0, agc = 0, noise = 0;
739 #endif
740 bool aec_on = false, agc_on = false, noise_on = false;
741 int32_t playout_delay = 0;
743 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
744 nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
745 (nsGlobalWindow::GetInnerWindowWithId(mWindowID));
747 // We're on main-thread, and the windowlist can only
748 // be invalidated from the main-thread (see OnNavigation)
749 StreamListeners* listeners = mManager->GetWindowListeners(mWindowID);
750 if (!listeners || !window || !window->GetExtantDoc()) {
751 // This window is no longer live. mListener has already been removed
752 return NS_OK;
755 #ifdef MOZ_WEBRTC
756 // Right now these configs are only of use if webrtc is available
757 nsresult rv;
758 nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
759 if (NS_SUCCEEDED(rv)) {
760 nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
762 if (branch) {
763 branch->GetBoolPref("media.getusermedia.aec_enabled", &aec_on);
764 branch->GetIntPref("media.getusermedia.aec", &aec);
765 branch->GetBoolPref("media.getusermedia.agc_enabled", &agc_on);
766 branch->GetIntPref("media.getusermedia.agc", &agc);
767 branch->GetBoolPref("media.getusermedia.noise_enabled", &noise_on);
768 branch->GetIntPref("media.getusermedia.noise", &noise);
769 branch->GetIntPref("media.getusermedia.playout_delay", &playout_delay);
772 #endif
773 // Create a media stream.
774 nsRefPtr<nsDOMUserMediaStream> trackunion =
775 nsDOMUserMediaStream::CreateTrackUnionStream(window, mListener,
776 mAudioSource, mVideoSource);
777 if (!trackunion) {
778 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
779 LOG(("Returning error for getUserMedia() - no stream"));
780 error->OnError(NS_LITERAL_STRING("NO_STREAM"));
781 return NS_OK;
783 trackunion->AudioConfig(aec_on, (uint32_t) aec,
784 agc_on, (uint32_t) agc,
785 noise_on, (uint32_t) noise,
786 playout_delay);
789 MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
790 nsRefPtr<SourceMediaStream> stream = gm->CreateSourceStream(nullptr);
792 // connect the source stream to the track union stream to avoid us blocking
793 trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true);
794 nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()->
795 AllocateInputPort(stream, MediaInputPort::FLAG_BLOCK_OUTPUT);
796 trackunion->mSourceStream = stream;
797 trackunion->mPort = port.forget();
798 // Log the relationship between SourceMediaStream and TrackUnion stream
799 // Make sure logger starts before capture
800 AsyncLatencyLogger::Get(true);
801 LogLatency(AsyncLatencyLogger::MediaStreamCreate,
802 reinterpret_cast<uint64_t>(stream.get()),
803 reinterpret_cast<int64_t>(trackunion->GetStream()));
805 nsCOMPtr<nsIPrincipal> principal;
806 if (mPeerIdentity) {
807 principal = do_CreateInstance("@mozilla.org/nullprincipal;1");
808 trackunion->SetPeerIdentity(mPeerIdentity.forget());
809 } else {
810 principal = window->GetExtantDoc()->NodePrincipal();
812 trackunion->CombineWithPrincipal(principal);
814 // The listener was added at the beginning in an inactive state.
815 // Activate our listener. We'll call Start() on the source when get a callback
816 // that the MediaStream has started consuming. The listener is freed
817 // when the page is invalidated (on navigation or close).
818 mListener->Activate(stream.forget(), mAudioSource, mVideoSource);
820 // Note: includes JS callbacks; must be released on MainThread
821 TracksAvailableCallback* tracksAvailableCallback =
822 new TracksAvailableCallback(mManager, mSuccess, mWindowID, trackunion);
824 mListener->AudioConfig(aec_on, (uint32_t) aec,
825 agc_on, (uint32_t) agc,
826 noise_on, (uint32_t) noise,
827 playout_delay);
829 // Dispatch to the media thread to ask it to start the sources,
830 // because that can take a while.
831 // Pass ownership of trackunion to the MediaOperationTask
832 // to ensure it's kept alive until the MediaOperationTask runs (at least).
833 MediaManager::GetMessageLoop()->PostTask(FROM_HERE,
834 new MediaOperationTask(MEDIA_START, mListener, trackunion,
835 tracksAvailableCallback,
836 mAudioSource, mVideoSource, false, mWindowID,
837 mError.forget()));
839 // We won't need mError now.
840 mError = nullptr;
841 return NS_OK;
844 private:
845 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
846 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
847 nsRefPtr<MediaEngineSource> mAudioSource;
848 nsRefPtr<MediaEngineSource> mVideoSource;
849 uint64_t mWindowID;
850 nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
851 nsAutoPtr<PeerIdentity> mPeerIdentity;
852 nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
855 static bool
856 IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) {
857 return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
860 static const MediaTrackConstraints&
861 GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) {
862 static const MediaTrackConstraints empty;
863 return aUnion.IsMediaTrackConstraints() ?
864 aUnion.GetAsMediaTrackConstraints() : empty;
868 * Helper functions that implement the constraints algorithm from
869 * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
872 // Reminder: add handling for new constraints both here and in GetSources below!
874 static bool SatisfyConstraintSet(const MediaEngineVideoSource *,
875 const MediaTrackConstraintSet &aConstraints,
876 nsIMediaDevice &aCandidate)
878 nsString s;
879 if (aConstraints.mFacingMode.WasPassed()) {
880 aCandidate.GetFacingMode(s);
881 if (!s.EqualsASCII(dom::VideoFacingModeEnumValues::strings[
882 uint32_t(aConstraints.mFacingMode.Value())].value)) {
883 return false;
886 aCandidate.GetMediaSource(s);
887 if (!s.EqualsASCII(dom::MediaSourceEnumValues::strings[
888 uint32_t(aConstraints.mMediaSource)].value)) {
889 return false;
891 // TODO: Add more video-specific constraints
892 return true;
895 static bool SatisfyConstraintSet(const MediaEngineAudioSource *,
896 const MediaTrackConstraintSet &aConstraints,
897 nsIMediaDevice &aCandidate)
899 // TODO: Add audio-specific constraints
900 return true;
903 typedef nsTArray<nsCOMPtr<nsIMediaDevice> > SourceSet;
905 // Source getter that constrains list returned
907 template<class SourceType, class ConstraintsType>
908 static SourceSet *
909 GetSources(MediaEngine *engine,
910 ConstraintsType &aConstraints,
911 void (MediaEngine::* aEnumerate)(MediaSourceType, nsTArray<nsRefPtr<SourceType> >*),
912 const char* media_device_name = nullptr)
914 ScopedDeletePtr<SourceSet> result(new SourceSet);
916 const SourceType * const type = nullptr;
917 nsString deviceName;
918 // First collect sources
919 SourceSet candidateSet;
921 nsTArray<nsRefPtr<SourceType> > sources;
922 // all MediaSourceEnums are contained in MediaSourceType
923 (engine->*aEnumerate)((MediaSourceType)((int)aConstraints.mMediaSource), &sources);
925 * We're allowing multiple tabs to access the same camera for parity
926 * with Chrome. See bug 811757 for some of the issues surrounding
927 * this decision. To disallow, we'd filter by IsAvailable() as we used
928 * to.
930 for (uint32_t len = sources.Length(), i = 0; i < len; i++) {
931 sources[i]->GetName(deviceName);
932 if (media_device_name && strlen(media_device_name) > 0) {
933 if (deviceName.EqualsASCII(media_device_name)) {
934 candidateSet.AppendElement(MediaDevice::Create(sources[i]));
935 break;
937 } else {
938 candidateSet.AppendElement(MediaDevice::Create(sources[i]));
943 // Apply constraints to the list of sources.
945 auto& c = aConstraints;
946 if (c.mUnsupportedRequirement) {
947 // Check upfront the names of required constraints that are unsupported for
948 // this media-type. The spec requires these to fail, so getting them out of
949 // the way early provides a necessary invariant for the remaining algorithm
950 // which maximizes code-reuse by ignoring constraints of the other type
951 // (specifically, SatisfyConstraintSet is reused for the advanced algorithm
952 // where the spec requires it to ignore constraints of the other type)
953 return result.forget();
956 // Now on to the actual algorithm: First apply required constraints.
958 for (uint32_t i = 0; i < candidateSet.Length();) {
959 // Overloading instead of template specialization keeps things local
960 if (!SatisfyConstraintSet(type, c.mRequired, *candidateSet[i])) {
961 candidateSet.RemoveElementAt(i);
962 } else {
963 ++i;
967 // TODO(jib): Proper non-ordered handling of nonrequired constraints (907352)
969 // For now, put nonrequired constraints at tail of Advanced list.
970 // This isn't entirely accurate, as order will matter, but few will notice
971 // the difference until we get camera selection and a few more constraints.
972 if (c.mNonrequired.Length()) {
973 if (!c.mAdvanced.WasPassed()) {
974 c.mAdvanced.Construct();
976 c.mAdvanced.Value().MoveElementsFrom(c.mNonrequired);
979 // Then apply advanced (formerly known as optional) constraints.
981 // These are only effective when there are multiple sources to pick from.
982 // Spec as-of-this-writing says to run algorithm on "all possible tracks
983 // of media type T that the browser COULD RETURN" (emphasis added).
985 // We think users ultimately control which devices we could return, so after
986 // determining the webpage's preferred list, we add the remaining choices
987 // to the tail, reasoning that they would all have passed individually,
988 // i.e. if the user had any one of them as their sole device (enabled).
990 // This avoids users having to unplug/disable devices should a webpage pick
991 // the wrong one (UX-fail). Webpage-preferred devices will be listed first.
993 SourceSet tailSet;
995 if (c.mAdvanced.WasPassed()) {
996 auto &array = c.mAdvanced.Value();
998 for (int i = 0; i < int(array.Length()); i++) {
999 SourceSet rejects;
1000 for (uint32_t j = 0; j < candidateSet.Length();) {
1001 if (!SatisfyConstraintSet(type, array[i], *candidateSet[j])) {
1002 rejects.AppendElement(candidateSet[j]);
1003 candidateSet.RemoveElementAt(j);
1004 } else {
1005 ++j;
1008 (candidateSet.Length()? tailSet : candidateSet).MoveElementsFrom(rejects);
1012 // TODO: Proper non-ordered handling of nonrequired constraints (Bug 907352)
1014 result->MoveElementsFrom(candidateSet);
1015 result->MoveElementsFrom(tailSet);
1016 return result.forget();
1020 * Runs on a seperate thread and is responsible for enumerating devices.
1021 * Depending on whether a picture or stream was asked for, either
1022 * ProcessGetUserMedia or ProcessGetUserMediaSnapshot is called, and the results
1023 * are sent back to the DOM.
1025 * Do not run this on the main thread. The success and error callbacks *MUST*
1026 * be dispatched on the main thread!
1028 class GetUserMediaTask : public Task
1030 public:
1031 GetUserMediaTask(
1032 const MediaStreamConstraints& aConstraints,
1033 already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
1034 already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
1035 uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
1036 MediaEnginePrefs &aPrefs)
1037 : mConstraints(aConstraints)
1038 , mSuccess(aSuccess)
1039 , mError(aError)
1040 , mWindowID(aWindowID)
1041 , mListener(aListener)
1042 , mPrefs(aPrefs)
1043 , mDeviceChosen(false)
1044 , mBackend(nullptr)
1045 , mManager(MediaManager::GetInstance())
1049 * The caller can also choose to provide their own backend instead of
1050 * using the one provided by MediaManager::GetBackend.
1052 GetUserMediaTask(
1053 const MediaStreamConstraints& aConstraints,
1054 already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
1055 already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
1056 uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
1057 MediaEnginePrefs &aPrefs,
1058 MediaEngine* aBackend)
1059 : mConstraints(aConstraints)
1060 , mSuccess(aSuccess)
1061 , mError(aError)
1062 , mWindowID(aWindowID)
1063 , mListener(aListener)
1064 , mPrefs(aPrefs)
1065 , mDeviceChosen(false)
1066 , mBackend(aBackend)
1067 , mManager(MediaManager::GetInstance())
1070 ~GetUserMediaTask() {
1073 void
1074 Fail(const nsAString& aMessage) {
1075 nsRefPtr<ErrorCallbackRunnable> runnable =
1076 new ErrorCallbackRunnable(mSuccess, mError, aMessage, mWindowID);
1077 // These should be empty now
1078 MOZ_ASSERT(!mSuccess);
1079 MOZ_ASSERT(!mError);
1081 NS_DispatchToMainThread(runnable);
1084 void
1085 Run()
1087 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1088 MOZ_ASSERT(mSuccess);
1089 MOZ_ASSERT(mError);
1091 MediaEngine* backend = mBackend;
1092 // Was a backend provided?
1093 if (!backend) {
1094 backend = mManager->GetBackend(mWindowID);
1097 // Was a device provided?
1098 if (!mDeviceChosen) {
1099 nsresult rv = SelectDevice(backend);
1100 if (rv != NS_OK) {
1101 return;
1105 // It is an error if audio or video are requested along with picture.
1106 if (mConstraints.mPicture &&
1107 (IsOn(mConstraints.mAudio) || IsOn(mConstraints.mVideo))) {
1108 Fail(NS_LITERAL_STRING("NOT_SUPPORTED_ERR"));
1109 return;
1112 if (mConstraints.mPicture) {
1113 ProcessGetUserMediaSnapshot(mVideoDevice->GetSource(), 0);
1114 return;
1117 // There's a bug in the permission code that can leave us with mAudio but no audio device
1118 ProcessGetUserMedia(((IsOn(mConstraints.mAudio) && mAudioDevice) ?
1119 mAudioDevice->GetSource() : nullptr),
1120 ((IsOn(mConstraints.mVideo) && mVideoDevice) ?
1121 mVideoDevice->GetSource() : nullptr));
1124 nsresult
1125 Denied(const nsAString& aErrorMsg)
1127 MOZ_ASSERT(mSuccess);
1128 MOZ_ASSERT(mError);
1130 // We add a disabled listener to the StreamListeners array until accepted
1131 // If this was the only active MediaStream, remove the window from the list.
1132 if (NS_IsMainThread()) {
1133 // This is safe since we're on main-thread, and the window can only
1134 // be invalidated from the main-thread (see OnNavigation)
1135 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget();
1136 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget();
1137 error->OnError(aErrorMsg);
1139 // Should happen *after* error runs for consistency, but may not matter
1140 nsRefPtr<MediaManager> manager(MediaManager::GetInstance());
1141 manager->RemoveFromWindowList(mWindowID, mListener);
1142 } else {
1143 // This will re-check the window being alive on main-thread
1144 // Note: we must remove the listener on MainThread as well
1145 Fail(aErrorMsg);
1147 // MUST happen after ErrorCallbackRunnable Run()s, as it checks the active window list
1148 NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, mListener));
1151 MOZ_ASSERT(!mSuccess);
1152 MOZ_ASSERT(!mError);
1154 return NS_OK;
1157 nsresult
1158 SetContraints(const MediaStreamConstraints& aConstraints)
1160 mConstraints = aConstraints;
1161 return NS_OK;
1164 nsresult
1165 SetAudioDevice(AudioDevice* aAudioDevice)
1167 mAudioDevice = aAudioDevice;
1168 mDeviceChosen = true;
1169 return NS_OK;
1172 nsresult
1173 SetVideoDevice(VideoDevice* aVideoDevice)
1175 mVideoDevice = aVideoDevice;
1176 mDeviceChosen = true;
1177 return NS_OK;
1180 nsresult
1181 SelectDevice(MediaEngine* backend)
1183 MOZ_ASSERT(mSuccess);
1184 MOZ_ASSERT(mError);
1185 if (mConstraints.mPicture || IsOn(mConstraints.mVideo)) {
1186 VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo));
1187 ScopedDeletePtr<SourceSet> sources(GetSources(backend, constraints,
1188 &MediaEngine::EnumerateVideoDevices));
1190 if (!sources->Length()) {
1191 Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
1192 return NS_ERROR_FAILURE;
1194 // Pick the first available device.
1195 mVideoDevice = do_QueryObject((*sources)[0]);
1196 LOG(("Selected video device"));
1199 if (IsOn(mConstraints.mAudio)) {
1200 AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio));
1201 ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints,
1202 &MediaEngine::EnumerateAudioDevices));
1204 if (!sources->Length()) {
1205 Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
1206 return NS_ERROR_FAILURE;
1208 // Pick the first available device.
1209 mAudioDevice = do_QueryObject((*sources)[0]);
1210 LOG(("Selected audio device"));
1213 return NS_OK;
1217 * Allocates a video or audio device and returns a MediaStream via
1218 * a GetUserMediaStreamRunnable. Runs off the main thread.
1220 void
1221 ProcessGetUserMedia(MediaEngineAudioSource* aAudioSource,
1222 MediaEngineVideoSource* aVideoSource)
1224 MOZ_ASSERT(mSuccess);
1225 MOZ_ASSERT(mError);
1226 nsresult rv;
1227 if (aAudioSource) {
1228 rv = aAudioSource->Allocate(GetInvariant(mConstraints.mAudio), mPrefs);
1229 if (NS_FAILED(rv)) {
1230 LOG(("Failed to allocate audiosource %d",rv));
1231 Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE"));
1232 return;
1235 if (aVideoSource) {
1236 rv = aVideoSource->Allocate(GetInvariant(mConstraints.mVideo), mPrefs);
1237 if (NS_FAILED(rv)) {
1238 LOG(("Failed to allocate videosource %d\n",rv));
1239 if (aAudioSource) {
1240 aAudioSource->Deallocate();
1242 Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE"));
1243 return;
1246 PeerIdentity* peerIdentity = nullptr;
1247 if (!mConstraints.mPeerIdentity.IsEmpty()) {
1248 peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
1251 NS_DispatchToMainThread(new GetUserMediaStreamRunnable(
1252 mSuccess, mError, mWindowID, mListener, aAudioSource, aVideoSource,
1253 peerIdentity
1256 MOZ_ASSERT(!mSuccess);
1257 MOZ_ASSERT(!mError);
1259 return;
1263 * Allocates a video device, takes a snapshot and returns a DOMFile via
1264 * a SuccessRunnable or an error via the ErrorRunnable. Off the main thread.
1266 void
1267 ProcessGetUserMediaSnapshot(MediaEngineVideoSource* aSource, int aDuration)
1269 MOZ_ASSERT(mSuccess);
1270 MOZ_ASSERT(mError);
1271 nsresult rv = aSource->Allocate(GetInvariant(mConstraints.mVideo), mPrefs);
1272 if (NS_FAILED(rv)) {
1273 Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE"));
1274 return;
1278 * Display picture capture UI here before calling Snapshot() - Bug 748835.
1280 nsCOMPtr<nsIDOMFile> file;
1281 aSource->Snapshot(aDuration, getter_AddRefs(file));
1282 aSource->Deallocate();
1284 NS_DispatchToMainThread(new SuccessCallbackRunnable(
1285 mSuccess, mError, file, mWindowID
1288 MOZ_ASSERT(!mSuccess);
1289 MOZ_ASSERT(!mError);
1291 return;
1294 private:
1295 MediaStreamConstraints mConstraints;
1297 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
1298 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
1299 uint64_t mWindowID;
1300 nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
1301 nsRefPtr<AudioDevice> mAudioDevice;
1302 nsRefPtr<VideoDevice> mVideoDevice;
1303 MediaEnginePrefs mPrefs;
1305 bool mDeviceChosen;
1307 RefPtr<MediaEngine> mBackend;
1308 nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
1311 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
1312 class GetUserMediaRunnableWrapper : public nsRunnable
1314 public:
1315 // This object must take ownership of task
1316 GetUserMediaRunnableWrapper(GetUserMediaTask* task) :
1317 mTask(task) {
1320 ~GetUserMediaRunnableWrapper() {
1323 NS_IMETHOD Run() {
1324 mTask->Run();
1325 return NS_OK;
1328 private:
1329 nsAutoPtr<GetUserMediaTask> mTask;
1331 #endif
1334 * Similar to GetUserMediaTask, but used for the chrome-only
1335 * GetUserMediaDevices function. Enumerates a list of audio & video devices,
1336 * wraps them up in nsIMediaDevice objects and returns it to the success
1337 * callback.
1339 class GetUserMediaDevicesTask : public Task
1341 public:
1342 GetUserMediaDevicesTask(
1343 const MediaStreamConstraints& aConstraints,
1344 already_AddRefed<nsIGetUserMediaDevicesSuccessCallback> aSuccess,
1345 already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
1346 uint64_t aWindowId, nsACString& aAudioLoopbackDev,
1347 nsACString& aVideoLoopbackDev)
1348 : mConstraints(aConstraints)
1349 , mSuccess(aSuccess)
1350 , mError(aError)
1351 , mManager(MediaManager::GetInstance())
1352 , mWindowId(aWindowId)
1353 , mLoopbackAudioDevice(aAudioLoopbackDev)
1354 , mLoopbackVideoDevice(aVideoLoopbackDev) {}
1356 void // NS_IMETHOD
1357 Run()
1359 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1361 nsRefPtr<MediaEngine> backend;
1362 if (mConstraints.mFake)
1363 backend = new MediaEngineDefault();
1364 else
1365 backend = mManager->GetBackend(mWindowId);
1367 ScopedDeletePtr<SourceSet> final(new SourceSet);
1368 if (IsOn(mConstraints.mVideo)) {
1369 VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo));
1370 ScopedDeletePtr<SourceSet> s(GetSources(backend, constraints,
1371 &MediaEngine::EnumerateVideoDevices,
1372 mLoopbackVideoDevice.get()));
1373 final->MoveElementsFrom(*s);
1375 if (IsOn(mConstraints.mAudio)) {
1376 AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio));
1377 ScopedDeletePtr<SourceSet> s (GetSources(backend, constraints,
1378 &MediaEngine::EnumerateAudioDevices,
1379 mLoopbackAudioDevice.get()));
1380 final->MoveElementsFrom(*s);
1383 NS_DispatchToMainThread(new DeviceSuccessCallbackRunnable(mWindowId,
1384 mSuccess, mError,
1385 final.forget()));
1386 // DeviceSuccessCallbackRunnable should have taken these.
1387 MOZ_ASSERT(!mSuccess && !mError);
1390 private:
1391 MediaStreamConstraints mConstraints;
1392 nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mSuccess;
1393 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
1394 nsRefPtr<MediaManager> mManager;
1395 uint64_t mWindowId;
1396 const nsString mCallId;
1397 // Audio & Video loopback devices to be used based on
1398 // the preference settings. This is currently used for
1399 // automated media tests only.
1400 nsCString mLoopbackAudioDevice;
1401 nsCString mLoopbackVideoDevice;
1404 MediaManager::MediaManager()
1405 : mMediaThread(nullptr)
1406 , mMutex("mozilla::MediaManager")
1407 , mBackend(nullptr) {
1408 mPrefs.mWidth = 0; // adaptive default
1409 mPrefs.mHeight = 0; // adaptive default
1410 mPrefs.mFPS = MediaEngine::DEFAULT_VIDEO_FPS;
1411 mPrefs.mMinFPS = MediaEngine::DEFAULT_VIDEO_MIN_FPS;
1413 nsresult rv;
1414 nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
1415 if (NS_SUCCEEDED(rv)) {
1416 nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
1417 if (branch) {
1418 GetPrefs(branch, nullptr);
1421 LOG(("%s: default prefs: %dx%d @%dfps (min %d)", __FUNCTION__,
1422 mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS));
1425 NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIObserver)
1427 /* static */ StaticRefPtr<MediaManager> MediaManager::sSingleton;
1429 // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
1430 // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
1431 // from MediaManager thread.
1432 /* static */ MediaManager*
1433 MediaManager::Get() {
1434 if (!sSingleton) {
1435 NS_ASSERTION(NS_IsMainThread(), "Only create MediaManager on main thread");
1437 sSingleton = new MediaManager();
1439 sSingleton->mMediaThread = new base::Thread("MediaManager");
1440 base::Thread::Options options;
1441 #if defined(_WIN32)
1442 options.message_loop_type = MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD;
1443 #else
1444 options.message_loop_type = MessageLoop::TYPE_MOZILLA_NONMAINTHREAD;
1445 #endif
1446 if (!sSingleton->mMediaThread->StartWithOptions(options)) {
1447 MOZ_CRASH();
1450 LOG(("New Media thread for gum"));
1452 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1453 if (obs) {
1454 obs->AddObserver(sSingleton, "xpcom-shutdown", false);
1455 obs->AddObserver(sSingleton, "getUserMedia:response:allow", false);
1456 obs->AddObserver(sSingleton, "getUserMedia:response:deny", false);
1457 obs->AddObserver(sSingleton, "getUserMedia:revoke", false);
1458 obs->AddObserver(sSingleton, "phone-state-changed", false);
1460 // else MediaManager won't work properly and will leak (see bug 837874)
1461 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
1462 if (prefs) {
1463 prefs->AddObserver("media.navigator.video.default_width", sSingleton, false);
1464 prefs->AddObserver("media.navigator.video.default_height", sSingleton, false);
1465 prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false);
1466 prefs->AddObserver("media.navigator.video.default_minfps", sSingleton, false);
1469 return sSingleton;
1472 /* static */ already_AddRefed<MediaManager>
1473 MediaManager::GetInstance()
1475 // so we can have non-refcounted getters
1476 nsRefPtr<MediaManager> service = MediaManager::Get();
1477 return service.forget();
1480 /* static */
1481 MessageLoop*
1482 MediaManager::GetMessageLoop()
1484 NS_ASSERTION(Get(), "MediaManager singleton?");
1485 NS_ASSERTION(Get()->mMediaThread, "No thread yet");
1486 return Get()->mMediaThread->message_loop();
1489 /* static */ nsresult
1490 MediaManager::NotifyRecordingStatusChange(nsPIDOMWindow* aWindow,
1491 const nsString& aMsg,
1492 const bool& aIsAudio,
1493 const bool& aIsVideo)
1495 NS_ENSURE_ARG(aWindow);
1497 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1498 if (!obs) {
1499 NS_WARNING("Could not get the Observer service for GetUserMedia recording notification.");
1500 return NS_ERROR_FAILURE;
1503 nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
1504 props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
1505 props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
1507 bool isApp = false;
1508 nsString requestURL;
1510 if (nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell()) {
1511 nsresult rv = docShell->GetIsApp(&isApp);
1512 NS_ENSURE_SUCCESS(rv, rv);
1514 if (isApp) {
1515 rv = docShell->GetAppManifestURL(requestURL);
1516 NS_ENSURE_SUCCESS(rv, rv);
1520 if (!isApp) {
1521 nsCString pageURL;
1522 nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
1523 NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE);
1525 nsresult rv = docURI->GetSpec(pageURL);
1526 NS_ENSURE_SUCCESS(rv, rv);
1528 requestURL = NS_ConvertUTF8toUTF16(pageURL);
1531 props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp);
1532 props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
1534 obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
1535 "recording-device-events",
1536 aMsg.get());
1538 // Forward recording events to parent process.
1539 // The events are gathered in chrome process and used for recording indicator
1540 if (XRE_GetProcessType() != GeckoProcessType_Default) {
1541 unused <<
1542 dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(aMsg,
1543 requestURL,
1544 aIsAudio,
1545 aIsVideo);
1548 return NS_OK;
1552 * The entry point for this file. A call from Navigator::mozGetUserMedia
1553 * will end up here. MediaManager is a singleton that is responsible
1554 * for handling all incoming getUserMedia calls from every window.
1556 nsresult
1557 MediaManager::GetUserMedia(bool aPrivileged,
1558 nsPIDOMWindow* aWindow, const MediaStreamConstraints& aConstraints,
1559 nsIDOMGetUserMediaSuccessCallback* aOnSuccess,
1560 nsIDOMGetUserMediaErrorCallback* aOnError)
1562 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1564 NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER);
1565 NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER);
1566 NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER);
1568 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess);
1569 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError);
1571 MediaStreamConstraints c(aConstraints); // copy
1574 * If we were asked to get a picture, before getting a snapshot, we check if
1575 * the calling page is allowed to open a popup. We do this because
1576 * {picture:true} will open a new "window" to let the user preview or select
1577 * an image, on Android. The desktop UI for {picture:true} is TBD, at which
1578 * may point we can decide whether to extend this test there as well.
1580 #if !defined(MOZ_WEBRTC)
1581 if (c.mPicture && !aPrivileged) {
1582 if (aWindow->GetPopupControlState() > openControlled) {
1583 nsCOMPtr<nsIPopupWindowManager> pm =
1584 do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID);
1585 if (!pm) {
1586 return NS_OK;
1588 uint32_t permission;
1589 nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
1590 if (doc) {
1591 pm->TestPermission(doc->NodePrincipal(), &permission);
1592 if (permission == nsIPopupWindowManager::DENY_POPUP) {
1593 aWindow->FirePopupBlockedEvent(doc, nullptr, EmptyString(),
1594 EmptyString());
1595 return NS_OK;
1600 #endif
1602 static bool created = false;
1603 if (!created) {
1604 // Force MediaManager to startup before we try to access it from other threads
1605 // Hack: should init singleton earlier unless it's expensive (mem or CPU)
1606 (void) MediaManager::Get();
1607 #ifdef MOZ_B2G
1608 // Initialize MediaPermissionManager before send out any permission request.
1609 (void) MediaPermissionManager::GetInstance();
1610 #endif //MOZ_B2G
1613 // Store the WindowID in a hash table and mark as active. The entry is removed
1614 // when this window is closed or navigated away from.
1615 uint64_t windowID = aWindow->WindowID();
1616 // This is safe since we're on main-thread, and the windowlist can only
1617 // be invalidated from the main-thread (see OnNavigation)
1618 StreamListeners* listeners = GetActiveWindows()->Get(windowID);
1619 if (!listeners) {
1620 listeners = new StreamListeners;
1621 GetActiveWindows()->Put(windowID, listeners);
1624 // Create a disabled listener to act as a placeholder
1625 GetUserMediaCallbackMediaStreamListener* listener =
1626 new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowID);
1628 // No need for locking because we always do this in the main thread.
1629 listeners->AppendElement(listener);
1631 // Developer preference for turning off permission check.
1632 if (Preferences::GetBool("media.navigator.permission.disabled", false)) {
1633 aPrivileged = true;
1635 if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
1636 c.mVideo.SetAsBoolean() = false;
1639 #if defined(ANDROID) || defined(MOZ_WIDGET_GONK)
1640 // Be backwards compatible only on mobile and only for facingMode.
1641 if (c.mVideo.IsMediaTrackConstraints()) {
1642 auto& tc = c.mVideo.GetAsMediaTrackConstraints();
1643 if (!tc.mRequire.WasPassed() &&
1644 tc.mMandatory.mFacingMode.WasPassed() && !tc.mFacingMode.WasPassed()) {
1645 tc.mFacingMode.Construct(tc.mMandatory.mFacingMode.Value());
1646 tc.mRequire.Construct().AppendElement(NS_LITERAL_STRING("facingMode"));
1648 if (tc.mOptional.WasPassed() && !tc.mAdvanced.WasPassed()) {
1649 tc.mAdvanced.Construct();
1650 for (uint32_t i = 0; i < tc.mOptional.Value().Length(); i++) {
1651 if (tc.mOptional.Value()[i].mFacingMode.WasPassed()) {
1652 MediaTrackConstraintSet n;
1653 n.mFacingMode.Construct(tc.mOptional.Value()[i].mFacingMode.Value());
1654 tc.mAdvanced.Value().AppendElement(n);
1659 #endif
1661 if (c.mVideo.IsMediaTrackConstraints() && !aPrivileged) {
1662 auto& tc = c.mVideo.GetAsMediaTrackConstraints();
1663 // only allow privileged content to set the window id
1664 if (tc.mBrowserWindow.WasPassed()) {
1665 tc.mBrowserWindow.Construct(-1);
1668 if (tc.mAdvanced.WasPassed()) {
1669 uint32_t length = tc.mAdvanced.Value().Length();
1670 for (uint32_t i = 0; i < length; i++) {
1671 if (tc.mAdvanced.Value()[i].mBrowserWindow.WasPassed()) {
1672 tc.mAdvanced.Value()[i].mBrowserWindow.Construct(-1);
1678 // Pass callbacks and MediaStreamListener along to GetUserMediaTask.
1679 nsAutoPtr<GetUserMediaTask> task;
1680 if (c.mFake) {
1681 // Fake stream from default backend.
1682 task = new GetUserMediaTask(c, onSuccess.forget(),
1683 onError.forget(), windowID, listener, mPrefs, new MediaEngineDefault());
1684 } else {
1685 // Stream from default device from WebRTC backend.
1686 task = new GetUserMediaTask(c, onSuccess.forget(),
1687 onError.forget(), windowID, listener, mPrefs);
1690 nsIURI* docURI = aWindow->GetDocumentURI();
1692 if (c.mVideo.IsMediaTrackConstraints()) {
1693 auto& tc = c.mVideo.GetAsMediaTrackConstraints();
1694 // deny screensharing request if support is disabled
1695 if (tc.mMediaSource != dom::MediaSourceEnum::Camera) {
1696 if (tc.mMediaSource == dom::MediaSourceEnum::Browser) {
1697 if (!Preferences::GetBool("media.getusermedia.browser.enabled", false)) {
1698 return task->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
1700 } else if (!Preferences::GetBool("media.getusermedia.screensharing.enabled", false)) {
1701 return task->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
1703 /* Deny screensharing if the requesting document is not from a host
1704 on the whitelist. */
1705 // Block screen/window sharing on Mac OSX 10.6 and WinXP until proved that they work
1706 if (
1707 #if defined(XP_MACOSX) || defined(XP_WIN)
1709 !Preferences::GetBool("media.getusermedia.screensharing.allow_on_old_platforms", false) &&
1710 #if defined(XP_MACOSX)
1711 !nsCocoaFeatures::OnLionOrLater()
1712 #endif
1713 #if defined (XP_WIN)
1714 !IsVistaOrLater()
1715 #endif
1716 ) ||
1717 #endif
1718 (!aPrivileged && !HostHasPermission(*docURI))) {
1719 return task->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
1724 #ifdef MOZ_B2G_CAMERA
1725 if (mCameraManager == nullptr) {
1726 mCameraManager = nsDOMCameraManager::CreateInstance(aWindow);
1728 #endif
1730 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
1731 if (c.mPicture) {
1732 // ShowFilePickerForMimeType() must run on the Main Thread! (on Android)
1733 // Note, GetUserMediaRunnableWrapper takes ownership of task.
1734 NS_DispatchToMainThread(new GetUserMediaRunnableWrapper(task.forget()));
1735 return NS_OK;
1737 #endif
1739 bool isLoop = false;
1740 nsCOMPtr<nsIURI> loopURI;
1741 nsresult rv = NS_NewURI(getter_AddRefs(loopURI), "about:loopconversation");
1742 NS_ENSURE_SUCCESS(rv, rv);
1743 rv = docURI->EqualsExceptRef(loopURI, &isLoop);
1744 NS_ENSURE_SUCCESS(rv, rv);
1746 if (isLoop) {
1747 aPrivileged = true;
1750 // XXX No full support for picture in Desktop yet (needs proper UI)
1751 if (aPrivileged ||
1752 (c.mFake && !Preferences::GetBool("media.navigator.permission.fake"))) {
1753 MediaManager::GetMessageLoop()->PostTask(FROM_HERE, task.forget());
1754 } else {
1755 bool isHTTPS = false;
1756 if (docURI) {
1757 docURI->SchemeIs("https", &isHTTPS);
1760 // Check if this site has persistent permissions.
1761 nsresult rv;
1762 nsCOMPtr<nsIPermissionManager> permManager =
1763 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
1764 NS_ENSURE_SUCCESS(rv, rv);
1766 uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
1767 if (IsOn(c.mAudio)) {
1768 rv = permManager->TestExactPermissionFromPrincipal(
1769 aWindow->GetExtantDoc()->NodePrincipal(), "microphone", &audioPerm);
1770 NS_ENSURE_SUCCESS(rv, rv);
1773 uint32_t videoPerm = nsIPermissionManager::UNKNOWN_ACTION;
1774 if (IsOn(c.mVideo)) {
1775 rv = permManager->TestExactPermissionFromPrincipal(
1776 aWindow->GetExtantDoc()->NodePrincipal(), "camera", &videoPerm);
1777 NS_ENSURE_SUCCESS(rv, rv);
1780 if ((!IsOn(c.mAudio) || audioPerm == nsIPermissionManager::DENY_ACTION) &&
1781 (!IsOn(c.mVideo) || videoPerm == nsIPermissionManager::DENY_ACTION)) {
1782 return task->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
1785 // Ask for user permission, and dispatch task (or not) when a response
1786 // is received via an observer notification. Each call is paired with its
1787 // task by a GUID.
1788 nsCOMPtr<nsIUUIDGenerator> uuidgen =
1789 do_GetService("@mozilla.org/uuid-generator;1", &rv);
1790 NS_ENSURE_SUCCESS(rv, rv);
1792 // Generate a call ID.
1793 nsID id;
1794 rv = uuidgen->GenerateUUIDInPlace(&id);
1795 NS_ENSURE_SUCCESS(rv, rv);
1797 char buffer[NSID_LENGTH];
1798 id.ToProvidedString(buffer);
1799 NS_ConvertUTF8toUTF16 callID(buffer);
1801 // Store the current unarmed task w/callbacks.
1802 mActiveCallbacks.Put(callID, task.forget());
1804 // Add a WindowID cross-reference so OnNavigation can tear things down
1805 nsTArray<nsString>* array;
1806 if (!mCallIds.Get(windowID, &array)) {
1807 array = new nsTArray<nsString>();
1808 array->AppendElement(callID);
1809 mCallIds.Put(windowID, array);
1810 } else {
1811 array->AppendElement(callID);
1813 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1814 nsRefPtr<GetUserMediaRequest> req = new GetUserMediaRequest(aWindow,
1815 callID, c, isHTTPS);
1816 obs->NotifyObservers(req, "getUserMedia:request", nullptr);
1819 #ifdef MOZ_WEBRTC
1820 EnableWebRtcLog();
1821 #endif
1823 return NS_OK;
1826 nsresult
1827 MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow,
1828 const MediaStreamConstraints& aConstraints,
1829 nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
1830 nsIDOMGetUserMediaErrorCallback* aOnError,
1831 uint64_t aInnerWindowID)
1833 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1835 NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER);
1836 NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER);
1838 nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
1839 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError);
1841 // Check if the preference for using loopback devices is enabled.
1842 nsAdoptingCString loopbackAudioDevice =
1843 Preferences::GetCString("media.audio_loopback_dev");
1844 nsAdoptingCString loopbackVideoDevice =
1845 Preferences::GetCString("media.video_loopback_dev");
1847 MediaManager::GetMessageLoop()->PostTask(FROM_HERE,
1848 new GetUserMediaDevicesTask(
1849 aConstraints, onSuccess.forget(), onError.forget(),
1850 (aInnerWindowID ? aInnerWindowID : aWindow->WindowID()),
1851 loopbackAudioDevice, loopbackVideoDevice));
1853 return NS_OK;
1856 MediaEngine*
1857 MediaManager::GetBackend(uint64_t aWindowId)
1859 // Plugin backends as appropriate. The default engine also currently
1860 // includes picture support for Android.
1861 // This IS called off main-thread.
1862 MutexAutoLock lock(mMutex);
1863 if (!mBackend) {
1864 #if defined(MOZ_WEBRTC)
1865 mBackend = new MediaEngineWebRTC(mPrefs);
1866 #else
1867 mBackend = new MediaEngineDefault();
1868 #endif
1870 return mBackend;
1873 static void
1874 StopSharingCallback(MediaManager *aThis,
1875 uint64_t aWindowID,
1876 StreamListeners *aListeners,
1877 void *aData)
1879 if (aListeners) {
1880 auto length = aListeners->Length();
1881 for (size_t i = 0; i < length; ++i) {
1882 GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
1884 if (listener->Stream()) { // aka HasBeenActivate()ed
1885 listener->Invalidate();
1887 listener->Remove();
1888 listener->StopScreenWindowSharing();
1890 aListeners->Clear();
1891 aThis->RemoveWindowID(aWindowID);
1896 void
1897 MediaManager::OnNavigation(uint64_t aWindowID)
1899 NS_ASSERTION(NS_IsMainThread(), "OnNavigation called off main thread");
1900 LOG(("OnNavigation for %llu", aWindowID));
1902 // Invalidate this window. The runnables check this value before making
1903 // a call to content.
1905 nsTArray<nsString>* callIds;
1906 if (mCallIds.Get(aWindowID, &callIds)) {
1907 for (int i = 0, len = callIds->Length(); i < len; ++i) {
1908 mActiveCallbacks.Remove((*callIds)[i]);
1910 mCallIds.Remove(aWindowID);
1913 // This is safe since we're on main-thread, and the windowlist can only
1914 // be added to from the main-thread
1915 nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
1916 (nsGlobalWindow::GetInnerWindowWithId(aWindowID));
1917 if (window) {
1918 IterateWindowListeners(window, StopSharingCallback, nullptr);
1919 } else {
1920 RemoveWindowID(aWindowID);
1924 void
1925 MediaManager::RemoveWindowID(uint64_t aWindowId)
1927 mActiveWindows.Remove(aWindowId);
1929 // get outer windowID
1930 nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
1931 (nsGlobalWindow::GetInnerWindowWithId(aWindowId));
1932 if (!window) {
1933 LOG(("No inner window for %llu", aWindowId));
1934 return;
1937 nsPIDOMWindow *outer = window->GetOuterWindow();
1938 if (!outer) {
1939 LOG(("No outer window for inner %llu", aWindowId));
1940 return;
1943 uint64_t outerID = outer->WindowID();
1945 // Notify the UI that this window no longer has gUM active
1946 char windowBuffer[32];
1947 PR_snprintf(windowBuffer, sizeof(windowBuffer), "%llu", outerID);
1948 nsString data = NS_ConvertUTF8toUTF16(windowBuffer);
1950 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1951 obs->NotifyObservers(nullptr, "recording-window-ended", data.get());
1952 LOG(("Sent recording-window-ended for window %llu (outer %llu)",
1953 aWindowId, outerID));
1956 void
1957 MediaManager::RemoveFromWindowList(uint64_t aWindowID,
1958 GetUserMediaCallbackMediaStreamListener *aListener)
1960 NS_ASSERTION(NS_IsMainThread(), "RemoveFromWindowList called off main thread");
1962 // This is defined as safe on an inactive GUMCMSListener
1963 aListener->Remove(); // really queues the remove
1965 StreamListeners* listeners = GetWindowListeners(aWindowID);
1966 if (!listeners) {
1967 return;
1969 listeners->RemoveElement(aListener);
1970 if (listeners->Length() == 0) {
1971 RemoveWindowID(aWindowID);
1972 // listeners has been deleted here
1976 void
1977 MediaManager::GetPref(nsIPrefBranch *aBranch, const char *aPref,
1978 const char *aData, int32_t *aVal)
1980 int32_t temp;
1981 if (aData == nullptr || strcmp(aPref,aData) == 0) {
1982 if (NS_SUCCEEDED(aBranch->GetIntPref(aPref, &temp))) {
1983 *aVal = temp;
1988 void
1989 MediaManager::GetPrefBool(nsIPrefBranch *aBranch, const char *aPref,
1990 const char *aData, bool *aVal)
1992 bool temp;
1993 if (aData == nullptr || strcmp(aPref,aData) == 0) {
1994 if (NS_SUCCEEDED(aBranch->GetBoolPref(aPref, &temp))) {
1995 *aVal = temp;
2000 void
2001 MediaManager::GetPrefs(nsIPrefBranch *aBranch, const char *aData)
2003 GetPref(aBranch, "media.navigator.video.default_width", aData, &mPrefs.mWidth);
2004 GetPref(aBranch, "media.navigator.video.default_height", aData, &mPrefs.mHeight);
2005 GetPref(aBranch, "media.navigator.video.default_fps", aData, &mPrefs.mFPS);
2006 GetPref(aBranch, "media.navigator.video.default_minfps", aData, &mPrefs.mMinFPS);
2009 nsresult
2010 MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
2011 const char16_t* aData)
2013 NS_ASSERTION(NS_IsMainThread(), "Observer invoked off the main thread");
2014 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
2016 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
2017 nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) );
2018 if (branch) {
2019 GetPrefs(branch,NS_ConvertUTF16toUTF8(aData).get());
2020 LOG(("%s: %dx%d @%dfps (min %d)", __FUNCTION__,
2021 mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS));
2023 } else if (!strcmp(aTopic, "xpcom-shutdown")) {
2024 obs->RemoveObserver(this, "xpcom-shutdown");
2025 obs->RemoveObserver(this, "getUserMedia:response:allow");
2026 obs->RemoveObserver(this, "getUserMedia:response:deny");
2027 obs->RemoveObserver(this, "getUserMedia:revoke");
2029 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
2030 if (prefs) {
2031 prefs->RemoveObserver("media.navigator.video.default_width", this);
2032 prefs->RemoveObserver("media.navigator.video.default_height", this);
2033 prefs->RemoveObserver("media.navigator.video.default_fps", this);
2034 prefs->RemoveObserver("media.navigator.video.default_minfps", this);
2037 // Close off any remaining active windows.
2039 MutexAutoLock lock(mMutex);
2040 GetActiveWindows()->Clear();
2041 mActiveCallbacks.Clear();
2042 mCallIds.Clear();
2043 LOG(("Releasing MediaManager singleton and thread"));
2044 // Note: won't be released immediately as the Observer has a ref to us
2045 sSingleton = nullptr;
2046 if (mMediaThread) {
2047 mMediaThread->Stop();
2049 mBackend = nullptr;
2052 return NS_OK;
2054 } else if (!strcmp(aTopic, "getUserMedia:response:allow")) {
2055 nsString key(aData);
2056 nsAutoPtr<GetUserMediaTask> task;
2057 mActiveCallbacks.RemoveAndForget(key, task);
2058 if (!task) {
2059 return NS_OK;
2062 if (aSubject) {
2063 // A particular device or devices were chosen by the user.
2064 // NOTE: does not allow setting a device to null; assumes nullptr
2065 nsCOMPtr<nsISupportsArray> array(do_QueryInterface(aSubject));
2066 MOZ_ASSERT(array);
2067 uint32_t len = 0;
2068 array->Count(&len);
2069 MOZ_ASSERT(len);
2070 if (!len) {
2071 // neither audio nor video were selected
2072 task->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
2073 return NS_OK;
2075 for (uint32_t i = 0; i < len; i++) {
2076 nsCOMPtr<nsISupports> supports;
2077 array->GetElementAt(i,getter_AddRefs(supports));
2078 nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supports));
2079 MOZ_ASSERT(device); // shouldn't be returning anything else...
2080 if (device) {
2081 nsString type;
2082 device->GetType(type);
2083 if (type.EqualsLiteral("video")) {
2084 task->SetVideoDevice(static_cast<VideoDevice*>(device.get()));
2085 } else if (type.EqualsLiteral("audio")) {
2086 task->SetAudioDevice(static_cast<AudioDevice*>(device.get()));
2087 } else {
2088 NS_WARNING("Unknown device type in getUserMedia");
2094 // Reuse the same thread to save memory.
2095 MediaManager::GetMessageLoop()->PostTask(FROM_HERE, task.forget());
2096 return NS_OK;
2098 } else if (!strcmp(aTopic, "getUserMedia:response:deny")) {
2099 nsString errorMessage(NS_LITERAL_STRING("PERMISSION_DENIED"));
2101 if (aSubject) {
2102 nsCOMPtr<nsISupportsString> msg(do_QueryInterface(aSubject));
2103 MOZ_ASSERT(msg);
2104 msg->GetData(errorMessage);
2105 if (errorMessage.IsEmpty())
2106 errorMessage.AssignLiteral(MOZ_UTF16("UNKNOWN_ERROR"));
2109 nsString key(aData);
2110 nsAutoPtr<GetUserMediaTask> task;
2111 mActiveCallbacks.RemoveAndForget(key, task);
2112 if (task) {
2113 task->Denied(errorMessage);
2115 return NS_OK;
2117 } else if (!strcmp(aTopic, "getUserMedia:revoke")) {
2118 nsresult rv;
2119 // may be windowid or screen:windowid
2120 nsDependentString data(aData);
2121 if (Substring(data, 0, strlen("screen:")).EqualsLiteral("screen:")) {
2122 uint64_t windowID = PromiseFlatString(Substring(data, strlen("screen:"))).ToInteger64(&rv);
2123 MOZ_ASSERT(NS_SUCCEEDED(rv));
2124 if (NS_SUCCEEDED(rv)) {
2125 LOG(("Revoking Screeen/windowCapture access for window %llu", windowID));
2126 StopScreensharing(windowID);
2128 } else {
2129 uint64_t windowID = nsString(aData).ToInteger64(&rv);
2130 MOZ_ASSERT(NS_SUCCEEDED(rv));
2131 if (NS_SUCCEEDED(rv)) {
2132 LOG(("Revoking MediaCapture access for window %llu", windowID));
2133 OnNavigation(windowID);
2136 return NS_OK;
2138 #ifdef MOZ_WIDGET_GONK
2139 else if (!strcmp(aTopic, "phone-state-changed")) {
2140 nsString state(aData);
2141 nsresult rv;
2142 uint32_t phoneState = state.ToInteger(&rv);
2144 if (NS_SUCCEEDED(rv) && phoneState == nsIAudioManager::PHONE_STATE_IN_CALL) {
2145 StopMediaStreams();
2147 return NS_OK;
2149 #endif
2151 return NS_OK;
2154 static PLDHashOperator
2155 WindowsHashToArrayFunc (const uint64_t& aId,
2156 StreamListeners* aData,
2157 void *userArg)
2159 nsISupportsArray *array =
2160 static_cast<nsISupportsArray *>(userArg);
2161 nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
2162 (nsGlobalWindow::GetInnerWindowWithId(aId));
2164 MOZ_ASSERT(window);
2165 if (window) {
2166 // mActiveWindows contains both windows that have requested device
2167 // access and windows that are currently capturing media. We want
2168 // to return only the latter. See bug 975177.
2169 bool capturing = false;
2170 if (aData) {
2171 uint32_t length = aData->Length();
2172 for (uint32_t i = 0; i < length; ++i) {
2173 nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener =
2174 aData->ElementAt(i);
2175 if (listener->CapturingVideo() || listener->CapturingAudio() ||
2176 listener->CapturingScreen() || listener->CapturingWindow() ||
2177 listener->CapturingApplication()) {
2178 capturing = true;
2179 break;
2184 if (capturing)
2185 array->AppendElement(window);
2187 return PL_DHASH_NEXT;
2191 nsresult
2192 MediaManager::GetActiveMediaCaptureWindows(nsISupportsArray **aArray)
2194 MOZ_ASSERT(aArray);
2195 nsISupportsArray *array;
2196 nsresult rv = NS_NewISupportsArray(&array); // AddRefs
2197 if (NS_FAILED(rv))
2198 return rv;
2200 mActiveWindows.EnumerateRead(WindowsHashToArrayFunc, array);
2202 *aArray = array;
2203 return NS_OK;
2206 // XXX flags might be better...
2207 struct CaptureWindowStateData {
2208 bool *mVideo;
2209 bool *mAudio;
2210 bool *mScreenShare;
2211 bool *mWindowShare;
2212 bool *mAppShare;
2215 static void
2216 CaptureWindowStateCallback(MediaManager *aThis,
2217 uint64_t aWindowID,
2218 StreamListeners *aListeners,
2219 void *aData)
2221 struct CaptureWindowStateData *data = (struct CaptureWindowStateData *) aData;
2223 if (aListeners) {
2224 auto length = aListeners->Length();
2225 for (size_t i = 0; i < length; ++i) {
2226 GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
2228 if (listener->CapturingVideo()) {
2229 *data->mVideo = true;
2231 if (listener->CapturingAudio()) {
2232 *data->mAudio = true;
2234 if (listener->CapturingScreen()) {
2235 *data->mScreenShare = true;
2237 if (listener->CapturingWindow()) {
2238 *data->mWindowShare = true;
2240 if (listener->CapturingApplication()) {
2241 *data->mAppShare = true;
2248 NS_IMETHODIMP
2249 MediaManager::MediaCaptureWindowState(nsIDOMWindow* aWindow, bool* aVideo,
2250 bool* aAudio, bool *aScreenShare,
2251 bool* aWindowShare, bool *aAppShare)
2253 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
2254 struct CaptureWindowStateData data;
2255 data.mVideo = aVideo;
2256 data.mAudio = aAudio;
2257 data.mScreenShare = aScreenShare;
2258 data.mWindowShare = aWindowShare;
2259 data.mAppShare = aAppShare;
2261 *aVideo = false;
2262 *aAudio = false;
2263 *aScreenShare = false;
2264 *aWindowShare = false;
2265 *aAppShare = false;
2267 nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
2268 if (piWin) {
2269 IterateWindowListeners(piWin, CaptureWindowStateCallback, &data);
2271 #ifdef DEBUG
2272 LOG(("%s: window %lld capturing %s %s %s %s %s", __FUNCTION__, piWin ? piWin->WindowID() : -1,
2273 *aVideo ? "video" : "", *aAudio ? "audio" : "",
2274 *aScreenShare ? "screenshare" : "", *aWindowShare ? "windowshare" : "",
2275 *aAppShare ? "appshare" : ""));
2276 #endif
2277 return NS_OK;
2280 static void
2281 StopScreensharingCallback(MediaManager *aThis,
2282 uint64_t aWindowID,
2283 StreamListeners *aListeners,
2284 void *aData)
2286 if (aListeners) {
2287 auto length = aListeners->Length();
2288 for (size_t i = 0; i < length; ++i) {
2289 aListeners->ElementAt(i)->StopScreenWindowSharing();
2294 void
2295 MediaManager::StopScreensharing(uint64_t aWindowID)
2297 // We need to stop window/screensharing for all streams in all innerwindows that
2298 // correspond to that outerwindow.
2300 nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
2301 (nsGlobalWindow::GetInnerWindowWithId(aWindowID));
2302 if (!window) {
2303 return;
2305 IterateWindowListeners(window, &StopScreensharingCallback, nullptr);
2308 // lets us do all sorts of things to the listeners
2309 void
2310 MediaManager::IterateWindowListeners(nsPIDOMWindow *aWindow,
2311 WindowListenerCallback aCallback,
2312 void *aData)
2314 // Iterate the docshell tree to find all the child windows, and for each
2315 // invoke the callback
2316 nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
2317 if (piWin) {
2318 if (piWin->IsInnerWindow() || piWin->GetCurrentInnerWindow()) {
2319 uint64_t windowID;
2320 if (piWin->IsInnerWindow()) {
2321 windowID = piWin->WindowID();
2322 } else {
2323 windowID = piWin->GetCurrentInnerWindow()->WindowID();
2325 StreamListeners* listeners = GetActiveWindows()->Get(windowID);
2326 // pass listeners so it can modify/delete the list
2327 (*aCallback)(this, windowID, listeners, aData);
2330 // iterate any children of *this* window (iframes, etc)
2331 nsCOMPtr<nsIDocShell> docShell = piWin->GetDocShell();
2332 if (docShell) {
2333 int32_t i, count;
2334 docShell->GetChildCount(&count);
2335 for (i = 0; i < count; ++i) {
2336 nsCOMPtr<nsIDocShellTreeItem> item;
2337 docShell->GetChildAt(i, getter_AddRefs(item));
2338 nsCOMPtr<nsPIDOMWindow> win = item ? item->GetWindow() : nullptr;
2340 if (win) {
2341 IterateWindowListeners(win, aCallback, aData);
2349 void
2350 MediaManager::StopMediaStreams()
2352 nsCOMPtr<nsISupportsArray> array;
2353 GetActiveMediaCaptureWindows(getter_AddRefs(array));
2354 uint32_t len;
2355 array->Count(&len);
2356 for (uint32_t i = 0; i < len; i++) {
2357 nsCOMPtr<nsISupports> window;
2358 array->GetElementAt(i, getter_AddRefs(window));
2359 nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(window));
2360 if (win) {
2361 OnNavigation(win->WindowID());
2366 void
2367 GetUserMediaCallbackMediaStreamListener::AudioConfig(bool aEchoOn,
2368 uint32_t aEcho,
2369 bool aAgcOn, uint32_t aAGC,
2370 bool aNoiseOn, uint32_t aNoise,
2371 int32_t aPlayoutDelay)
2373 if (mAudioSource) {
2374 #ifdef MOZ_WEBRTC
2375 mMediaThread->message_loop()->PostTask(FROM_HERE,
2376 NewRunnableMethod(mAudioSource.get(), &MediaEngineSource::Config,
2377 aEchoOn, aEcho, aAgcOn, aAGC, aNoiseOn,
2378 aNoise, aPlayoutDelay));
2379 #endif
2383 // Can be invoked from EITHER MainThread or MSG thread
2384 void
2385 GetUserMediaCallbackMediaStreamListener::Invalidate()
2387 // We can't take a chance on blocking here, so proxy this to another
2388 // thread.
2389 // Pass a ref to us (which is threadsafe) so it can query us for the
2390 // source stream info.
2391 MediaManager::GetMessageLoop()->PostTask(FROM_HERE,
2392 new MediaOperationTask(MEDIA_STOP,
2393 this, nullptr, nullptr,
2394 mAudioSource, mVideoSource,
2395 mFinished, mWindowID, nullptr));
2398 // Doesn't kill audio
2399 // XXX refactor to combine with Invalidate()?
2400 void
2401 GetUserMediaCallbackMediaStreamListener::StopScreenWindowSharing()
2403 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
2404 if (mVideoSource && !mStopped &&
2405 (mVideoSource->GetMediaSource() == MediaSourceType::Screen ||
2406 mVideoSource->GetMediaSource() == MediaSourceType::Application ||
2407 mVideoSource->GetMediaSource() == MediaSourceType::Window)) {
2408 // Stop the whole stream if there's no audio; just the video track if we have both
2409 MediaManager::GetMessageLoop()->PostTask(FROM_HERE,
2410 new MediaOperationTask(mAudioSource ? MEDIA_STOP_TRACK : MEDIA_STOP,
2411 this, nullptr, nullptr,
2412 nullptr, mVideoSource,
2413 mFinished, mWindowID, nullptr));
2417 // Stop backend for track
2419 void
2420 GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aID, bool aIsAudio)
2422 if (((aIsAudio && mAudioSource) ||
2423 (!aIsAudio && mVideoSource)) && !mStopped)
2425 // XXX to support multiple tracks of a type in a stream, this should key off
2426 // the TrackID and not just the type
2427 MediaManager::GetMessageLoop()->PostTask(FROM_HERE,
2428 new MediaOperationTask(MEDIA_STOP_TRACK,
2429 this, nullptr, nullptr,
2430 aIsAudio ? mAudioSource : nullptr,
2431 !aIsAudio ? mVideoSource : nullptr,
2432 mFinished, mWindowID, nullptr));
2433 } else {
2434 LOG(("gUM track %d ended, but we don't have type %s",
2435 aID, aIsAudio ? "audio" : "video"));
2439 // Called from the MediaStreamGraph thread
2440 void
2441 GetUserMediaCallbackMediaStreamListener::NotifyFinished(MediaStreamGraph* aGraph)
2443 mFinished = true;
2444 Invalidate(); // we know it's been activated
2445 NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, this));
2448 // Called from the MediaStreamGraph thread
2449 void
2450 GetUserMediaCallbackMediaStreamListener::NotifyDirectListeners(MediaStreamGraph* aGraph,
2451 bool aHasListeners)
2453 MediaManager::GetMessageLoop()->PostTask(FROM_HERE,
2454 new MediaOperationTask(MEDIA_DIRECT_LISTENERS,
2455 this, nullptr, nullptr,
2456 mAudioSource, mVideoSource,
2457 aHasListeners, mWindowID, nullptr));
2460 // Called from the MediaStreamGraph thread
2461 // this can be in response to our own RemoveListener() (via ::Remove()), or
2462 // because the DOM GC'd the DOMLocalMediaStream/etc we're attached to.
2463 void
2464 GetUserMediaCallbackMediaStreamListener::NotifyRemoved(MediaStreamGraph* aGraph)
2467 MutexAutoLock lock(mLock); // protect access to mRemoved
2468 MM_LOG(("Listener removed by DOM Destroy(), mFinished = %d", (int) mFinished));
2469 mRemoved = true;
2471 if (!mFinished) {
2472 NotifyFinished(aGraph);
2476 NS_IMETHODIMP
2477 GetUserMediaNotificationEvent::Run()
2479 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
2480 // Make sure mStream is cleared and our reference to the DOMMediaStream
2481 // is dropped on the main thread, no matter what happens in this method.
2482 // Otherwise this object might be destroyed off the main thread,
2483 // releasing DOMMediaStream off the main thread, which is not allowed.
2484 nsRefPtr<DOMMediaStream> stream = mStream.forget();
2486 nsString msg;
2487 switch (mStatus) {
2488 case STARTING:
2489 msg = NS_LITERAL_STRING("starting");
2490 stream->OnTracksAvailable(mOnTracksAvailableCallback.forget());
2491 break;
2492 case STOPPING:
2493 msg = NS_LITERAL_STRING("shutdown");
2494 if (mListener) {
2495 mListener->SetStopped();
2497 break;
2498 case STOPPED_TRACK:
2499 msg = NS_LITERAL_STRING("shutdown");
2500 break;
2503 nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
2504 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
2506 return MediaManager::NotifyRecordingStatusChange(window, msg, mIsAudio, mIsVideo);
2509 } // namespace mozilla