Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 |