1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/media/MediaManager.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2072 @@ 1.4 +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "MediaManager.h" 1.11 + 1.12 +#include "MediaStreamGraph.h" 1.13 +#include "GetUserMediaRequest.h" 1.14 +#include "nsHashPropertyBag.h" 1.15 +#ifdef MOZ_WIDGET_GONK 1.16 +#include "nsIAudioManager.h" 1.17 +#endif 1.18 +#include "nsIDOMFile.h" 1.19 +#include "nsIEventTarget.h" 1.20 +#include "nsIUUIDGenerator.h" 1.21 +#include "nsIScriptGlobalObject.h" 1.22 +#include "nsIPermissionManager.h" 1.23 +#include "nsIPopupWindowManager.h" 1.24 +#include "nsISupportsArray.h" 1.25 +#include "nsIDocShell.h" 1.26 +#include "nsIDocument.h" 1.27 +#include "nsISupportsPrimitives.h" 1.28 +#include "nsIInterfaceRequestorUtils.h" 1.29 +#include "mozilla/Types.h" 1.30 +#include "mozilla/dom/ContentChild.h" 1.31 +#include "mozilla/dom/MediaStreamBinding.h" 1.32 +#include "mozilla/dom/MediaStreamTrackBinding.h" 1.33 +#include "mozilla/dom/GetUserMediaRequestBinding.h" 1.34 +#include "MediaTrackConstraints.h" 1.35 + 1.36 +#include "Latency.h" 1.37 + 1.38 +// For PR_snprintf 1.39 +#include "prprf.h" 1.40 + 1.41 +#include "nsJSUtils.h" 1.42 +#include "nsDOMFile.h" 1.43 +#include "nsGlobalWindow.h" 1.44 + 1.45 +/* Using WebRTC backend on Desktops (Mac, Windows, Linux), otherwise default */ 1.46 +#include "MediaEngineDefault.h" 1.47 +#if defined(MOZ_WEBRTC) 1.48 +#include "MediaEngineWebRTC.h" 1.49 +#endif 1.50 + 1.51 +#ifdef MOZ_B2G 1.52 +#include "MediaPermissionGonk.h" 1.53 +#endif 1.54 + 1.55 +// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to 1.56 +// GetTickCount() and conflicts with MediaStream::GetCurrentTime. 1.57 +#ifdef GetCurrentTime 1.58 +#undef GetCurrentTime 1.59 +#endif 1.60 + 1.61 +namespace mozilla { 1.62 + 1.63 +#ifdef LOG 1.64 +#undef LOG 1.65 +#endif 1.66 + 1.67 +#ifdef PR_LOGGING 1.68 +PRLogModuleInfo* 1.69 +GetMediaManagerLog() 1.70 +{ 1.71 + static PRLogModuleInfo *sLog; 1.72 + if (!sLog) 1.73 + sLog = PR_NewLogModule("MediaManager"); 1.74 + return sLog; 1.75 +} 1.76 +#define LOG(msg) PR_LOG(GetMediaManagerLog(), PR_LOG_DEBUG, msg) 1.77 +#else 1.78 +#define LOG(msg) 1.79 +#endif 1.80 + 1.81 +using dom::MediaStreamConstraints; // Outside API (contains JSObject) 1.82 +using dom::MediaTrackConstraintSet; // Mandatory or optional constraints 1.83 +using dom::MediaTrackConstraints; // Raw mMandatory (as JSObject) 1.84 +using dom::GetUserMediaRequest; 1.85 +using dom::Sequence; 1.86 +using dom::OwningBooleanOrMediaTrackConstraints; 1.87 +using dom::SupportedAudioConstraints; 1.88 +using dom::SupportedVideoConstraints; 1.89 + 1.90 +ErrorCallbackRunnable::ErrorCallbackRunnable( 1.91 + nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess, 1.92 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError, 1.93 + const nsAString& aErrorMsg, uint64_t aWindowID) 1.94 + : mErrorMsg(aErrorMsg) 1.95 + , mWindowID(aWindowID) 1.96 + , mManager(MediaManager::GetInstance()) 1.97 +{ 1.98 + mSuccess.swap(aSuccess); 1.99 + mError.swap(aError); 1.100 +} 1.101 + 1.102 +ErrorCallbackRunnable::~ErrorCallbackRunnable() 1.103 +{ 1.104 + MOZ_ASSERT(!mSuccess && !mError); 1.105 +} 1.106 + 1.107 +NS_IMETHODIMP 1.108 +ErrorCallbackRunnable::Run() 1.109 +{ 1.110 + // Only run if the window is still active. 1.111 + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); 1.112 + 1.113 + nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget(); 1.114 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget(); 1.115 + 1.116 + if (!(mManager->IsWindowStillActive(mWindowID))) { 1.117 + return NS_OK; 1.118 + } 1.119 + // This is safe since we're on main-thread, and the windowlist can only 1.120 + // be invalidated from the main-thread (see OnNavigation) 1.121 + error->OnError(mErrorMsg); 1.122 + return NS_OK; 1.123 +} 1.124 + 1.125 +/** 1.126 + * Invoke the "onSuccess" callback in content. The callback will take a 1.127 + * DOMBlob in the case of {picture:true}, and a MediaStream in the case of 1.128 + * {audio:true} or {video:true}. There is a constructor available for each 1.129 + * form. Do this only on the main thread. 1.130 + */ 1.131 +class SuccessCallbackRunnable : public nsRunnable 1.132 +{ 1.133 +public: 1.134 + SuccessCallbackRunnable( 1.135 + nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess, 1.136 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError, 1.137 + nsIDOMFile* aFile, uint64_t aWindowID) 1.138 + : mFile(aFile) 1.139 + , mWindowID(aWindowID) 1.140 + , mManager(MediaManager::GetInstance()) 1.141 + { 1.142 + mSuccess.swap(aSuccess); 1.143 + mError.swap(aError); 1.144 + } 1.145 + 1.146 + NS_IMETHOD 1.147 + Run() 1.148 + { 1.149 + // Only run if the window is still active. 1.150 + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); 1.151 + 1.152 + nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget(); 1.153 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget(); 1.154 + 1.155 + if (!(mManager->IsWindowStillActive(mWindowID))) { 1.156 + return NS_OK; 1.157 + } 1.158 + // This is safe since we're on main-thread, and the windowlist can only 1.159 + // be invalidated from the main-thread (see OnNavigation) 1.160 + success->OnSuccess(mFile); 1.161 + return NS_OK; 1.162 + } 1.163 + 1.164 +private: 1.165 + nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess; 1.166 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError; 1.167 + nsCOMPtr<nsIDOMFile> mFile; 1.168 + uint64_t mWindowID; 1.169 + nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable 1.170 +}; 1.171 + 1.172 +/** 1.173 + * Invoke the GetUserMediaDevices success callback. Wrapped in a runnable 1.174 + * so that it may be called on the main thread. The error callback is also 1.175 + * passed so it can be released correctly. 1.176 + */ 1.177 +class DeviceSuccessCallbackRunnable: public nsRunnable 1.178 +{ 1.179 +public: 1.180 + DeviceSuccessCallbackRunnable( 1.181 + uint64_t aWindowID, 1.182 + nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback>& aSuccess, 1.183 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError, 1.184 + nsTArray<nsCOMPtr<nsIMediaDevice> >* aDevices) 1.185 + : mDevices(aDevices) 1.186 + , mWindowID(aWindowID) 1.187 + , mManager(MediaManager::GetInstance()) 1.188 + { 1.189 + mSuccess.swap(aSuccess); 1.190 + mError.swap(aError); 1.191 + } 1.192 + 1.193 + NS_IMETHOD 1.194 + Run() 1.195 + { 1.196 + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); 1.197 + 1.198 + // Only run if window is still on our active list. 1.199 + if (!mManager->IsWindowStillActive(mWindowID)) { 1.200 + return NS_OK; 1.201 + } 1.202 + 1.203 + nsCOMPtr<nsIWritableVariant> devices = 1.204 + do_CreateInstance("@mozilla.org/variant;1"); 1.205 + 1.206 + int32_t len = mDevices->Length(); 1.207 + if (len == 0) { 1.208 + // XXX 1.209 + // We should in the future return an empty array, and dynamically add 1.210 + // devices to the dropdowns if things are hotplugged while the 1.211 + // requester is up. 1.212 + mError->OnError(NS_LITERAL_STRING("NO_DEVICES_FOUND")); 1.213 + return NS_OK; 1.214 + } 1.215 + 1.216 + nsTArray<nsIMediaDevice*> tmp(len); 1.217 + for (int32_t i = 0; i < len; i++) { 1.218 + tmp.AppendElement(mDevices->ElementAt(i)); 1.219 + } 1.220 + 1.221 + devices->SetAsArray(nsIDataType::VTYPE_INTERFACE, 1.222 + &NS_GET_IID(nsIMediaDevice), 1.223 + mDevices->Length(), 1.224 + const_cast<void*>( 1.225 + static_cast<const void*>(tmp.Elements()) 1.226 + )); 1.227 + 1.228 + mSuccess->OnSuccess(devices); 1.229 + return NS_OK; 1.230 + } 1.231 + 1.232 +private: 1.233 + nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mSuccess; 1.234 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError; 1.235 + nsAutoPtr<nsTArray<nsCOMPtr<nsIMediaDevice> > > mDevices; 1.236 + uint64_t mWindowID; 1.237 + nsRefPtr<MediaManager> mManager; 1.238 +}; 1.239 + 1.240 +// Handle removing GetUserMediaCallbackMediaStreamListener from main thread 1.241 +class GetUserMediaListenerRemove: public nsRunnable 1.242 +{ 1.243 +public: 1.244 + GetUserMediaListenerRemove(uint64_t aWindowID, 1.245 + GetUserMediaCallbackMediaStreamListener *aListener) 1.246 + : mWindowID(aWindowID) 1.247 + , mListener(aListener) {} 1.248 + 1.249 + NS_IMETHOD 1.250 + Run() 1.251 + { 1.252 + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); 1.253 + nsRefPtr<MediaManager> manager(MediaManager::GetInstance()); 1.254 + manager->RemoveFromWindowList(mWindowID, mListener); 1.255 + return NS_OK; 1.256 + } 1.257 + 1.258 +protected: 1.259 + uint64_t mWindowID; 1.260 + nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; 1.261 +}; 1.262 + 1.263 +/** 1.264 + * nsIMediaDevice implementation. 1.265 + */ 1.266 +NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice) 1.267 + 1.268 +MediaDevice* MediaDevice::Create(MediaEngineVideoSource* source) { 1.269 + return new VideoDevice(source); 1.270 +} 1.271 + 1.272 +MediaDevice* MediaDevice::Create(MediaEngineAudioSource* source) { 1.273 + return new AudioDevice(source); 1.274 +} 1.275 + 1.276 +MediaDevice::MediaDevice(MediaEngineSource* aSource) 1.277 + : mHasFacingMode(false) 1.278 + , mSource(aSource) { 1.279 + mSource->GetName(mName); 1.280 + mSource->GetUUID(mID); 1.281 +} 1.282 + 1.283 +VideoDevice::VideoDevice(MediaEngineVideoSource* aSource) 1.284 + : MediaDevice(aSource) { 1.285 +#ifdef MOZ_B2G_CAMERA 1.286 + if (mName.EqualsLiteral("back")) { 1.287 + mHasFacingMode = true; 1.288 + mFacingMode = dom::VideoFacingModeEnum::Environment; 1.289 + } else if (mName.EqualsLiteral("front")) { 1.290 + mHasFacingMode = true; 1.291 + mFacingMode = dom::VideoFacingModeEnum::User; 1.292 + } 1.293 +#endif // MOZ_B2G_CAMERA 1.294 + 1.295 + // Kludge to test user-facing cameras on OSX. 1.296 + if (mName.Find(NS_LITERAL_STRING("Face")) != -1) { 1.297 + mHasFacingMode = true; 1.298 + mFacingMode = dom::VideoFacingModeEnum::User; 1.299 + } 1.300 +} 1.301 + 1.302 +AudioDevice::AudioDevice(MediaEngineAudioSource* aSource) 1.303 + : MediaDevice(aSource) {} 1.304 + 1.305 +NS_IMETHODIMP 1.306 +MediaDevice::GetName(nsAString& aName) 1.307 +{ 1.308 + aName.Assign(mName); 1.309 + return NS_OK; 1.310 +} 1.311 + 1.312 +NS_IMETHODIMP 1.313 +MediaDevice::GetType(nsAString& aType) 1.314 +{ 1.315 + return NS_OK; 1.316 +} 1.317 + 1.318 +NS_IMETHODIMP 1.319 +VideoDevice::GetType(nsAString& aType) 1.320 +{ 1.321 + aType.Assign(NS_LITERAL_STRING("video")); 1.322 + return NS_OK; 1.323 +} 1.324 + 1.325 +NS_IMETHODIMP 1.326 +AudioDevice::GetType(nsAString& aType) 1.327 +{ 1.328 + aType.Assign(NS_LITERAL_STRING("audio")); 1.329 + return NS_OK; 1.330 +} 1.331 + 1.332 +NS_IMETHODIMP 1.333 +MediaDevice::GetId(nsAString& aID) 1.334 +{ 1.335 + aID.Assign(mID); 1.336 + return NS_OK; 1.337 +} 1.338 + 1.339 +NS_IMETHODIMP 1.340 +MediaDevice::GetFacingMode(nsAString& aFacingMode) 1.341 +{ 1.342 + if (mHasFacingMode) { 1.343 + aFacingMode.Assign(NS_ConvertUTF8toUTF16( 1.344 + dom::VideoFacingModeEnumValues::strings[uint32_t(mFacingMode)].value)); 1.345 + } else { 1.346 + aFacingMode.Truncate(0); 1.347 + } 1.348 + return NS_OK; 1.349 +} 1.350 + 1.351 +MediaEngineVideoSource* 1.352 +VideoDevice::GetSource() 1.353 +{ 1.354 + return static_cast<MediaEngineVideoSource*>(&*mSource); 1.355 +} 1.356 + 1.357 +MediaEngineAudioSource* 1.358 +AudioDevice::GetSource() 1.359 +{ 1.360 + return static_cast<MediaEngineAudioSource*>(&*mSource); 1.361 +} 1.362 + 1.363 +/** 1.364 + * A subclass that we only use to stash internal pointers to MediaStreamGraph objects 1.365 + * that need to be cleaned up. 1.366 + */ 1.367 +class nsDOMUserMediaStream : public DOMLocalMediaStream 1.368 +{ 1.369 +public: 1.370 + static already_AddRefed<nsDOMUserMediaStream> 1.371 + CreateTrackUnionStream(nsIDOMWindow* aWindow, 1.372 + MediaEngineSource *aAudioSource, 1.373 + MediaEngineSource *aVideoSource) 1.374 + { 1.375 + DOMMediaStream::TrackTypeHints hints = 1.376 + (aAudioSource ? DOMMediaStream::HINT_CONTENTS_AUDIO : 0) | 1.377 + (aVideoSource ? DOMMediaStream::HINT_CONTENTS_VIDEO : 0); 1.378 + 1.379 + nsRefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aAudioSource); 1.380 + stream->InitTrackUnionStream(aWindow, hints); 1.381 + return stream.forget(); 1.382 + } 1.383 + 1.384 + nsDOMUserMediaStream(MediaEngineSource *aAudioSource) : 1.385 + mAudioSource(aAudioSource), 1.386 + mEchoOn(true), 1.387 + mAgcOn(false), 1.388 + mNoiseOn(true), 1.389 +#ifdef MOZ_WEBRTC 1.390 + mEcho(webrtc::kEcDefault), 1.391 + mAgc(webrtc::kAgcDefault), 1.392 + mNoise(webrtc::kNsDefault), 1.393 +#else 1.394 + mEcho(0), 1.395 + mAgc(0), 1.396 + mNoise(0), 1.397 +#endif 1.398 + mPlayoutDelay(20) 1.399 + {} 1.400 + 1.401 + virtual ~nsDOMUserMediaStream() 1.402 + { 1.403 + Stop(); 1.404 + 1.405 + if (mPort) { 1.406 + mPort->Destroy(); 1.407 + } 1.408 + if (mSourceStream) { 1.409 + mSourceStream->Destroy(); 1.410 + } 1.411 + } 1.412 + 1.413 + virtual void Stop() 1.414 + { 1.415 + if (mSourceStream) { 1.416 + mSourceStream->EndAllTrackAndFinish(); 1.417 + } 1.418 + } 1.419 + 1.420 + // Allow getUserMedia to pass input data directly to PeerConnection/MediaPipeline 1.421 + virtual bool AddDirectListener(MediaStreamDirectListener *aListener) MOZ_OVERRIDE 1.422 + { 1.423 + if (mSourceStream) { 1.424 + mSourceStream->AddDirectListener(aListener); 1.425 + return true; // application should ignore NotifyQueuedTrackData 1.426 + } 1.427 + return false; 1.428 + } 1.429 + 1.430 + virtual void 1.431 + AudioConfig(bool aEchoOn, uint32_t aEcho, 1.432 + bool aAgcOn, uint32_t aAgc, 1.433 + bool aNoiseOn, uint32_t aNoise, 1.434 + int32_t aPlayoutDelay) 1.435 + { 1.436 + mEchoOn = aEchoOn; 1.437 + mEcho = aEcho; 1.438 + mAgcOn = aAgcOn; 1.439 + mAgc = aAgc; 1.440 + mNoiseOn = aNoiseOn; 1.441 + mNoise = aNoise; 1.442 + mPlayoutDelay = aPlayoutDelay; 1.443 + } 1.444 + 1.445 + virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) MOZ_OVERRIDE 1.446 + { 1.447 + if (mSourceStream) { 1.448 + mSourceStream->RemoveDirectListener(aListener); 1.449 + } 1.450 + } 1.451 + 1.452 + // let us intervene for direct listeners when someone does track.enabled = false 1.453 + virtual void SetTrackEnabled(TrackID aID, bool aEnabled) MOZ_OVERRIDE 1.454 + { 1.455 + // We encapsulate the SourceMediaStream and TrackUnion into one entity, so 1.456 + // we can handle the disabling at the SourceMediaStream 1.457 + 1.458 + // We need to find the input track ID for output ID aID, so we let the TrackUnion 1.459 + // forward the request to the source and translate the ID 1.460 + GetStream()->AsProcessedStream()->ForwardTrackEnabled(aID, aEnabled); 1.461 + } 1.462 + 1.463 + // The actual MediaStream is a TrackUnionStream. But these resources need to be 1.464 + // explicitly destroyed too. 1.465 + nsRefPtr<SourceMediaStream> mSourceStream; 1.466 + nsRefPtr<MediaInputPort> mPort; 1.467 + nsRefPtr<MediaEngineSource> mAudioSource; // so we can turn on AEC 1.468 + bool mEchoOn; 1.469 + bool mAgcOn; 1.470 + bool mNoiseOn; 1.471 + uint32_t mEcho; 1.472 + uint32_t mAgc; 1.473 + uint32_t mNoise; 1.474 + uint32_t mPlayoutDelay; 1.475 +}; 1.476 + 1.477 +/** 1.478 + * Creates a MediaStream, attaches a listener and fires off a success callback 1.479 + * to the DOM with the stream. We also pass in the error callback so it can 1.480 + * be released correctly. 1.481 + * 1.482 + * All of this must be done on the main thread! 1.483 + * 1.484 + * Note that the various GetUserMedia Runnable classes currently allow for 1.485 + * two streams. If we ever need to support getting more than two streams 1.486 + * at once, we could convert everything to nsTArray<nsRefPtr<blah> >'s, 1.487 + * though that would complicate the constructors some. Currently the 1.488 + * GetUserMedia spec does not allow for more than 2 streams to be obtained in 1.489 + * one call, to simplify handling of constraints. 1.490 + */ 1.491 +class GetUserMediaStreamRunnable : public nsRunnable 1.492 +{ 1.493 +public: 1.494 + GetUserMediaStreamRunnable( 1.495 + nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess, 1.496 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError, 1.497 + uint64_t aWindowID, 1.498 + GetUserMediaCallbackMediaStreamListener* aListener, 1.499 + MediaEngineSource* aAudioSource, 1.500 + MediaEngineSource* aVideoSource) 1.501 + : mAudioSource(aAudioSource) 1.502 + , mVideoSource(aVideoSource) 1.503 + , mWindowID(aWindowID) 1.504 + , mListener(aListener) 1.505 + , mManager(MediaManager::GetInstance()) 1.506 + { 1.507 + mSuccess.swap(aSuccess); 1.508 + mError.swap(aError); 1.509 + } 1.510 + 1.511 + ~GetUserMediaStreamRunnable() {} 1.512 + 1.513 + class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback 1.514 + { 1.515 + public: 1.516 + TracksAvailableCallback(MediaManager* aManager, 1.517 + nsIDOMGetUserMediaSuccessCallback* aSuccess, 1.518 + uint64_t aWindowID, 1.519 + DOMMediaStream* aStream) 1.520 + : mWindowID(aWindowID), mSuccess(aSuccess), mManager(aManager), 1.521 + mStream(aStream) {} 1.522 + virtual void NotifyTracksAvailable(DOMMediaStream* aStream) MOZ_OVERRIDE 1.523 + { 1.524 + // We're in the main thread, so no worries here. 1.525 + if (!(mManager->IsWindowStillActive(mWindowID))) { 1.526 + return; 1.527 + } 1.528 + 1.529 + // Start currentTime from the point where this stream was successfully 1.530 + // returned. 1.531 + aStream->SetLogicalStreamStartTime(aStream->GetStream()->GetCurrentTime()); 1.532 + 1.533 + // This is safe since we're on main-thread, and the windowlist can only 1.534 + // be invalidated from the main-thread (see OnNavigation) 1.535 + LOG(("Returning success for getUserMedia()")); 1.536 + mSuccess->OnSuccess(aStream); 1.537 + } 1.538 + uint64_t mWindowID; 1.539 + nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess; 1.540 + nsRefPtr<MediaManager> mManager; 1.541 + // Keep the DOMMediaStream alive until the NotifyTracksAvailable callback 1.542 + // has fired, otherwise we might immediately destroy the DOMMediaStream and 1.543 + // shut down the underlying MediaStream prematurely. 1.544 + // This creates a cycle which is broken when NotifyTracksAvailable 1.545 + // is fired (which will happen unless the browser shuts down, 1.546 + // since we only add this callback when we've successfully appended 1.547 + // the desired tracks in the MediaStreamGraph) or when 1.548 + // DOMMediaStream::NotifyMediaStreamGraphShutdown is called. 1.549 + nsRefPtr<DOMMediaStream> mStream; 1.550 + }; 1.551 + 1.552 + NS_IMETHOD 1.553 + Run() 1.554 + { 1.555 +#ifdef MOZ_WEBRTC 1.556 + int32_t aec = (int32_t) webrtc::kEcUnchanged; 1.557 + int32_t agc = (int32_t) webrtc::kAgcUnchanged; 1.558 + int32_t noise = (int32_t) webrtc::kNsUnchanged; 1.559 +#else 1.560 + int32_t aec = 0, agc = 0, noise = 0; 1.561 +#endif 1.562 + bool aec_on = false, agc_on = false, noise_on = false; 1.563 + int32_t playout_delay = 0; 1.564 + 1.565 + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); 1.566 + nsPIDOMWindow *window = static_cast<nsPIDOMWindow*> 1.567 + (nsGlobalWindow::GetInnerWindowWithId(mWindowID)); 1.568 + 1.569 + // We're on main-thread, and the windowlist can only 1.570 + // be invalidated from the main-thread (see OnNavigation) 1.571 + StreamListeners* listeners = mManager->GetWindowListeners(mWindowID); 1.572 + if (!listeners || !window || !window->GetExtantDoc()) { 1.573 + // This window is no longer live. mListener has already been removed 1.574 + return NS_OK; 1.575 + } 1.576 + 1.577 +#ifdef MOZ_WEBRTC 1.578 + // Right now these configs are only of use if webrtc is available 1.579 + nsresult rv; 1.580 + nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); 1.581 + if (NS_SUCCEEDED(rv)) { 1.582 + nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs); 1.583 + 1.584 + if (branch) { 1.585 + branch->GetBoolPref("media.getusermedia.aec_enabled", &aec_on); 1.586 + branch->GetIntPref("media.getusermedia.aec", &aec); 1.587 + branch->GetBoolPref("media.getusermedia.agc_enabled", &agc_on); 1.588 + branch->GetIntPref("media.getusermedia.agc", &agc); 1.589 + branch->GetBoolPref("media.getusermedia.noise_enabled", &noise_on); 1.590 + branch->GetIntPref("media.getusermedia.noise", &noise); 1.591 + branch->GetIntPref("media.getusermedia.playout_delay", &playout_delay); 1.592 + } 1.593 + } 1.594 +#endif 1.595 + // Create a media stream. 1.596 + nsRefPtr<nsDOMUserMediaStream> trackunion = 1.597 + nsDOMUserMediaStream::CreateTrackUnionStream(window, mAudioSource, 1.598 + mVideoSource); 1.599 + if (!trackunion) { 1.600 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget(); 1.601 + LOG(("Returning error for getUserMedia() - no stream")); 1.602 + error->OnError(NS_LITERAL_STRING("NO_STREAM")); 1.603 + return NS_OK; 1.604 + } 1.605 + trackunion->AudioConfig(aec_on, (uint32_t) aec, 1.606 + agc_on, (uint32_t) agc, 1.607 + noise_on, (uint32_t) noise, 1.608 + playout_delay); 1.609 + 1.610 + 1.611 + MediaStreamGraph* gm = MediaStreamGraph::GetInstance(); 1.612 + nsRefPtr<SourceMediaStream> stream = gm->CreateSourceStream(nullptr); 1.613 + 1.614 + // connect the source stream to the track union stream to avoid us blocking 1.615 + trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true); 1.616 + nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()-> 1.617 + AllocateInputPort(stream, MediaInputPort::FLAG_BLOCK_OUTPUT); 1.618 + trackunion->mSourceStream = stream; 1.619 + trackunion->mPort = port.forget(); 1.620 + // Log the relationship between SourceMediaStream and TrackUnion stream 1.621 + // Make sure logger starts before capture 1.622 + AsyncLatencyLogger::Get(true); 1.623 + LogLatency(AsyncLatencyLogger::MediaStreamCreate, 1.624 + reinterpret_cast<uint64_t>(stream.get()), 1.625 + reinterpret_cast<int64_t>(trackunion->GetStream())); 1.626 + 1.627 + trackunion->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal()); 1.628 + 1.629 + // The listener was added at the begining in an inactive state. 1.630 + // Activate our listener. We'll call Start() on the source when get a callback 1.631 + // that the MediaStream has started consuming. The listener is freed 1.632 + // when the page is invalidated (on navigation or close). 1.633 + mListener->Activate(stream.forget(), mAudioSource, mVideoSource); 1.634 + 1.635 + // Note: includes JS callbacks; must be released on MainThread 1.636 + TracksAvailableCallback* tracksAvailableCallback = 1.637 + new TracksAvailableCallback(mManager, mSuccess, mWindowID, trackunion); 1.638 + 1.639 + mListener->AudioConfig(aec_on, (uint32_t) aec, 1.640 + agc_on, (uint32_t) agc, 1.641 + noise_on, (uint32_t) noise, 1.642 + playout_delay); 1.643 + 1.644 + // Dispatch to the media thread to ask it to start the sources, 1.645 + // because that can take a while. 1.646 + // Pass ownership of trackunion to the MediaOperationRunnable 1.647 + // to ensure it's kept alive until the MediaOperationRunnable runs (at least). 1.648 + nsIThread *mediaThread = MediaManager::GetThread(); 1.649 + nsRefPtr<MediaOperationRunnable> runnable( 1.650 + new MediaOperationRunnable(MEDIA_START, mListener, trackunion, 1.651 + tracksAvailableCallback, 1.652 + mAudioSource, mVideoSource, false, mWindowID, 1.653 + mError.forget())); 1.654 + mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); 1.655 + 1.656 + // We won't need mError now. 1.657 + mError = nullptr; 1.658 + return NS_OK; 1.659 + } 1.660 + 1.661 +private: 1.662 + nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess; 1.663 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError; 1.664 + nsRefPtr<MediaEngineSource> mAudioSource; 1.665 + nsRefPtr<MediaEngineSource> mVideoSource; 1.666 + uint64_t mWindowID; 1.667 + nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; 1.668 + nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable 1.669 +}; 1.670 + 1.671 +static bool 1.672 +IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) { 1.673 + return !aUnion.IsBoolean() || aUnion.GetAsBoolean(); 1.674 +} 1.675 + 1.676 +static const MediaTrackConstraints& 1.677 +GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) { 1.678 + static const MediaTrackConstraints empty; 1.679 + return aUnion.IsMediaTrackConstraints() ? 1.680 + aUnion.GetAsMediaTrackConstraints() : empty; 1.681 +} 1.682 + 1.683 +/** 1.684 + * Helper functions that implement the constraints algorithm from 1.685 + * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5 1.686 + */ 1.687 + 1.688 +// Reminder: add handling for new constraints both here and in GetSources below! 1.689 + 1.690 +static bool SatisfyConstraintSet(const MediaEngineVideoSource *, 1.691 + const MediaTrackConstraintSet &aConstraints, 1.692 + nsIMediaDevice &aCandidate) 1.693 +{ 1.694 + if (aConstraints.mFacingMode.WasPassed()) { 1.695 + nsString s; 1.696 + aCandidate.GetFacingMode(s); 1.697 + if (!s.EqualsASCII(dom::VideoFacingModeEnumValues::strings[ 1.698 + uint32_t(aConstraints.mFacingMode.Value())].value)) { 1.699 + return false; 1.700 + } 1.701 + } 1.702 + // TODO: Add more video-specific constraints 1.703 + return true; 1.704 +} 1.705 + 1.706 +static bool SatisfyConstraintSet(const MediaEngineAudioSource *, 1.707 + const MediaTrackConstraintSet &aConstraints, 1.708 + nsIMediaDevice &aCandidate) 1.709 +{ 1.710 + // TODO: Add audio-specific constraints 1.711 + return true; 1.712 +} 1.713 + 1.714 +typedef nsTArray<nsCOMPtr<nsIMediaDevice> > SourceSet; 1.715 + 1.716 +// Source getter that constrains list returned 1.717 + 1.718 +template<class SourceType, class ConstraintsType> 1.719 +static SourceSet * 1.720 + GetSources(MediaEngine *engine, 1.721 + ConstraintsType &aConstraints, 1.722 + void (MediaEngine::* aEnumerate)(nsTArray<nsRefPtr<SourceType> >*), 1.723 + char* media_device_name = nullptr) 1.724 +{ 1.725 + ScopedDeletePtr<SourceSet> result(new SourceSet); 1.726 + 1.727 + const SourceType * const type = nullptr; 1.728 + nsString deviceName; 1.729 + // First collect sources 1.730 + SourceSet candidateSet; 1.731 + { 1.732 + nsTArray<nsRefPtr<SourceType> > sources; 1.733 + (engine->*aEnumerate)(&sources); 1.734 + /** 1.735 + * We're allowing multiple tabs to access the same camera for parity 1.736 + * with Chrome. See bug 811757 for some of the issues surrounding 1.737 + * this decision. To disallow, we'd filter by IsAvailable() as we used 1.738 + * to. 1.739 + */ 1.740 + for (uint32_t len = sources.Length(), i = 0; i < len; i++) { 1.741 +#ifdef DEBUG 1.742 + sources[i]->GetName(deviceName); 1.743 + if (media_device_name && strlen(media_device_name) > 0) { 1.744 + if (deviceName.EqualsASCII(media_device_name)) { 1.745 + candidateSet.AppendElement(MediaDevice::Create(sources[i])); 1.746 + break; 1.747 + } 1.748 + } else { 1.749 +#endif 1.750 + candidateSet.AppendElement(MediaDevice::Create(sources[i])); 1.751 +#ifdef DEBUG 1.752 + } 1.753 +#endif 1.754 + } 1.755 + } 1.756 + 1.757 + // Apply constraints to the list of sources. 1.758 + 1.759 + auto& c = aConstraints; 1.760 + if (c.mUnsupportedRequirement) { 1.761 + // Check upfront the names of required constraints that are unsupported for 1.762 + // this media-type. The spec requires these to fail, so getting them out of 1.763 + // the way early provides a necessary invariant for the remaining algorithm 1.764 + // which maximizes code-reuse by ignoring constraints of the other type 1.765 + // (specifically, SatisfyConstraintSet is reused for the advanced algorithm 1.766 + // where the spec requires it to ignore constraints of the other type) 1.767 + return result.forget(); 1.768 + } 1.769 + 1.770 + // Now on to the actual algorithm: First apply required constraints. 1.771 + 1.772 + for (uint32_t i = 0; i < candidateSet.Length();) { 1.773 + // Overloading instead of template specialization keeps things local 1.774 + if (!SatisfyConstraintSet(type, c.mRequired, *candidateSet[i])) { 1.775 + candidateSet.RemoveElementAt(i); 1.776 + } else { 1.777 + ++i; 1.778 + } 1.779 + } 1.780 + 1.781 + // TODO(jib): Proper non-ordered handling of nonrequired constraints (907352) 1.782 + // 1.783 + // For now, put nonrequired constraints at tail of Advanced list. 1.784 + // This isn't entirely accurate, as order will matter, but few will notice 1.785 + // the difference until we get camera selection and a few more constraints. 1.786 + if (c.mNonrequired.Length()) { 1.787 + if (!c.mAdvanced.WasPassed()) { 1.788 + c.mAdvanced.Construct(); 1.789 + } 1.790 + c.mAdvanced.Value().MoveElementsFrom(c.mNonrequired); 1.791 + } 1.792 + 1.793 + // Then apply advanced (formerly known as optional) constraints. 1.794 + // 1.795 + // These are only effective when there are multiple sources to pick from. 1.796 + // Spec as-of-this-writing says to run algorithm on "all possible tracks 1.797 + // of media type T that the browser COULD RETURN" (emphasis added). 1.798 + // 1.799 + // We think users ultimately control which devices we could return, so after 1.800 + // determining the webpage's preferred list, we add the remaining choices 1.801 + // to the tail, reasoning that they would all have passed individually, 1.802 + // i.e. if the user had any one of them as their sole device (enabled). 1.803 + // 1.804 + // This avoids users having to unplug/disable devices should a webpage pick 1.805 + // the wrong one (UX-fail). Webpage-preferred devices will be listed first. 1.806 + 1.807 + SourceSet tailSet; 1.808 + 1.809 + if (c.mAdvanced.WasPassed()) { 1.810 + auto &array = c.mAdvanced.Value(); 1.811 + 1.812 + for (int i = 0; i < int(array.Length()); i++) { 1.813 + SourceSet rejects; 1.814 + for (uint32_t j = 0; j < candidateSet.Length();) { 1.815 + if (!SatisfyConstraintSet(type, array[i], *candidateSet[j])) { 1.816 + rejects.AppendElement(candidateSet[j]); 1.817 + candidateSet.RemoveElementAt(j); 1.818 + } else { 1.819 + ++j; 1.820 + } 1.821 + } 1.822 + (candidateSet.Length()? tailSet : candidateSet).MoveElementsFrom(rejects); 1.823 + } 1.824 + } 1.825 + 1.826 + // TODO: Proper non-ordered handling of nonrequired constraints (Bug 907352) 1.827 + 1.828 + result->MoveElementsFrom(candidateSet); 1.829 + result->MoveElementsFrom(tailSet); 1.830 + return result.forget(); 1.831 +} 1.832 + 1.833 +/** 1.834 + * Runs on a seperate thread and is responsible for enumerating devices. 1.835 + * Depending on whether a picture or stream was asked for, either 1.836 + * ProcessGetUserMedia or ProcessGetUserMediaSnapshot is called, and the results 1.837 + * are sent back to the DOM. 1.838 + * 1.839 + * Do not run this on the main thread. The success and error callbacks *MUST* 1.840 + * be dispatched on the main thread! 1.841 + */ 1.842 +class GetUserMediaRunnable : public nsRunnable 1.843 +{ 1.844 +public: 1.845 + GetUserMediaRunnable( 1.846 + const MediaStreamConstraints& aConstraints, 1.847 + already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess, 1.848 + already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError, 1.849 + uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener, 1.850 + MediaEnginePrefs &aPrefs) 1.851 + : mConstraints(aConstraints) 1.852 + , mSuccess(aSuccess) 1.853 + , mError(aError) 1.854 + , mWindowID(aWindowID) 1.855 + , mListener(aListener) 1.856 + , mPrefs(aPrefs) 1.857 + , mDeviceChosen(false) 1.858 + , mBackend(nullptr) 1.859 + , mManager(MediaManager::GetInstance()) 1.860 + {} 1.861 + 1.862 + /** 1.863 + * The caller can also choose to provide their own backend instead of 1.864 + * using the one provided by MediaManager::GetBackend. 1.865 + */ 1.866 + GetUserMediaRunnable( 1.867 + const MediaStreamConstraints& aConstraints, 1.868 + already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess, 1.869 + already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError, 1.870 + uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener, 1.871 + MediaEnginePrefs &aPrefs, 1.872 + MediaEngine* aBackend) 1.873 + : mConstraints(aConstraints) 1.874 + , mSuccess(aSuccess) 1.875 + , mError(aError) 1.876 + , mWindowID(aWindowID) 1.877 + , mListener(aListener) 1.878 + , mPrefs(aPrefs) 1.879 + , mDeviceChosen(false) 1.880 + , mBackend(aBackend) 1.881 + , mManager(MediaManager::GetInstance()) 1.882 + {} 1.883 + 1.884 + ~GetUserMediaRunnable() { 1.885 + } 1.886 + 1.887 + void 1.888 + Fail(const nsAString& aMessage) { 1.889 + nsRefPtr<ErrorCallbackRunnable> runnable = 1.890 + new ErrorCallbackRunnable(mSuccess, mError, aMessage, mWindowID); 1.891 + // These should be empty now 1.892 + MOZ_ASSERT(!mSuccess); 1.893 + MOZ_ASSERT(!mError); 1.894 + 1.895 + NS_DispatchToMainThread(runnable); 1.896 + } 1.897 + 1.898 + NS_IMETHOD 1.899 + Run() 1.900 + { 1.901 + NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); 1.902 + MOZ_ASSERT(mSuccess); 1.903 + MOZ_ASSERT(mError); 1.904 + 1.905 + MediaEngine* backend = mBackend; 1.906 + // Was a backend provided? 1.907 + if (!backend) { 1.908 + backend = mManager->GetBackend(mWindowID); 1.909 + } 1.910 + 1.911 + // Was a device provided? 1.912 + if (!mDeviceChosen) { 1.913 + nsresult rv = SelectDevice(backend); 1.914 + if (rv != NS_OK) { 1.915 + return rv; 1.916 + } 1.917 + } 1.918 + 1.919 + // It is an error if audio or video are requested along with picture. 1.920 + if (mConstraints.mPicture && 1.921 + (IsOn(mConstraints.mAudio) || IsOn(mConstraints.mVideo))) { 1.922 + Fail(NS_LITERAL_STRING("NOT_SUPPORTED_ERR")); 1.923 + return NS_OK; 1.924 + } 1.925 + 1.926 + if (mConstraints.mPicture) { 1.927 + ProcessGetUserMediaSnapshot(mVideoDevice->GetSource(), 0); 1.928 + return NS_OK; 1.929 + } 1.930 + 1.931 + // There's a bug in the permission code that can leave us with mAudio but no audio device 1.932 + ProcessGetUserMedia(((IsOn(mConstraints.mAudio) && mAudioDevice) ? 1.933 + mAudioDevice->GetSource() : nullptr), 1.934 + ((IsOn(mConstraints.mVideo) && mVideoDevice) ? 1.935 + mVideoDevice->GetSource() : nullptr)); 1.936 + return NS_OK; 1.937 + } 1.938 + 1.939 + nsresult 1.940 + Denied(const nsAString& aErrorMsg) 1.941 + { 1.942 + MOZ_ASSERT(mSuccess); 1.943 + MOZ_ASSERT(mError); 1.944 + 1.945 + // We add a disabled listener to the StreamListeners array until accepted 1.946 + // If this was the only active MediaStream, remove the window from the list. 1.947 + if (NS_IsMainThread()) { 1.948 + // This is safe since we're on main-thread, and the window can only 1.949 + // be invalidated from the main-thread (see OnNavigation) 1.950 + nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success = mSuccess.forget(); 1.951 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error = mError.forget(); 1.952 + error->OnError(aErrorMsg); 1.953 + 1.954 + // Should happen *after* error runs for consistency, but may not matter 1.955 + nsRefPtr<MediaManager> manager(MediaManager::GetInstance()); 1.956 + manager->RemoveFromWindowList(mWindowID, mListener); 1.957 + } else { 1.958 + // This will re-check the window being alive on main-thread 1.959 + // Note: we must remove the listener on MainThread as well 1.960 + Fail(aErrorMsg); 1.961 + 1.962 + // MUST happen after ErrorCallbackRunnable Run()s, as it checks the active window list 1.963 + NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, mListener)); 1.964 + } 1.965 + 1.966 + MOZ_ASSERT(!mSuccess); 1.967 + MOZ_ASSERT(!mError); 1.968 + 1.969 + return NS_OK; 1.970 + } 1.971 + 1.972 + nsresult 1.973 + SetContraints(const MediaStreamConstraints& aConstraints) 1.974 + { 1.975 + mConstraints = aConstraints; 1.976 + return NS_OK; 1.977 + } 1.978 + 1.979 + nsresult 1.980 + SetAudioDevice(AudioDevice* aAudioDevice) 1.981 + { 1.982 + mAudioDevice = aAudioDevice; 1.983 + mDeviceChosen = true; 1.984 + return NS_OK; 1.985 + } 1.986 + 1.987 + nsresult 1.988 + SetVideoDevice(VideoDevice* aVideoDevice) 1.989 + { 1.990 + mVideoDevice = aVideoDevice; 1.991 + mDeviceChosen = true; 1.992 + return NS_OK; 1.993 + } 1.994 + 1.995 + nsresult 1.996 + SelectDevice(MediaEngine* backend) 1.997 + { 1.998 + MOZ_ASSERT(mSuccess); 1.999 + MOZ_ASSERT(mError); 1.1000 + if (mConstraints.mPicture || IsOn(mConstraints.mVideo)) { 1.1001 + VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo)); 1.1002 + ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints, 1.1003 + &MediaEngine::EnumerateVideoDevices)); 1.1004 + 1.1005 + if (!sources->Length()) { 1.1006 + Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND")); 1.1007 + return NS_ERROR_FAILURE; 1.1008 + } 1.1009 + // Pick the first available device. 1.1010 + mVideoDevice = do_QueryObject((*sources)[0]); 1.1011 + LOG(("Selected video device")); 1.1012 + } 1.1013 + 1.1014 + if (IsOn(mConstraints.mAudio)) { 1.1015 + AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio)); 1.1016 + ScopedDeletePtr<SourceSet> sources (GetSources(backend, constraints, 1.1017 + &MediaEngine::EnumerateAudioDevices)); 1.1018 + 1.1019 + if (!sources->Length()) { 1.1020 + Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND")); 1.1021 + return NS_ERROR_FAILURE; 1.1022 + } 1.1023 + // Pick the first available device. 1.1024 + mAudioDevice = do_QueryObject((*sources)[0]); 1.1025 + LOG(("Selected audio device")); 1.1026 + } 1.1027 + 1.1028 + return NS_OK; 1.1029 + } 1.1030 + 1.1031 + /** 1.1032 + * Allocates a video or audio device and returns a MediaStream via 1.1033 + * a GetUserMediaStreamRunnable. Runs off the main thread. 1.1034 + */ 1.1035 + void 1.1036 + ProcessGetUserMedia(MediaEngineAudioSource* aAudioSource, 1.1037 + MediaEngineVideoSource* aVideoSource) 1.1038 + { 1.1039 + MOZ_ASSERT(mSuccess); 1.1040 + MOZ_ASSERT(mError); 1.1041 + nsresult rv; 1.1042 + if (aAudioSource) { 1.1043 + rv = aAudioSource->Allocate(GetInvariant(mConstraints.mAudio), mPrefs); 1.1044 + if (NS_FAILED(rv)) { 1.1045 + LOG(("Failed to allocate audiosource %d",rv)); 1.1046 + Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE")); 1.1047 + return; 1.1048 + } 1.1049 + } 1.1050 + if (aVideoSource) { 1.1051 + rv = aVideoSource->Allocate(GetInvariant(mConstraints.mVideo), mPrefs); 1.1052 + if (NS_FAILED(rv)) { 1.1053 + LOG(("Failed to allocate videosource %d\n",rv)); 1.1054 + if (aAudioSource) { 1.1055 + aAudioSource->Deallocate(); 1.1056 + } 1.1057 + Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE")); 1.1058 + return; 1.1059 + } 1.1060 + } 1.1061 + 1.1062 + NS_DispatchToMainThread(new GetUserMediaStreamRunnable( 1.1063 + mSuccess, mError, mWindowID, mListener, aAudioSource, aVideoSource 1.1064 + )); 1.1065 + 1.1066 + MOZ_ASSERT(!mSuccess); 1.1067 + MOZ_ASSERT(!mError); 1.1068 + 1.1069 + return; 1.1070 + } 1.1071 + 1.1072 + /** 1.1073 + * Allocates a video device, takes a snapshot and returns a DOMFile via 1.1074 + * a SuccessRunnable or an error via the ErrorRunnable. Off the main thread. 1.1075 + */ 1.1076 + void 1.1077 + ProcessGetUserMediaSnapshot(MediaEngineVideoSource* aSource, int aDuration) 1.1078 + { 1.1079 + MOZ_ASSERT(mSuccess); 1.1080 + MOZ_ASSERT(mError); 1.1081 + nsresult rv = aSource->Allocate(GetInvariant(mConstraints.mVideo), mPrefs); 1.1082 + if (NS_FAILED(rv)) { 1.1083 + Fail(NS_LITERAL_STRING("HARDWARE_UNAVAILABLE")); 1.1084 + return; 1.1085 + } 1.1086 + 1.1087 + /** 1.1088 + * Display picture capture UI here before calling Snapshot() - Bug 748835. 1.1089 + */ 1.1090 + nsCOMPtr<nsIDOMFile> file; 1.1091 + aSource->Snapshot(aDuration, getter_AddRefs(file)); 1.1092 + aSource->Deallocate(); 1.1093 + 1.1094 + NS_DispatchToMainThread(new SuccessCallbackRunnable( 1.1095 + mSuccess, mError, file, mWindowID 1.1096 + )); 1.1097 + 1.1098 + MOZ_ASSERT(!mSuccess); 1.1099 + MOZ_ASSERT(!mError); 1.1100 + 1.1101 + return; 1.1102 + } 1.1103 + 1.1104 +private: 1.1105 + MediaStreamConstraints mConstraints; 1.1106 + 1.1107 + nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess; 1.1108 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError; 1.1109 + uint64_t mWindowID; 1.1110 + nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; 1.1111 + nsRefPtr<AudioDevice> mAudioDevice; 1.1112 + nsRefPtr<VideoDevice> mVideoDevice; 1.1113 + MediaEnginePrefs mPrefs; 1.1114 + 1.1115 + bool mDeviceChosen; 1.1116 + 1.1117 + RefPtr<MediaEngine> mBackend; 1.1118 + nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable 1.1119 +}; 1.1120 + 1.1121 +/** 1.1122 + * Similar to GetUserMediaRunnable, but used for the chrome-only 1.1123 + * GetUserMediaDevices function. Enumerates a list of audio & video devices, 1.1124 + * wraps them up in nsIMediaDevice objects and returns it to the success 1.1125 + * callback. 1.1126 + */ 1.1127 +class GetUserMediaDevicesRunnable : public nsRunnable 1.1128 +{ 1.1129 +public: 1.1130 + GetUserMediaDevicesRunnable( 1.1131 + const MediaStreamConstraints& aConstraints, 1.1132 + already_AddRefed<nsIGetUserMediaDevicesSuccessCallback> aSuccess, 1.1133 + already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError, 1.1134 + uint64_t aWindowId, char* aAudioLoopbackDev, char* aVideoLoopbackDev) 1.1135 + : mConstraints(aConstraints) 1.1136 + , mSuccess(aSuccess) 1.1137 + , mError(aError) 1.1138 + , mManager(MediaManager::GetInstance()) 1.1139 + , mWindowId(aWindowId) 1.1140 + , mLoopbackAudioDevice(aAudioLoopbackDev) 1.1141 + , mLoopbackVideoDevice(aVideoLoopbackDev) {} 1.1142 + 1.1143 + NS_IMETHOD 1.1144 + Run() 1.1145 + { 1.1146 + NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); 1.1147 + 1.1148 + nsRefPtr<MediaEngine> backend; 1.1149 + if (mConstraints.mFake) 1.1150 + backend = new MediaEngineDefault(); 1.1151 + else 1.1152 + backend = mManager->GetBackend(mWindowId); 1.1153 + 1.1154 + ScopedDeletePtr<SourceSet> final(new SourceSet); 1.1155 + if (IsOn(mConstraints.mVideo)) { 1.1156 + VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo)); 1.1157 + ScopedDeletePtr<SourceSet> s(GetSources(backend, constraints, 1.1158 + &MediaEngine::EnumerateVideoDevices, 1.1159 + mLoopbackVideoDevice)); 1.1160 + final->MoveElementsFrom(*s); 1.1161 + } 1.1162 + if (IsOn(mConstraints.mAudio)) { 1.1163 + AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio)); 1.1164 + ScopedDeletePtr<SourceSet> s (GetSources(backend, constraints, 1.1165 + &MediaEngine::EnumerateAudioDevices, 1.1166 + mLoopbackAudioDevice)); 1.1167 + final->MoveElementsFrom(*s); 1.1168 + } 1.1169 + NS_DispatchToMainThread(new DeviceSuccessCallbackRunnable(mWindowId, 1.1170 + mSuccess, mError, 1.1171 + final.forget())); 1.1172 + // DeviceSuccessCallbackRunnable should have taken these. 1.1173 + MOZ_ASSERT(!mSuccess && !mError); 1.1174 + return NS_OK; 1.1175 + } 1.1176 + 1.1177 +private: 1.1178 + MediaStreamConstraints mConstraints; 1.1179 + nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> mSuccess; 1.1180 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError; 1.1181 + nsRefPtr<MediaManager> mManager; 1.1182 + uint64_t mWindowId; 1.1183 + const nsString mCallId; 1.1184 + // Audio & Video loopback devices to be used based on 1.1185 + // the preference settings. This is currently used for 1.1186 + // automated media tests only. 1.1187 + char* mLoopbackAudioDevice; 1.1188 + char* mLoopbackVideoDevice; 1.1189 +}; 1.1190 + 1.1191 +MediaManager::MediaManager() 1.1192 + : mMediaThread(nullptr) 1.1193 + , mMutex("mozilla::MediaManager") 1.1194 + , mBackend(nullptr) { 1.1195 + mPrefs.mWidth = 0; // adaptive default 1.1196 + mPrefs.mHeight = 0; // adaptive default 1.1197 + mPrefs.mFPS = MediaEngine::DEFAULT_VIDEO_FPS; 1.1198 + mPrefs.mMinFPS = MediaEngine::DEFAULT_VIDEO_MIN_FPS; 1.1199 + 1.1200 + nsresult rv; 1.1201 + nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); 1.1202 + if (NS_SUCCEEDED(rv)) { 1.1203 + nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs); 1.1204 + if (branch) { 1.1205 + GetPrefs(branch, nullptr); 1.1206 + } 1.1207 + } 1.1208 + LOG(("%s: default prefs: %dx%d @%dfps (min %d)", __FUNCTION__, 1.1209 + mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS)); 1.1210 +} 1.1211 + 1.1212 +NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIObserver) 1.1213 + 1.1214 +/* static */ StaticRefPtr<MediaManager> MediaManager::sSingleton; 1.1215 + 1.1216 +// NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager 1.1217 +// thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread 1.1218 +// from MediaManager thread. 1.1219 +/* static */ MediaManager* 1.1220 +MediaManager::Get() { 1.1221 + if (!sSingleton) { 1.1222 + sSingleton = new MediaManager(); 1.1223 + 1.1224 + NS_NewNamedThread("MediaManager", getter_AddRefs(sSingleton->mMediaThread)); 1.1225 + LOG(("New Media thread for gum")); 1.1226 + 1.1227 + NS_ASSERTION(NS_IsMainThread(), "Only create MediaManager on main thread"); 1.1228 + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1.1229 + if (obs) { 1.1230 + obs->AddObserver(sSingleton, "xpcom-shutdown", false); 1.1231 + obs->AddObserver(sSingleton, "getUserMedia:response:allow", false); 1.1232 + obs->AddObserver(sSingleton, "getUserMedia:response:deny", false); 1.1233 + obs->AddObserver(sSingleton, "getUserMedia:revoke", false); 1.1234 + obs->AddObserver(sSingleton, "phone-state-changed", false); 1.1235 + } 1.1236 + // else MediaManager won't work properly and will leak (see bug 837874) 1.1237 + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 1.1238 + if (prefs) { 1.1239 + prefs->AddObserver("media.navigator.video.default_width", sSingleton, false); 1.1240 + prefs->AddObserver("media.navigator.video.default_height", sSingleton, false); 1.1241 + prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false); 1.1242 + prefs->AddObserver("media.navigator.video.default_minfps", sSingleton, false); 1.1243 + } 1.1244 + } 1.1245 + return sSingleton; 1.1246 +} 1.1247 + 1.1248 +/* static */ already_AddRefed<MediaManager> 1.1249 +MediaManager::GetInstance() 1.1250 +{ 1.1251 + // so we can have non-refcounted getters 1.1252 + nsRefPtr<MediaManager> service = MediaManager::Get(); 1.1253 + return service.forget(); 1.1254 +} 1.1255 + 1.1256 +/* static */ nsresult 1.1257 +MediaManager::NotifyRecordingStatusChange(nsPIDOMWindow* aWindow, 1.1258 + const nsString& aMsg, 1.1259 + const bool& aIsAudio, 1.1260 + const bool& aIsVideo) 1.1261 +{ 1.1262 + NS_ENSURE_ARG(aWindow); 1.1263 + 1.1264 + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1.1265 + if (!obs) { 1.1266 + NS_WARNING("Could not get the Observer service for GetUserMedia recording notification."); 1.1267 + return NS_ERROR_FAILURE; 1.1268 + } 1.1269 + 1.1270 + nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag(); 1.1271 + props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio); 1.1272 + props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo); 1.1273 + 1.1274 + bool isApp = false; 1.1275 + nsString requestURL; 1.1276 + 1.1277 + if (nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell()) { 1.1278 + nsresult rv = docShell->GetIsApp(&isApp); 1.1279 + NS_ENSURE_SUCCESS(rv, rv); 1.1280 + 1.1281 + if (isApp) { 1.1282 + rv = docShell->GetAppManifestURL(requestURL); 1.1283 + NS_ENSURE_SUCCESS(rv, rv); 1.1284 + } 1.1285 + } 1.1286 + 1.1287 + if (!isApp) { 1.1288 + nsCString pageURL; 1.1289 + nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI(); 1.1290 + NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE); 1.1291 + 1.1292 + nsresult rv = docURI->GetSpec(pageURL); 1.1293 + NS_ENSURE_SUCCESS(rv, rv); 1.1294 + 1.1295 + requestURL = NS_ConvertUTF8toUTF16(pageURL); 1.1296 + } 1.1297 + 1.1298 + props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp); 1.1299 + props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL); 1.1300 + 1.1301 + obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props), 1.1302 + "recording-device-events", 1.1303 + aMsg.get()); 1.1304 + 1.1305 + // Forward recording events to parent process. 1.1306 + // The events are gathered in chrome process and used for recording indicator 1.1307 + if (XRE_GetProcessType() != GeckoProcessType_Default) { 1.1308 + unused << 1.1309 + dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(aMsg, 1.1310 + requestURL, 1.1311 + aIsAudio, 1.1312 + aIsVideo); 1.1313 + } 1.1314 + 1.1315 + return NS_OK; 1.1316 +} 1.1317 + 1.1318 +/** 1.1319 + * The entry point for this file. A call from Navigator::mozGetUserMedia 1.1320 + * will end up here. MediaManager is a singleton that is responsible 1.1321 + * for handling all incoming getUserMedia calls from every window. 1.1322 + */ 1.1323 +nsresult 1.1324 +MediaManager::GetUserMedia(bool aPrivileged, 1.1325 + nsPIDOMWindow* aWindow, const MediaStreamConstraints& aConstraints, 1.1326 + nsIDOMGetUserMediaSuccessCallback* aOnSuccess, 1.1327 + nsIDOMGetUserMediaErrorCallback* aOnError) 1.1328 +{ 1.1329 + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); 1.1330 + 1.1331 + NS_ENSURE_TRUE(aWindow, NS_ERROR_NULL_POINTER); 1.1332 + NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER); 1.1333 + NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER); 1.1334 + 1.1335 + nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess); 1.1336 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError); 1.1337 + 1.1338 + MediaStreamConstraints c(aConstraints); // copy 1.1339 + 1.1340 + /** 1.1341 + * If we were asked to get a picture, before getting a snapshot, we check if 1.1342 + * the calling page is allowed to open a popup. We do this because 1.1343 + * {picture:true} will open a new "window" to let the user preview or select 1.1344 + * an image, on Android. The desktop UI for {picture:true} is TBD, at which 1.1345 + * may point we can decide whether to extend this test there as well. 1.1346 + */ 1.1347 +#if !defined(MOZ_WEBRTC) 1.1348 + if (c.mPicture && !aPrivileged) { 1.1349 + if (aWindow->GetPopupControlState() > openControlled) { 1.1350 + nsCOMPtr<nsIPopupWindowManager> pm = 1.1351 + do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID); 1.1352 + if (!pm) { 1.1353 + return NS_OK; 1.1354 + } 1.1355 + uint32_t permission; 1.1356 + nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc(); 1.1357 + pm->TestPermission(doc->NodePrincipal(), &permission); 1.1358 + if (permission == nsIPopupWindowManager::DENY_POPUP) { 1.1359 + nsGlobalWindow::FirePopupBlockedEvent( 1.1360 + doc, aWindow, nullptr, EmptyString(), EmptyString() 1.1361 + ); 1.1362 + return NS_OK; 1.1363 + } 1.1364 + } 1.1365 + } 1.1366 +#endif 1.1367 + 1.1368 + static bool created = false; 1.1369 + if (!created) { 1.1370 + // Force MediaManager to startup before we try to access it from other threads 1.1371 + // Hack: should init singleton earlier unless it's expensive (mem or CPU) 1.1372 + (void) MediaManager::Get(); 1.1373 +#ifdef MOZ_B2G 1.1374 + // Initialize MediaPermissionManager before send out any permission request. 1.1375 + (void) MediaPermissionManager::GetInstance(); 1.1376 +#endif //MOZ_B2G 1.1377 + } 1.1378 + 1.1379 + // Store the WindowID in a hash table and mark as active. The entry is removed 1.1380 + // when this window is closed or navigated away from. 1.1381 + uint64_t windowID = aWindow->WindowID(); 1.1382 + // This is safe since we're on main-thread, and the windowlist can only 1.1383 + // be invalidated from the main-thread (see OnNavigation) 1.1384 + StreamListeners* listeners = GetActiveWindows()->Get(windowID); 1.1385 + if (!listeners) { 1.1386 + listeners = new StreamListeners; 1.1387 + GetActiveWindows()->Put(windowID, listeners); 1.1388 + } 1.1389 + 1.1390 + // Ensure there's a thread for gum to proxy to off main thread 1.1391 + nsIThread *mediaThread = MediaManager::GetThread(); 1.1392 + 1.1393 + // Create a disabled listener to act as a placeholder 1.1394 + GetUserMediaCallbackMediaStreamListener* listener = 1.1395 + new GetUserMediaCallbackMediaStreamListener(mediaThread, windowID); 1.1396 + 1.1397 + // No need for locking because we always do this in the main thread. 1.1398 + listeners->AppendElement(listener); 1.1399 + 1.1400 + // Developer preference for turning off permission check. 1.1401 + if (Preferences::GetBool("media.navigator.permission.disabled", false)) { 1.1402 + aPrivileged = true; 1.1403 + } 1.1404 + if (!Preferences::GetBool("media.navigator.video.enabled", true)) { 1.1405 + c.mVideo.SetAsBoolean() = false; 1.1406 + } 1.1407 + 1.1408 +#if defined(ANDROID) || defined(MOZ_WIDGET_GONK) 1.1409 + // Be backwards compatible only on mobile and only for facingMode. 1.1410 + if (c.mVideo.IsMediaTrackConstraints()) { 1.1411 + auto& tc = c.mVideo.GetAsMediaTrackConstraints(); 1.1412 + if (!tc.mRequire.WasPassed() && 1.1413 + tc.mMandatory.mFacingMode.WasPassed() && !tc.mFacingMode.WasPassed()) { 1.1414 + tc.mFacingMode.Construct(tc.mMandatory.mFacingMode.Value()); 1.1415 + tc.mRequire.Construct().AppendElement(NS_LITERAL_STRING("facingMode")); 1.1416 + } 1.1417 + if (tc.mOptional.WasPassed() && !tc.mAdvanced.WasPassed()) { 1.1418 + tc.mAdvanced.Construct(); 1.1419 + for (uint32_t i = 0; i < tc.mOptional.Value().Length(); i++) { 1.1420 + if (tc.mOptional.Value()[i].mFacingMode.WasPassed()) { 1.1421 + MediaTrackConstraintSet n; 1.1422 + n.mFacingMode.Construct(tc.mOptional.Value()[i].mFacingMode.Value()); 1.1423 + tc.mAdvanced.Value().AppendElement(n); 1.1424 + } 1.1425 + } 1.1426 + } 1.1427 + } 1.1428 +#endif 1.1429 + 1.1430 + // Pass callbacks and MediaStreamListener along to GetUserMediaRunnable. 1.1431 + nsRefPtr<GetUserMediaRunnable> runnable; 1.1432 + if (c.mFake) { 1.1433 + // Fake stream from default backend. 1.1434 + runnable = new GetUserMediaRunnable(c, onSuccess.forget(), 1.1435 + onError.forget(), windowID, listener, mPrefs, new MediaEngineDefault()); 1.1436 + } else { 1.1437 + // Stream from default device from WebRTC backend. 1.1438 + runnable = new GetUserMediaRunnable(c, onSuccess.forget(), 1.1439 + onError.forget(), windowID, listener, mPrefs); 1.1440 + } 1.1441 + 1.1442 +#ifdef MOZ_B2G_CAMERA 1.1443 + if (mCameraManager == nullptr) { 1.1444 + mCameraManager = nsDOMCameraManager::CreateInstance(aWindow); 1.1445 + } 1.1446 +#endif 1.1447 + 1.1448 +#if defined(ANDROID) && !defined(MOZ_WIDGET_GONK) 1.1449 + if (c.mPicture) { 1.1450 + // ShowFilePickerForMimeType() must run on the Main Thread! (on Android) 1.1451 + NS_DispatchToMainThread(runnable); 1.1452 + return NS_OK; 1.1453 + } 1.1454 +#endif 1.1455 + // XXX No full support for picture in Desktop yet (needs proper UI) 1.1456 + if (aPrivileged || 1.1457 + (c.mFake && !Preferences::GetBool("media.navigator.permission.fake"))) { 1.1458 + mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); 1.1459 + } else { 1.1460 + bool isHTTPS = false; 1.1461 + nsIURI* docURI = aWindow->GetDocumentURI(); 1.1462 + if (docURI) { 1.1463 + docURI->SchemeIs("https", &isHTTPS); 1.1464 + } 1.1465 + 1.1466 + // Check if this site has persistent permissions. 1.1467 + nsresult rv; 1.1468 + nsCOMPtr<nsIPermissionManager> permManager = 1.1469 + do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); 1.1470 + NS_ENSURE_SUCCESS(rv, rv); 1.1471 + 1.1472 + uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION; 1.1473 + if (IsOn(c.mAudio)) { 1.1474 + rv = permManager->TestExactPermissionFromPrincipal( 1.1475 + aWindow->GetExtantDoc()->NodePrincipal(), "microphone", &audioPerm); 1.1476 + NS_ENSURE_SUCCESS(rv, rv); 1.1477 + } 1.1478 + 1.1479 + uint32_t videoPerm = nsIPermissionManager::UNKNOWN_ACTION; 1.1480 + if (IsOn(c.mVideo)) { 1.1481 + rv = permManager->TestExactPermissionFromPrincipal( 1.1482 + aWindow->GetExtantDoc()->NodePrincipal(), "camera", &videoPerm); 1.1483 + NS_ENSURE_SUCCESS(rv, rv); 1.1484 + } 1.1485 + 1.1486 + if ((!IsOn(c.mAudio) || audioPerm == nsIPermissionManager::DENY_ACTION) && 1.1487 + (!IsOn(c.mVideo) || videoPerm == nsIPermissionManager::DENY_ACTION)) { 1.1488 + return runnable->Denied(NS_LITERAL_STRING("PERMISSION_DENIED")); 1.1489 + } 1.1490 + 1.1491 + // Ask for user permission, and dispatch runnable (or not) when a response 1.1492 + // is received via an observer notification. Each call is paired with its 1.1493 + // runnable by a GUID. 1.1494 + nsCOMPtr<nsIUUIDGenerator> uuidgen = 1.1495 + do_GetService("@mozilla.org/uuid-generator;1", &rv); 1.1496 + NS_ENSURE_SUCCESS(rv, rv); 1.1497 + 1.1498 + // Generate a call ID. 1.1499 + nsID id; 1.1500 + rv = uuidgen->GenerateUUIDInPlace(&id); 1.1501 + NS_ENSURE_SUCCESS(rv, rv); 1.1502 + 1.1503 + char buffer[NSID_LENGTH]; 1.1504 + id.ToProvidedString(buffer); 1.1505 + NS_ConvertUTF8toUTF16 callID(buffer); 1.1506 + 1.1507 + // Store the current unarmed runnable w/callbacks. 1.1508 + mActiveCallbacks.Put(callID, runnable); 1.1509 + 1.1510 + // Add a WindowID cross-reference so OnNavigation can tear things down 1.1511 + nsTArray<nsString>* array; 1.1512 + if (!mCallIds.Get(windowID, &array)) { 1.1513 + array = new nsTArray<nsString>(); 1.1514 + array->AppendElement(callID); 1.1515 + mCallIds.Put(windowID, array); 1.1516 + } else { 1.1517 + array->AppendElement(callID); 1.1518 + } 1.1519 + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1.1520 + nsRefPtr<GetUserMediaRequest> req = new GetUserMediaRequest(aWindow, 1.1521 + callID, c, isHTTPS); 1.1522 + obs->NotifyObservers(req, "getUserMedia:request", nullptr); 1.1523 + } 1.1524 + 1.1525 + return NS_OK; 1.1526 +} 1.1527 + 1.1528 +nsresult 1.1529 +MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow, 1.1530 + const MediaStreamConstraints& aConstraints, 1.1531 + nsIGetUserMediaDevicesSuccessCallback* aOnSuccess, 1.1532 + nsIDOMGetUserMediaErrorCallback* aOnError, 1.1533 + uint64_t aInnerWindowID) 1.1534 +{ 1.1535 + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); 1.1536 + 1.1537 + NS_ENSURE_TRUE(aOnError, NS_ERROR_NULL_POINTER); 1.1538 + NS_ENSURE_TRUE(aOnSuccess, NS_ERROR_NULL_POINTER); 1.1539 + 1.1540 + nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess); 1.1541 + nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError); 1.1542 + char* loopbackAudioDevice = nullptr; 1.1543 + char* loopbackVideoDevice = nullptr; 1.1544 + 1.1545 +#ifdef DEBUG 1.1546 + nsresult rv; 1.1547 + 1.1548 + // Check if the preference for using loopback devices is enabled. 1.1549 + nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); 1.1550 + if (NS_SUCCEEDED(rv)) { 1.1551 + nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs); 1.1552 + if (branch) { 1.1553 + branch->GetCharPref("media.audio_loopback_dev", &loopbackAudioDevice); 1.1554 + branch->GetCharPref("media.video_loopback_dev", &loopbackVideoDevice); 1.1555 + } 1.1556 + } 1.1557 +#endif 1.1558 + 1.1559 + nsCOMPtr<nsIRunnable> gUMDRunnable = new GetUserMediaDevicesRunnable( 1.1560 + aConstraints, onSuccess.forget(), onError.forget(), 1.1561 + (aInnerWindowID ? aInnerWindowID : aWindow->WindowID()), 1.1562 + loopbackAudioDevice, loopbackVideoDevice); 1.1563 + 1.1564 + mMediaThread->Dispatch(gUMDRunnable, NS_DISPATCH_NORMAL); 1.1565 + return NS_OK; 1.1566 +} 1.1567 + 1.1568 +MediaEngine* 1.1569 +MediaManager::GetBackend(uint64_t aWindowId) 1.1570 +{ 1.1571 + // Plugin backends as appropriate. The default engine also currently 1.1572 + // includes picture support for Android. 1.1573 + // This IS called off main-thread. 1.1574 + MutexAutoLock lock(mMutex); 1.1575 + if (!mBackend) { 1.1576 +#if defined(MOZ_WEBRTC) 1.1577 + mBackend = new MediaEngineWebRTC(mPrefs); 1.1578 +#else 1.1579 + mBackend = new MediaEngineDefault(); 1.1580 +#endif 1.1581 + } 1.1582 + return mBackend; 1.1583 +} 1.1584 + 1.1585 +static void 1.1586 +StopSharingCallback(MediaManager *aThis, 1.1587 + uint64_t aWindowID, 1.1588 + StreamListeners *aListeners, 1.1589 + void *aData) 1.1590 +{ 1.1591 + if (aListeners) { 1.1592 + auto length = aListeners->Length(); 1.1593 + for (size_t i = 0; i < length; ++i) { 1.1594 + GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i); 1.1595 + 1.1596 + if (listener->Stream()) { // aka HasBeenActivate()ed 1.1597 + listener->Invalidate(); 1.1598 + } 1.1599 + listener->Remove(); 1.1600 + } 1.1601 + aListeners->Clear(); 1.1602 + aThis->RemoveWindowID(aWindowID); 1.1603 + } 1.1604 +} 1.1605 + 1.1606 + 1.1607 +void 1.1608 +MediaManager::OnNavigation(uint64_t aWindowID) 1.1609 +{ 1.1610 + NS_ASSERTION(NS_IsMainThread(), "OnNavigation called off main thread"); 1.1611 + LOG(("OnNavigation for %llu", aWindowID)); 1.1612 + 1.1613 + // Invalidate this window. The runnables check this value before making 1.1614 + // a call to content. 1.1615 + 1.1616 + nsTArray<nsString>* callIds; 1.1617 + if (mCallIds.Get(aWindowID, &callIds)) { 1.1618 + for (int i = 0, len = callIds->Length(); i < len; ++i) { 1.1619 + mActiveCallbacks.Remove((*callIds)[i]); 1.1620 + } 1.1621 + mCallIds.Remove(aWindowID); 1.1622 + } 1.1623 + 1.1624 + // This is safe since we're on main-thread, and the windowlist can only 1.1625 + // be added to from the main-thread 1.1626 + nsPIDOMWindow *window = static_cast<nsPIDOMWindow*> 1.1627 + (nsGlobalWindow::GetInnerWindowWithId(aWindowID)); 1.1628 + if (window) { 1.1629 + IterateWindowListeners(window, StopSharingCallback, nullptr); 1.1630 + } else { 1.1631 + RemoveWindowID(aWindowID); 1.1632 + } 1.1633 +} 1.1634 + 1.1635 +void 1.1636 +MediaManager::RemoveFromWindowList(uint64_t aWindowID, 1.1637 + GetUserMediaCallbackMediaStreamListener *aListener) 1.1638 +{ 1.1639 + NS_ASSERTION(NS_IsMainThread(), "RemoveFromWindowList called off main thread"); 1.1640 + 1.1641 + // This is defined as safe on an inactive GUMCMSListener 1.1642 + aListener->Remove(); // really queues the remove 1.1643 + 1.1644 + StreamListeners* listeners = GetWindowListeners(aWindowID); 1.1645 + if (!listeners) { 1.1646 + return; 1.1647 + } 1.1648 + listeners->RemoveElement(aListener); 1.1649 + if (listeners->Length() == 0) { 1.1650 + RemoveWindowID(aWindowID); 1.1651 + // listeners has been deleted here 1.1652 + 1.1653 + // get outer windowID 1.1654 + nsPIDOMWindow *window = static_cast<nsPIDOMWindow*> 1.1655 + (nsGlobalWindow::GetInnerWindowWithId(aWindowID)); 1.1656 + if (window) { 1.1657 + nsPIDOMWindow *outer = window->GetOuterWindow(); 1.1658 + if (outer) { 1.1659 + uint64_t outerID = outer->WindowID(); 1.1660 + 1.1661 + // Notify the UI that this window no longer has gUM active 1.1662 + char windowBuffer[32]; 1.1663 + PR_snprintf(windowBuffer, sizeof(windowBuffer), "%llu", outerID); 1.1664 + nsAutoString data; 1.1665 + data.Append(NS_ConvertUTF8toUTF16(windowBuffer)); 1.1666 + 1.1667 + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1.1668 + obs->NotifyObservers(nullptr, "recording-window-ended", data.get()); 1.1669 + LOG(("Sent recording-window-ended for window %llu (outer %llu)", 1.1670 + aWindowID, outerID)); 1.1671 + } else { 1.1672 + LOG(("No outer window for inner %llu", aWindowID)); 1.1673 + } 1.1674 + } else { 1.1675 + LOG(("No inner window for %llu", aWindowID)); 1.1676 + } 1.1677 + } 1.1678 +} 1.1679 + 1.1680 +void 1.1681 +MediaManager::GetPref(nsIPrefBranch *aBranch, const char *aPref, 1.1682 + const char *aData, int32_t *aVal) 1.1683 +{ 1.1684 + int32_t temp; 1.1685 + if (aData == nullptr || strcmp(aPref,aData) == 0) { 1.1686 + if (NS_SUCCEEDED(aBranch->GetIntPref(aPref, &temp))) { 1.1687 + *aVal = temp; 1.1688 + } 1.1689 + } 1.1690 +} 1.1691 + 1.1692 +void 1.1693 +MediaManager::GetPrefBool(nsIPrefBranch *aBranch, const char *aPref, 1.1694 + const char *aData, bool *aVal) 1.1695 +{ 1.1696 + bool temp; 1.1697 + if (aData == nullptr || strcmp(aPref,aData) == 0) { 1.1698 + if (NS_SUCCEEDED(aBranch->GetBoolPref(aPref, &temp))) { 1.1699 + *aVal = temp; 1.1700 + } 1.1701 + } 1.1702 +} 1.1703 + 1.1704 +void 1.1705 +MediaManager::GetPrefs(nsIPrefBranch *aBranch, const char *aData) 1.1706 +{ 1.1707 + GetPref(aBranch, "media.navigator.video.default_width", aData, &mPrefs.mWidth); 1.1708 + GetPref(aBranch, "media.navigator.video.default_height", aData, &mPrefs.mHeight); 1.1709 + GetPref(aBranch, "media.navigator.video.default_fps", aData, &mPrefs.mFPS); 1.1710 + GetPref(aBranch, "media.navigator.video.default_minfps", aData, &mPrefs.mMinFPS); 1.1711 +} 1.1712 + 1.1713 +nsresult 1.1714 +MediaManager::Observe(nsISupports* aSubject, const char* aTopic, 1.1715 + const char16_t* aData) 1.1716 +{ 1.1717 + NS_ASSERTION(NS_IsMainThread(), "Observer invoked off the main thread"); 1.1718 + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1.1719 + 1.1720 + if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { 1.1721 + nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) ); 1.1722 + if (branch) { 1.1723 + GetPrefs(branch,NS_ConvertUTF16toUTF8(aData).get()); 1.1724 + LOG(("%s: %dx%d @%dfps (min %d)", __FUNCTION__, 1.1725 + mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS)); 1.1726 + } 1.1727 + } else if (!strcmp(aTopic, "xpcom-shutdown")) { 1.1728 + obs->RemoveObserver(this, "xpcom-shutdown"); 1.1729 + obs->RemoveObserver(this, "getUserMedia:response:allow"); 1.1730 + obs->RemoveObserver(this, "getUserMedia:response:deny"); 1.1731 + obs->RemoveObserver(this, "getUserMedia:revoke"); 1.1732 + 1.1733 + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 1.1734 + if (prefs) { 1.1735 + prefs->RemoveObserver("media.navigator.video.default_width", this); 1.1736 + prefs->RemoveObserver("media.navigator.video.default_height", this); 1.1737 + prefs->RemoveObserver("media.navigator.video.default_fps", this); 1.1738 + prefs->RemoveObserver("media.navigator.video.default_minfps", this); 1.1739 + } 1.1740 + 1.1741 + // Close off any remaining active windows. 1.1742 + { 1.1743 + MutexAutoLock lock(mMutex); 1.1744 + GetActiveWindows()->Clear(); 1.1745 + mActiveCallbacks.Clear(); 1.1746 + mCallIds.Clear(); 1.1747 + LOG(("Releasing MediaManager singleton and thread")); 1.1748 + // Note: won't be released immediately as the Observer has a ref to us 1.1749 + sSingleton = nullptr; 1.1750 + mBackend = nullptr; 1.1751 + } 1.1752 + 1.1753 + return NS_OK; 1.1754 + 1.1755 + } else if (!strcmp(aTopic, "getUserMedia:response:allow")) { 1.1756 + nsString key(aData); 1.1757 + nsRefPtr<GetUserMediaRunnable> runnable; 1.1758 + if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) { 1.1759 + return NS_OK; 1.1760 + } 1.1761 + mActiveCallbacks.Remove(key); 1.1762 + 1.1763 + if (aSubject) { 1.1764 + // A particular device or devices were chosen by the user. 1.1765 + // NOTE: does not allow setting a device to null; assumes nullptr 1.1766 + nsCOMPtr<nsISupportsArray> array(do_QueryInterface(aSubject)); 1.1767 + MOZ_ASSERT(array); 1.1768 + uint32_t len = 0; 1.1769 + array->Count(&len); 1.1770 + MOZ_ASSERT(len); 1.1771 + if (!len) { 1.1772 + // neither audio nor video were selected 1.1773 + runnable->Denied(NS_LITERAL_STRING("PERMISSION_DENIED")); 1.1774 + return NS_OK; 1.1775 + } 1.1776 + for (uint32_t i = 0; i < len; i++) { 1.1777 + nsCOMPtr<nsISupports> supports; 1.1778 + array->GetElementAt(i,getter_AddRefs(supports)); 1.1779 + nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supports)); 1.1780 + MOZ_ASSERT(device); // shouldn't be returning anything else... 1.1781 + if (device) { 1.1782 + nsString type; 1.1783 + device->GetType(type); 1.1784 + if (type.EqualsLiteral("video")) { 1.1785 + runnable->SetVideoDevice(static_cast<VideoDevice*>(device.get())); 1.1786 + } else if (type.EqualsLiteral("audio")) { 1.1787 + runnable->SetAudioDevice(static_cast<AudioDevice*>(device.get())); 1.1788 + } else { 1.1789 + NS_WARNING("Unknown device type in getUserMedia"); 1.1790 + } 1.1791 + } 1.1792 + } 1.1793 + } 1.1794 + 1.1795 + // Reuse the same thread to save memory. 1.1796 + mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); 1.1797 + return NS_OK; 1.1798 + 1.1799 + } else if (!strcmp(aTopic, "getUserMedia:response:deny")) { 1.1800 + nsString errorMessage(NS_LITERAL_STRING("PERMISSION_DENIED")); 1.1801 + 1.1802 + if (aSubject) { 1.1803 + nsCOMPtr<nsISupportsString> msg(do_QueryInterface(aSubject)); 1.1804 + MOZ_ASSERT(msg); 1.1805 + msg->GetData(errorMessage); 1.1806 + if (errorMessage.IsEmpty()) 1.1807 + errorMessage.Assign(NS_LITERAL_STRING("UNKNOWN_ERROR")); 1.1808 + } 1.1809 + 1.1810 + nsString key(aData); 1.1811 + nsRefPtr<GetUserMediaRunnable> runnable; 1.1812 + if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) { 1.1813 + return NS_OK; 1.1814 + } 1.1815 + mActiveCallbacks.Remove(key); 1.1816 + runnable->Denied(errorMessage); 1.1817 + return NS_OK; 1.1818 + 1.1819 + } else if (!strcmp(aTopic, "getUserMedia:revoke")) { 1.1820 + nsresult rv; 1.1821 + uint64_t windowID = nsString(aData).ToInteger64(&rv); 1.1822 + MOZ_ASSERT(NS_SUCCEEDED(rv)); 1.1823 + if (NS_SUCCEEDED(rv)) { 1.1824 + LOG(("Revoking MediaCapture access for window %llu",windowID)); 1.1825 + OnNavigation(windowID); 1.1826 + } 1.1827 + 1.1828 + return NS_OK; 1.1829 + } 1.1830 +#ifdef MOZ_WIDGET_GONK 1.1831 + else if (!strcmp(aTopic, "phone-state-changed")) { 1.1832 + nsString state(aData); 1.1833 + if (atoi((const char*)state.get()) == nsIAudioManager::PHONE_STATE_IN_CALL) { 1.1834 + StopMediaStreams(); 1.1835 + } 1.1836 + return NS_OK; 1.1837 + } 1.1838 +#endif 1.1839 + 1.1840 + return NS_OK; 1.1841 +} 1.1842 + 1.1843 +static PLDHashOperator 1.1844 +WindowsHashToArrayFunc (const uint64_t& aId, 1.1845 + StreamListeners* aData, 1.1846 + void *userArg) 1.1847 +{ 1.1848 + nsISupportsArray *array = 1.1849 + static_cast<nsISupportsArray *>(userArg); 1.1850 + nsPIDOMWindow *window = static_cast<nsPIDOMWindow*> 1.1851 + (nsGlobalWindow::GetInnerWindowWithId(aId)); 1.1852 + 1.1853 + MOZ_ASSERT(window); 1.1854 + if (window) { 1.1855 + // mActiveWindows contains both windows that have requested device 1.1856 + // access and windows that are currently capturing media. We want 1.1857 + // to return only the latter. See bug 975177. 1.1858 + bool capturing = false; 1.1859 + if (aData) { 1.1860 + uint32_t length = aData->Length(); 1.1861 + for (uint32_t i = 0; i < length; ++i) { 1.1862 + nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener = 1.1863 + aData->ElementAt(i); 1.1864 + if (listener->CapturingVideo() || listener->CapturingAudio()) { 1.1865 + capturing = true; 1.1866 + break; 1.1867 + } 1.1868 + } 1.1869 + } 1.1870 + 1.1871 + if (capturing) 1.1872 + array->AppendElement(window); 1.1873 + } 1.1874 + return PL_DHASH_NEXT; 1.1875 +} 1.1876 + 1.1877 + 1.1878 +nsresult 1.1879 +MediaManager::GetActiveMediaCaptureWindows(nsISupportsArray **aArray) 1.1880 +{ 1.1881 + MOZ_ASSERT(aArray); 1.1882 + nsISupportsArray *array; 1.1883 + nsresult rv = NS_NewISupportsArray(&array); // AddRefs 1.1884 + if (NS_FAILED(rv)) 1.1885 + return rv; 1.1886 + 1.1887 + mActiveWindows.EnumerateRead(WindowsHashToArrayFunc, array); 1.1888 + 1.1889 + *aArray = array; 1.1890 + return NS_OK; 1.1891 +} 1.1892 + 1.1893 +// XXX flags might be better... 1.1894 +struct CaptureWindowStateData { 1.1895 + bool *mVideo; 1.1896 + bool *mAudio; 1.1897 +}; 1.1898 + 1.1899 +static void 1.1900 +CaptureWindowStateCallback(MediaManager *aThis, 1.1901 + uint64_t aWindowID, 1.1902 + StreamListeners *aListeners, 1.1903 + void *aData) 1.1904 +{ 1.1905 + struct CaptureWindowStateData *data = (struct CaptureWindowStateData *) aData; 1.1906 + 1.1907 + if (aListeners) { 1.1908 + auto length = aListeners->Length(); 1.1909 + for (size_t i = 0; i < length; ++i) { 1.1910 + GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i); 1.1911 + 1.1912 + if (listener->CapturingVideo()) { 1.1913 + *data->mVideo = true; 1.1914 + } 1.1915 + if (listener->CapturingAudio()) { 1.1916 + *data->mAudio = true; 1.1917 + } 1.1918 + } 1.1919 + } 1.1920 +} 1.1921 + 1.1922 + 1.1923 +NS_IMETHODIMP 1.1924 +MediaManager::MediaCaptureWindowState(nsIDOMWindow* aWindow, bool* aVideo, 1.1925 + bool* aAudio) 1.1926 +{ 1.1927 + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); 1.1928 + struct CaptureWindowStateData data; 1.1929 + data.mVideo = aVideo; 1.1930 + data.mAudio = aAudio; 1.1931 + 1.1932 + *aVideo = false; 1.1933 + *aAudio = false; 1.1934 + 1.1935 + nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow); 1.1936 + if (piWin) { 1.1937 + IterateWindowListeners(piWin, CaptureWindowStateCallback, &data); 1.1938 + } 1.1939 +#ifdef DEBUG 1.1940 + LOG(("%s: window %lld capturing %s %s", __FUNCTION__, piWin ? piWin->WindowID() : -1, 1.1941 + *aVideo ? "video" : "", *aAudio ? "audio" : "")); 1.1942 +#endif 1.1943 + return NS_OK; 1.1944 +} 1.1945 + 1.1946 +// lets us do all sorts of things to the listeners 1.1947 +void 1.1948 +MediaManager::IterateWindowListeners(nsPIDOMWindow *aWindow, 1.1949 + WindowListenerCallback aCallback, 1.1950 + void *aData) 1.1951 +{ 1.1952 + // Iterate the docshell tree to find all the child windows, and for each 1.1953 + // invoke the callback 1.1954 + nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow); 1.1955 + if (piWin) { 1.1956 + if (piWin->IsInnerWindow() || piWin->GetCurrentInnerWindow()) { 1.1957 + uint64_t windowID; 1.1958 + if (piWin->IsInnerWindow()) { 1.1959 + windowID = piWin->WindowID(); 1.1960 + } else { 1.1961 + windowID = piWin->GetCurrentInnerWindow()->WindowID(); 1.1962 + } 1.1963 + StreamListeners* listeners = GetActiveWindows()->Get(windowID); 1.1964 + // pass listeners so it can modify/delete the list 1.1965 + (*aCallback)(this, windowID, listeners, aData); 1.1966 + } 1.1967 + 1.1968 + // iterate any children of *this* window (iframes, etc) 1.1969 + nsCOMPtr<nsIDocShell> docShell = piWin->GetDocShell(); 1.1970 + if (docShell) { 1.1971 + int32_t i, count; 1.1972 + docShell->GetChildCount(&count); 1.1973 + for (i = 0; i < count; ++i) { 1.1974 + nsCOMPtr<nsIDocShellTreeItem> item; 1.1975 + docShell->GetChildAt(i, getter_AddRefs(item)); 1.1976 + nsCOMPtr<nsPIDOMWindow> win = do_GetInterface(item); 1.1977 + 1.1978 + if (win) { 1.1979 + IterateWindowListeners(win, aCallback, aData); 1.1980 + } 1.1981 + } 1.1982 + } 1.1983 + } 1.1984 +} 1.1985 + 1.1986 +void 1.1987 +MediaManager::StopMediaStreams() 1.1988 +{ 1.1989 + nsCOMPtr<nsISupportsArray> array; 1.1990 + GetActiveMediaCaptureWindows(getter_AddRefs(array)); 1.1991 + uint32_t len; 1.1992 + array->Count(&len); 1.1993 + for (uint32_t i = 0; i < len; i++) { 1.1994 + nsCOMPtr<nsISupports> window; 1.1995 + array->GetElementAt(i, getter_AddRefs(window)); 1.1996 + nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(window)); 1.1997 + if (win) { 1.1998 + OnNavigation(win->WindowID()); 1.1999 + } 1.2000 + } 1.2001 +} 1.2002 + 1.2003 +// Can be invoked from EITHER MainThread or MSG thread 1.2004 +void 1.2005 +GetUserMediaCallbackMediaStreamListener::Invalidate() 1.2006 +{ 1.2007 + 1.2008 + nsRefPtr<MediaOperationRunnable> runnable; 1.2009 + // We can't take a chance on blocking here, so proxy this to another 1.2010 + // thread. 1.2011 + // Pass a ref to us (which is threadsafe) so it can query us for the 1.2012 + // source stream info. 1.2013 + runnable = new MediaOperationRunnable(MEDIA_STOP, 1.2014 + this, nullptr, nullptr, 1.2015 + mAudioSource, mVideoSource, 1.2016 + mFinished, mWindowID, nullptr); 1.2017 + mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); 1.2018 +} 1.2019 + 1.2020 +// Called from the MediaStreamGraph thread 1.2021 +void 1.2022 +GetUserMediaCallbackMediaStreamListener::NotifyFinished(MediaStreamGraph* aGraph) 1.2023 +{ 1.2024 + mFinished = true; 1.2025 + Invalidate(); // we know it's been activated 1.2026 + NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, this)); 1.2027 +} 1.2028 + 1.2029 +// Called from the MediaStreamGraph thread 1.2030 +// this can be in response to our own RemoveListener() (via ::Remove()), or 1.2031 +// because the DOM GC'd the DOMLocalMediaStream/etc we're attached to. 1.2032 +void 1.2033 +GetUserMediaCallbackMediaStreamListener::NotifyRemoved(MediaStreamGraph* aGraph) 1.2034 +{ 1.2035 + { 1.2036 + MutexAutoLock lock(mLock); // protect access to mRemoved 1.2037 + MM_LOG(("Listener removed by DOM Destroy(), mFinished = %d", (int) mFinished)); 1.2038 + mRemoved = true; 1.2039 + } 1.2040 + if (!mFinished) { 1.2041 + NotifyFinished(aGraph); 1.2042 + } 1.2043 +} 1.2044 + 1.2045 +NS_IMETHODIMP 1.2046 +GetUserMediaNotificationEvent::Run() 1.2047 +{ 1.2048 + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); 1.2049 + // Make sure mStream is cleared and our reference to the DOMMediaStream 1.2050 + // is dropped on the main thread, no matter what happens in this method. 1.2051 + // Otherwise this object might be destroyed off the main thread, 1.2052 + // releasing DOMMediaStream off the main thread, which is not allowed. 1.2053 + nsRefPtr<DOMMediaStream> stream = mStream.forget(); 1.2054 + 1.2055 + nsString msg; 1.2056 + switch (mStatus) { 1.2057 + case STARTING: 1.2058 + msg = NS_LITERAL_STRING("starting"); 1.2059 + stream->OnTracksAvailable(mOnTracksAvailableCallback.forget()); 1.2060 + break; 1.2061 + case STOPPING: 1.2062 + msg = NS_LITERAL_STRING("shutdown"); 1.2063 + if (mListener) { 1.2064 + mListener->SetStopped(); 1.2065 + } 1.2066 + break; 1.2067 + } 1.2068 + 1.2069 + nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID); 1.2070 + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); 1.2071 + 1.2072 + return MediaManager::NotifyRecordingStatusChange(window, msg, mIsAudio, mIsVideo); 1.2073 +} 1.2074 + 1.2075 +} // namespace mozilla