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"
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"
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"
55 #include "MediaPermissionGonk.h"
58 #if defined(XP_MACOSX)
59 #include "nsCocoaFeatures.h"
62 #include "mozilla/WindowsVersion.h"
65 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
66 // GetTickCount() and conflicts with MediaStream::GetCurrentTime.
71 // XXX Workaround for bug 986974 to maintain the existing broken semantics
73 struct nsIMediaDevice::COMTypeInfo
<mozilla::VideoDevice
, void> {
74 static const nsIID kIID
;
76 const nsIID
nsIMediaDevice::COMTypeInfo
<mozilla::VideoDevice
, void>::kIID
= NS_IMEDIADEVICE_IID
;
78 struct nsIMediaDevice::COMTypeInfo
<mozilla::AudioDevice
, void> {
79 static const nsIID kIID
;
81 const nsIID
nsIMediaDevice::COMTypeInfo
<mozilla::AudioDevice
, void>::kIID
= NS_IMEDIADEVICE_IID
;
93 static PRLogModuleInfo
*sLog
;
95 sLog
= PR_NewLogModule("MediaManager");
98 #define LOG(msg) PR_LOG(GetMediaManagerLog(), PR_LOG_DEBUG, msg)
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
;
108 using dom::OwningBooleanOrMediaTrackConstraints
;
109 using dom::SupportedAudioConstraints
;
110 using dom::SupportedVideoConstraints
;
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] == '.') {
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.
131 nsDependentCString
hostRoot(aHost
, hostOffset
);
132 return hostRoot
.EqualsIgnoreCase(aPattern
.BeginReading() + patternOffset
);
136 HostHasPermission(nsIURI
&docURI
)
141 rv
= docURI
.SchemeIs("https",&isHttps
);
142 if (NS_WARN_IF(NS_FAILED(rv
))) {
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()) {
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
))) {
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.
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
),
183 if (NS_SUCCEEDED(rv
)) {
184 if (HostInDomain(hostName
, domainName
)) {
188 NS_WARNING("Failed to convert UTF-8 host to ASCII");
192 } while (end
< domainWhiteList
.Length());
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
);
209 ErrorCallbackRunnable::~ErrorCallbackRunnable()
211 MOZ_ASSERT(!mSuccess
&& !mError
);
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
))) {
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
);
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
241 SuccessCallbackRunnable(
242 nsCOMPtr
<nsIDOMGetUserMediaSuccessCallback
>& aSuccess
,
243 nsCOMPtr
<nsIDOMGetUserMediaErrorCallback
>& aError
,
244 nsIDOMFile
* aFile
, uint64_t aWindowID
)
246 , mWindowID(aWindowID
)
247 , mManager(MediaManager::GetInstance())
249 mSuccess
.swap(aSuccess
);
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
))) {
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
);
272 nsCOMPtr
<nsIDOMGetUserMediaSuccessCallback
> mSuccess
;
273 nsCOMPtr
<nsIDOMGetUserMediaErrorCallback
> mError
;
274 nsCOMPtr
<nsIDOMFile
> mFile
;
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
287 DeviceSuccessCallbackRunnable(
289 nsCOMPtr
<nsIGetUserMediaDevicesSuccessCallback
>& aSuccess
,
290 nsCOMPtr
<nsIDOMGetUserMediaErrorCallback
>& aError
,
291 nsTArray
<nsCOMPtr
<nsIMediaDevice
> >* aDevices
)
293 , mWindowID(aWindowID
)
294 , mManager(MediaManager::GetInstance())
296 mSuccess
.swap(aSuccess
);
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
)) {
310 nsCOMPtr
<nsIWritableVariant
> devices
=
311 do_CreateInstance("@mozilla.org/variant;1");
313 int32_t len
= mDevices
->Length();
316 // We should in the future return an empty array, and dynamically add
317 // devices to the dropdowns if things are hotplugged while the
319 mError
->OnError(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
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
),
332 static_cast<const void*>(tmp
.Elements())
335 mSuccess
->OnSuccess(devices
);
340 nsCOMPtr
<nsIGetUserMediaDevicesSuccessCallback
> mSuccess
;
341 nsCOMPtr
<nsIDOMGetUserMediaErrorCallback
> mError
;
342 nsAutoPtr
<nsTArray
<nsCOMPtr
<nsIMediaDevice
> > > mDevices
;
344 nsRefPtr
<MediaManager
> mManager
;
347 // Handle removing GetUserMediaCallbackMediaStreamListener from main thread
348 class GetUserMediaListenerRemove
: public nsRunnable
351 GetUserMediaListenerRemove(uint64_t aWindowID
,
352 GetUserMediaCallbackMediaStreamListener
*aListener
)
353 : mWindowID(aWindowID
)
354 , mListener(aListener
) {}
359 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
360 nsRefPtr
<MediaManager
> manager(MediaManager::GetInstance());
361 manager
->RemoveFromWindowList(mWindowID
, mListener
);
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)
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
;
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
;
422 mMediaSource
= aSource
->GetMediaSource();
425 AudioDevice::AudioDevice(MediaEngineAudioSource
* aSource
)
426 : MediaDevice(aSource
) {}
429 MediaDevice::GetName(nsAString
& aName
)
436 MediaDevice::GetType(nsAString
& aType
)
442 VideoDevice::GetType(nsAString
& aType
)
444 aType
.AssignLiteral(MOZ_UTF16("video"));
449 AudioDevice::GetType(nsAString
& aType
)
451 aType
.AssignLiteral(MOZ_UTF16("audio"));
456 MediaDevice::GetId(nsAString
& aID
)
463 MediaDevice::GetFacingMode(nsAString
& aFacingMode
)
465 if (mHasFacingMode
) {
466 aFacingMode
.Assign(NS_ConvertUTF8toUTF16(
467 dom::VideoFacingModeEnumValues::strings
[uint32_t(mFacingMode
)].value
));
469 aFacingMode
.Truncate(0);
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
));
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
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
,
519 stream
->InitTrackUnionStream(aWindow
, hints
);
520 return stream
.forget();
523 nsDOMUserMediaStream(GetUserMediaCallbackMediaStreamListener
* aListener
,
524 MediaEngineSource
*aAudioSource
) :
525 mListener(aListener
),
526 mAudioSource(aAudioSource
),
531 mEcho(webrtc::kEcDefault
),
532 mAgc(webrtc::kAgcDefault
),
533 mNoise(webrtc::kNsDefault
),
542 virtual ~nsDOMUserMediaStream()
550 mSourceStream
->Destroy();
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
)
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());
575 LOG(("StopTrack(%d) on non-existant track", aTrackID
));
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
);
595 // Allow getUserMedia to pass input data directly to PeerConnection/MediaPipeline
596 virtual bool AddDirectListener(MediaStreamDirectListener
*aListener
) MOZ_OVERRIDE
599 mSourceStream
->AddDirectListener(aListener
);
600 return true; // application should ignore NotifyQueuedTrackData
606 AudioConfig(bool aEchoOn
, uint32_t aEcho
,
607 bool aAgcOn
, uint32_t aAgc
,
608 bool aNoiseOn
, uint32_t aNoise
,
609 int32_t aPlayoutDelay
)
617 mPlayoutDelay
= aPlayoutDelay
;
620 virtual void RemoveDirectListener(MediaStreamDirectListener
*aListener
) MOZ_OVERRIDE
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
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
670 GetUserMediaStreamRunnable(
671 nsCOMPtr
<nsIDOMGetUserMediaSuccessCallback
>& aSuccess
,
672 nsCOMPtr
<nsIDOMGetUserMediaErrorCallback
>& aError
,
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
);
689 ~GetUserMediaStreamRunnable() {}
691 class TracksAvailableCallback
: public DOMMediaStream::OnTracksAvailableCallback
694 TracksAvailableCallback(MediaManager
* aManager
,
695 nsIDOMGetUserMediaSuccessCallback
* aSuccess
,
697 DOMMediaStream
* aStream
)
698 : mWindowID(aWindowID
), mSuccess(aSuccess
), mManager(aManager
),
700 virtual void NotifyTracksAvailable(DOMMediaStream
* aStream
) MOZ_OVERRIDE
702 // We're in the main thread, so no worries here.
703 if (!(mManager
->IsWindowStillActive(mWindowID
))) {
707 // Start currentTime from the point where this stream was successfully
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
);
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
;
734 int32_t aec
= (int32_t) webrtc::kEcUnchanged
;
735 int32_t agc
= (int32_t) webrtc::kAgcUnchanged
;
736 int32_t noise
= (int32_t) webrtc::kNsUnchanged
;
738 int32_t aec
= 0, agc
= 0, noise
= 0;
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
756 // Right now these configs are only of use if webrtc is available
758 nsCOMPtr
<nsIPrefService
> prefs
= do_GetService("@mozilla.org/preferences-service;1", &rv
);
759 if (NS_SUCCEEDED(rv
)) {
760 nsCOMPtr
<nsIPrefBranch
> branch
= do_QueryInterface(prefs
);
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
);
773 // Create a media stream.
774 nsRefPtr
<nsDOMUserMediaStream
> trackunion
=
775 nsDOMUserMediaStream::CreateTrackUnionStream(window
, mListener
,
776 mAudioSource
, mVideoSource
);
778 nsCOMPtr
<nsIDOMGetUserMediaErrorCallback
> error
= mError
.forget();
779 LOG(("Returning error for getUserMedia() - no stream"));
780 error
->OnError(NS_LITERAL_STRING("NO_STREAM"));
783 trackunion
->AudioConfig(aec_on
, (uint32_t) aec
,
784 agc_on
, (uint32_t) agc
,
785 noise_on
, (uint32_t) noise
,
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
;
807 principal
= do_CreateInstance("@mozilla.org/nullprincipal;1");
808 trackunion
->SetPeerIdentity(mPeerIdentity
.forget());
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
,
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
,
839 // We won't need mError now.
845 nsCOMPtr
<nsIDOMGetUserMediaSuccessCallback
> mSuccess
;
846 nsCOMPtr
<nsIDOMGetUserMediaErrorCallback
> mError
;
847 nsRefPtr
<MediaEngineSource
> mAudioSource
;
848 nsRefPtr
<MediaEngineSource
> mVideoSource
;
850 nsRefPtr
<GetUserMediaCallbackMediaStreamListener
> mListener
;
851 nsAutoPtr
<PeerIdentity
> mPeerIdentity
;
852 nsRefPtr
<MediaManager
> mManager
; // get ref to this when creating the runnable
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
)
879 if (aConstraints
.mFacingMode
.WasPassed()) {
880 aCandidate
.GetFacingMode(s
);
881 if (!s
.EqualsASCII(dom::VideoFacingModeEnumValues::strings
[
882 uint32_t(aConstraints
.mFacingMode
.Value())].value
)) {
886 aCandidate
.GetMediaSource(s
);
887 if (!s
.EqualsASCII(dom::MediaSourceEnumValues::strings
[
888 uint32_t(aConstraints
.mMediaSource
)].value
)) {
891 // TODO: Add more video-specific constraints
895 static bool SatisfyConstraintSet(const MediaEngineAudioSource
*,
896 const MediaTrackConstraintSet
&aConstraints
,
897 nsIMediaDevice
&aCandidate
)
899 // TODO: Add audio-specific constraints
903 typedef nsTArray
<nsCOMPtr
<nsIMediaDevice
> > SourceSet
;
905 // Source getter that constrains list returned
907 template<class SourceType
, class ConstraintsType
>
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;
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
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
]));
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
);
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.
995 if (c
.mAdvanced
.WasPassed()) {
996 auto &array
= c
.mAdvanced
.Value();
998 for (int i
= 0; i
< int(array
.Length()); i
++) {
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
);
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
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
)
1040 , mWindowID(aWindowID
)
1041 , mListener(aListener
)
1043 , mDeviceChosen(false)
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.
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
)
1062 , mWindowID(aWindowID
)
1063 , mListener(aListener
)
1065 , mDeviceChosen(false)
1066 , mBackend(aBackend
)
1067 , mManager(MediaManager::GetInstance())
1070 ~GetUserMediaTask() {
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
);
1087 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1088 MOZ_ASSERT(mSuccess
);
1091 MediaEngine
* backend
= mBackend
;
1092 // Was a backend provided?
1094 backend
= mManager
->GetBackend(mWindowID
);
1097 // Was a device provided?
1098 if (!mDeviceChosen
) {
1099 nsresult rv
= SelectDevice(backend
);
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"));
1112 if (mConstraints
.mPicture
) {
1113 ProcessGetUserMediaSnapshot(mVideoDevice
->GetSource(), 0);
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));
1125 Denied(const nsAString
& aErrorMsg
)
1127 MOZ_ASSERT(mSuccess
);
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
);
1143 // This will re-check the window being alive on main-thread
1144 // Note: we must remove the listener on MainThread as well
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
);
1158 SetContraints(const MediaStreamConstraints
& aConstraints
)
1160 mConstraints
= aConstraints
;
1165 SetAudioDevice(AudioDevice
* aAudioDevice
)
1167 mAudioDevice
= aAudioDevice
;
1168 mDeviceChosen
= true;
1173 SetVideoDevice(VideoDevice
* aVideoDevice
)
1175 mVideoDevice
= aVideoDevice
;
1176 mDeviceChosen
= true;
1181 SelectDevice(MediaEngine
* backend
)
1183 MOZ_ASSERT(mSuccess
);
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"));
1217 * Allocates a video or audio device and returns a MediaStream via
1218 * a GetUserMediaStreamRunnable. Runs off the main thread.
1221 ProcessGetUserMedia(MediaEngineAudioSource
* aAudioSource
,
1222 MediaEngineVideoSource
* aVideoSource
)
1224 MOZ_ASSERT(mSuccess
);
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"));
1236 rv
= aVideoSource
->Allocate(GetInvariant(mConstraints
.mVideo
), mPrefs
);
1237 if (NS_FAILED(rv
)) {
1238 LOG(("Failed to allocate videosource %d\n",rv
));
1240 aAudioSource
->Deallocate();
1242 Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE"));
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
,
1256 MOZ_ASSERT(!mSuccess
);
1257 MOZ_ASSERT(!mError
);
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.
1267 ProcessGetUserMediaSnapshot(MediaEngineVideoSource
* aSource
, int aDuration
)
1269 MOZ_ASSERT(mSuccess
);
1271 nsresult rv
= aSource
->Allocate(GetInvariant(mConstraints
.mVideo
), mPrefs
);
1272 if (NS_FAILED(rv
)) {
1273 Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE"));
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
);
1295 MediaStreamConstraints mConstraints
;
1297 nsCOMPtr
<nsIDOMGetUserMediaSuccessCallback
> mSuccess
;
1298 nsCOMPtr
<nsIDOMGetUserMediaErrorCallback
> mError
;
1300 nsRefPtr
<GetUserMediaCallbackMediaStreamListener
> mListener
;
1301 nsRefPtr
<AudioDevice
> mAudioDevice
;
1302 nsRefPtr
<VideoDevice
> mVideoDevice
;
1303 MediaEnginePrefs mPrefs
;
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
1315 // This object must take ownership of task
1316 GetUserMediaRunnableWrapper(GetUserMediaTask
* task
) :
1320 ~GetUserMediaRunnableWrapper() {
1329 nsAutoPtr
<GetUserMediaTask
> mTask
;
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
1339 class GetUserMediaDevicesTask
: public Task
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
)
1351 , mManager(MediaManager::GetInstance())
1352 , mWindowId(aWindowId
)
1353 , mLoopbackAudioDevice(aAudioLoopbackDev
)
1354 , mLoopbackVideoDevice(aVideoLoopbackDev
) {}
1359 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1361 nsRefPtr
<MediaEngine
> backend
;
1362 if (mConstraints
.mFake
)
1363 backend
= new MediaEngineDefault();
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
,
1386 // DeviceSuccessCallbackRunnable should have taken these.
1387 MOZ_ASSERT(!mSuccess
&& !mError
);
1391 MediaStreamConstraints mConstraints
;
1392 nsCOMPtr
<nsIGetUserMediaDevicesSuccessCallback
> mSuccess
;
1393 nsCOMPtr
<nsIDOMGetUserMediaErrorCallback
> mError
;
1394 nsRefPtr
<MediaManager
> mManager
;
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
;
1414 nsCOMPtr
<nsIPrefService
> prefs
= do_GetService("@mozilla.org/preferences-service;1", &rv
);
1415 if (NS_SUCCEEDED(rv
)) {
1416 nsCOMPtr
<nsIPrefBranch
> branch
= do_QueryInterface(prefs
);
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() {
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
;
1442 options
.message_loop_type
= MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD
;
1444 options
.message_loop_type
= MessageLoop::TYPE_MOZILLA_NONMAINTHREAD
;
1446 if (!sSingleton
->mMediaThread
->StartWithOptions(options
)) {
1450 LOG(("New Media thread for gum"));
1452 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
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
);
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);
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();
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();
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
);
1508 nsString requestURL
;
1510 if (nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell()) {
1511 nsresult rv
= docShell
->GetIsApp(&isApp
);
1512 NS_ENSURE_SUCCESS(rv
, rv
);
1515 rv
= docShell
->GetAppManifestURL(requestURL
);
1516 NS_ENSURE_SUCCESS(rv
, rv
);
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",
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
) {
1542 dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(aMsg
,
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.
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
);
1588 uint32_t permission
;
1589 nsCOMPtr
<nsIDocument
> doc
= aWindow
->GetExtantDoc();
1591 pm
->TestPermission(doc
->NodePrincipal(), &permission
);
1592 if (permission
== nsIPopupWindowManager::DENY_POPUP
) {
1593 aWindow
->FirePopupBlockedEvent(doc
, nullptr, EmptyString(),
1602 static bool created
= false;
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();
1608 // Initialize MediaPermissionManager before send out any permission request.
1609 (void) MediaPermissionManager::GetInstance();
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
);
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)) {
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
);
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
;
1681 // Fake stream from default backend.
1682 task
= new GetUserMediaTask(c
, onSuccess
.forget(),
1683 onError
.forget(), windowID
, listener
, mPrefs
, new MediaEngineDefault());
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
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()
1713 #if defined (XP_WIN)
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
);
1730 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
1732 // ShowFilePickerForMimeType() must run on the Main Thread! (on Android)
1733 // Note, GetUserMediaRunnableWrapper takes ownership of task.
1734 NS_DispatchToMainThread(new GetUserMediaRunnableWrapper(task
.forget()));
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
);
1750 // XXX No full support for picture in Desktop yet (needs proper UI)
1752 (c
.mFake
&& !Preferences::GetBool("media.navigator.permission.fake"))) {
1753 MediaManager::GetMessageLoop()->PostTask(FROM_HERE
, task
.forget());
1755 bool isHTTPS
= false;
1757 docURI
->SchemeIs("https", &isHTTPS
);
1760 // Check if this site has persistent permissions.
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
1788 nsCOMPtr
<nsIUUIDGenerator
> uuidgen
=
1789 do_GetService("@mozilla.org/uuid-generator;1", &rv
);
1790 NS_ENSURE_SUCCESS(rv
, rv
);
1792 // Generate a call 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
);
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);
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
));
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
);
1864 #if defined(MOZ_WEBRTC)
1865 mBackend
= new MediaEngineWebRTC(mPrefs
);
1867 mBackend
= new MediaEngineDefault();
1874 StopSharingCallback(MediaManager
*aThis
,
1876 StreamListeners
*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();
1888 listener
->StopScreenWindowSharing();
1890 aListeners
->Clear();
1891 aThis
->RemoveWindowID(aWindowID
);
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
));
1918 IterateWindowListeners(window
, StopSharingCallback
, nullptr);
1920 RemoveWindowID(aWindowID
);
1925 MediaManager::RemoveWindowID(uint64_t aWindowId
)
1927 mActiveWindows
.Remove(aWindowId
);
1929 // get outer windowID
1930 nsPIDOMWindow
*window
= static_cast<nsPIDOMWindow
*>
1931 (nsGlobalWindow::GetInnerWindowWithId(aWindowId
));
1933 LOG(("No inner window for %llu", aWindowId
));
1937 nsPIDOMWindow
*outer
= window
->GetOuterWindow();
1939 LOG(("No outer window for inner %llu", aWindowId
));
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
));
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
);
1969 listeners
->RemoveElement(aListener
);
1970 if (listeners
->Length() == 0) {
1971 RemoveWindowID(aWindowID
);
1972 // listeners has been deleted here
1977 MediaManager::GetPref(nsIPrefBranch
*aBranch
, const char *aPref
,
1978 const char *aData
, int32_t *aVal
)
1981 if (aData
== nullptr || strcmp(aPref
,aData
) == 0) {
1982 if (NS_SUCCEEDED(aBranch
->GetIntPref(aPref
, &temp
))) {
1989 MediaManager::GetPrefBool(nsIPrefBranch
*aBranch
, const char *aPref
,
1990 const char *aData
, bool *aVal
)
1993 if (aData
== nullptr || strcmp(aPref
,aData
) == 0) {
1994 if (NS_SUCCEEDED(aBranch
->GetBoolPref(aPref
, &temp
))) {
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
);
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
) );
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
);
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();
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;
2047 mMediaThread
->Stop();
2054 } else if (!strcmp(aTopic
, "getUserMedia:response:allow")) {
2055 nsString
key(aData
);
2056 nsAutoPtr
<GetUserMediaTask
> task
;
2057 mActiveCallbacks
.RemoveAndForget(key
, task
);
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
));
2071 // neither audio nor video were selected
2072 task
->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
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...
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()));
2088 NS_WARNING("Unknown device type in getUserMedia");
2094 // Reuse the same thread to save memory.
2095 MediaManager::GetMessageLoop()->PostTask(FROM_HERE
, task
.forget());
2098 } else if (!strcmp(aTopic
, "getUserMedia:response:deny")) {
2099 nsString
errorMessage(NS_LITERAL_STRING("PERMISSION_DENIED"));
2102 nsCOMPtr
<nsISupportsString
> msg(do_QueryInterface(aSubject
));
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
);
2113 task
->Denied(errorMessage
);
2117 } else if (!strcmp(aTopic
, "getUserMedia:revoke")) {
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
);
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
);
2138 #ifdef MOZ_WIDGET_GONK
2139 else if (!strcmp(aTopic
, "phone-state-changed")) {
2140 nsString
state(aData
);
2142 uint32_t phoneState
= state
.ToInteger(&rv
);
2144 if (NS_SUCCEEDED(rv
) && phoneState
== nsIAudioManager::PHONE_STATE_IN_CALL
) {
2154 static PLDHashOperator
2155 WindowsHashToArrayFunc (const uint64_t& aId
,
2156 StreamListeners
* aData
,
2159 nsISupportsArray
*array
=
2160 static_cast<nsISupportsArray
*>(userArg
);
2161 nsPIDOMWindow
*window
= static_cast<nsPIDOMWindow
*>
2162 (nsGlobalWindow::GetInnerWindowWithId(aId
));
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;
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()) {
2185 array
->AppendElement(window
);
2187 return PL_DHASH_NEXT
;
2192 MediaManager::GetActiveMediaCaptureWindows(nsISupportsArray
**aArray
)
2195 nsISupportsArray
*array
;
2196 nsresult rv
= NS_NewISupportsArray(&array
); // AddRefs
2200 mActiveWindows
.EnumerateRead(WindowsHashToArrayFunc
, array
);
2206 // XXX flags might be better...
2207 struct CaptureWindowStateData
{
2216 CaptureWindowStateCallback(MediaManager
*aThis
,
2218 StreamListeners
*aListeners
,
2221 struct CaptureWindowStateData
*data
= (struct CaptureWindowStateData
*) aData
;
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;
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
;
2263 *aScreenShare
= false;
2264 *aWindowShare
= false;
2267 nsCOMPtr
<nsPIDOMWindow
> piWin
= do_QueryInterface(aWindow
);
2269 IterateWindowListeners(piWin
, CaptureWindowStateCallback
, &data
);
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" : ""));
2281 StopScreensharingCallback(MediaManager
*aThis
,
2283 StreamListeners
*aListeners
,
2287 auto length
= aListeners
->Length();
2288 for (size_t i
= 0; i
< length
; ++i
) {
2289 aListeners
->ElementAt(i
)->StopScreenWindowSharing();
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
));
2305 IterateWindowListeners(window
, &StopScreensharingCallback
, nullptr);
2308 // lets us do all sorts of things to the listeners
2310 MediaManager::IterateWindowListeners(nsPIDOMWindow
*aWindow
,
2311 WindowListenerCallback aCallback
,
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
);
2318 if (piWin
->IsInnerWindow() || piWin
->GetCurrentInnerWindow()) {
2320 if (piWin
->IsInnerWindow()) {
2321 windowID
= piWin
->WindowID();
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();
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;
2341 IterateWindowListeners(win
, aCallback
, aData
);
2350 MediaManager::StopMediaStreams()
2352 nsCOMPtr
<nsISupportsArray
> array
;
2353 GetActiveMediaCaptureWindows(getter_AddRefs(array
));
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
));
2361 OnNavigation(win
->WindowID());
2367 GetUserMediaCallbackMediaStreamListener::AudioConfig(bool aEchoOn
,
2369 bool aAgcOn
, uint32_t aAGC
,
2370 bool aNoiseOn
, uint32_t aNoise
,
2371 int32_t aPlayoutDelay
)
2375 mMediaThread
->message_loop()->PostTask(FROM_HERE
,
2376 NewRunnableMethod(mAudioSource
.get(), &MediaEngineSource::Config
,
2377 aEchoOn
, aEcho
, aAgcOn
, aAGC
, aNoiseOn
,
2378 aNoise
, aPlayoutDelay
));
2383 // Can be invoked from EITHER MainThread or MSG thread
2385 GetUserMediaCallbackMediaStreamListener::Invalidate()
2387 // We can't take a chance on blocking here, so proxy this to another
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()?
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
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));
2434 LOG(("gUM track %d ended, but we don't have type %s",
2435 aID
, aIsAudio
? "audio" : "video"));
2439 // Called from the MediaStreamGraph thread
2441 GetUserMediaCallbackMediaStreamListener::NotifyFinished(MediaStreamGraph
* aGraph
)
2444 Invalidate(); // we know it's been activated
2445 NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID
, this));
2448 // Called from the MediaStreamGraph thread
2450 GetUserMediaCallbackMediaStreamListener::NotifyDirectListeners(MediaStreamGraph
* aGraph
,
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.
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
));
2472 NotifyFinished(aGraph
);
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();
2489 msg
= NS_LITERAL_STRING("starting");
2490 stream
->OnTracksAvailable(mOnTracksAvailableCallback
.forget());
2493 msg
= NS_LITERAL_STRING("shutdown");
2495 mListener
->SetStopped();
2499 msg
= NS_LITERAL_STRING("shutdown");
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