dom/media/MediaManager.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial