dom/media/MediaManager.h

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:046fed412c50
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include "MediaEngine.h"
6 #include "mozilla/Services.h"
7 #include "mozilla/unused.h"
8 #include "nsIMediaManager.h"
9
10 #include "nsHashKeys.h"
11 #include "nsGlobalWindow.h"
12 #include "nsClassHashtable.h"
13 #include "nsRefPtrHashtable.h"
14 #include "nsIObserver.h"
15 #include "nsIPrefService.h"
16 #include "nsIPrefBranch.h"
17
18 #include "nsPIDOMWindow.h"
19 #include "nsIDOMNavigatorUserMedia.h"
20 #include "nsXULAppAPI.h"
21 #include "mozilla/Attributes.h"
22 #include "mozilla/Preferences.h"
23 #include "mozilla/StaticPtr.h"
24 #include "mozilla/dom/MediaStreamBinding.h"
25 #include "mozilla/dom/MediaStreamTrackBinding.h"
26 #include "prlog.h"
27 #include "DOMMediaStream.h"
28
29 #ifdef MOZ_WEBRTC
30 #include "mtransport/runnable_utils.h"
31 #endif
32
33 #ifdef MOZ_WIDGET_GONK
34 #include "DOMCameraManager.h"
35 #endif
36
37 namespace mozilla {
38 namespace dom {
39 class MediaStreamConstraints;
40 class NavigatorUserMediaSuccessCallback;
41 class NavigatorUserMediaErrorCallback;
42 }
43
44 #ifdef PR_LOGGING
45 extern PRLogModuleInfo* GetMediaManagerLog();
46 #define MM_LOG(msg) PR_LOG(GetMediaManagerLog(), PR_LOG_DEBUG, msg)
47 #else
48 #define MM_LOG(msg)
49 #endif
50
51 /**
52 * This class is an implementation of MediaStreamListener. This is used
53 * to Start() and Stop() the underlying MediaEngineSource when MediaStreams
54 * are assigned and deassigned in content.
55 */
56 class GetUserMediaCallbackMediaStreamListener : public MediaStreamListener
57 {
58 public:
59 // Create in an inactive state
60 GetUserMediaCallbackMediaStreamListener(nsIThread *aThread,
61 uint64_t aWindowID)
62 : mMediaThread(aThread)
63 , mWindowID(aWindowID)
64 , mStopped(false)
65 , mFinished(false)
66 , mLock("mozilla::GUMCMSL")
67 , mRemoved(false) {}
68
69 ~GetUserMediaCallbackMediaStreamListener()
70 {
71 // It's OK to release mStream on any thread; they have thread-safe
72 // refcounts.
73 }
74
75 void Activate(already_AddRefed<SourceMediaStream> aStream,
76 MediaEngineSource* aAudioSource,
77 MediaEngineSource* aVideoSource)
78 {
79 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
80 mStream = aStream;
81 mAudioSource = aAudioSource;
82 mVideoSource = aVideoSource;
83 mLastEndTimeAudio = 0;
84 mLastEndTimeVideo = 0;
85
86 mStream->AddListener(this);
87 }
88
89 MediaStream *Stream() // Can be used to test if Activate was called
90 {
91 return mStream;
92 }
93 SourceMediaStream *GetSourceStream()
94 {
95 NS_ASSERTION(mStream,"Getting stream from never-activated GUMCMSListener");
96 if (!mStream) {
97 return nullptr;
98 }
99 return mStream->AsSourceStream();
100 }
101
102 // mVideo/AudioSource are set by Activate(), so we assume they're capturing
103 // if set and represent a real capture device.
104 bool CapturingVideo()
105 {
106 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
107 return mVideoSource && !mStopped &&
108 (!mVideoSource->IsFake() ||
109 Preferences::GetBool("media.navigator.permission.fake"));
110 }
111 bool CapturingAudio()
112 {
113 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
114 return mAudioSource && !mStopped &&
115 (!mAudioSource->IsFake() ||
116 Preferences::GetBool("media.navigator.permission.fake"));
117 }
118
119 void SetStopped()
120 {
121 mStopped = true;
122 }
123
124 // implement in .cpp to avoid circular dependency with MediaOperationRunnable
125 // Can be invoked from EITHER MainThread or MSG thread
126 void Invalidate();
127
128 void
129 AudioConfig(bool aEchoOn, uint32_t aEcho,
130 bool aAgcOn, uint32_t aAGC,
131 bool aNoiseOn, uint32_t aNoise,
132 int32_t aPlayoutDelay)
133 {
134 if (mAudioSource) {
135 #ifdef MOZ_WEBRTC
136 // Right now these configs are only of use if webrtc is available
137 RUN_ON_THREAD(mMediaThread,
138 WrapRunnable(nsRefPtr<MediaEngineSource>(mAudioSource), // threadsafe
139 &MediaEngineSource::Config,
140 aEchoOn, aEcho, aAgcOn, aAGC, aNoiseOn, aNoise, aPlayoutDelay),
141 NS_DISPATCH_NORMAL);
142 #endif
143 }
144 }
145
146 void
147 Remove()
148 {
149 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
150 // allow calling even if inactive (!mStream) for easier cleanup
151 // Caller holds strong reference to us, so no death grip required
152 MutexAutoLock lock(mLock); // protect access to mRemoved
153 if (mStream && !mRemoved) {
154 MM_LOG(("Listener removed on purpose, mFinished = %d", (int) mFinished));
155 mRemoved = true; // RemoveListener is async, avoid races
156 // If it's destroyed, don't call - listener will be removed and we'll be notified!
157 if (!mStream->IsDestroyed()) {
158 mStream->RemoveListener(this);
159 }
160 }
161 }
162
163 // Proxy NotifyPull() to sources
164 virtual void
165 NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) MOZ_OVERRIDE
166 {
167 // Currently audio sources ignore NotifyPull, but they could
168 // watch it especially for fake audio.
169 if (mAudioSource) {
170 mAudioSource->NotifyPull(aGraph, mStream, kAudioTrack, aDesiredTime, mLastEndTimeAudio);
171 }
172 if (mVideoSource) {
173 mVideoSource->NotifyPull(aGraph, mStream, kVideoTrack, aDesiredTime, mLastEndTimeVideo);
174 }
175 }
176
177 virtual void
178 NotifyFinished(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
179
180 virtual void
181 NotifyRemoved(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
182
183 private:
184 // Set at construction
185 nsCOMPtr<nsIThread> mMediaThread;
186 uint64_t mWindowID;
187
188 bool mStopped; // MainThread only
189
190 // Set at Activate on MainThread
191
192 // Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
193 // No locking needed as they're only addrefed except on the MediaManager thread
194 nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe refcnt
195 nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe refcnt
196 nsRefPtr<SourceMediaStream> mStream; // threadsafe refcnt
197 TrackTicks mLastEndTimeAudio;
198 TrackTicks mLastEndTimeVideo;
199 bool mFinished;
200
201 // Accessed from MainThread and MSG thread
202 Mutex mLock; // protects mRemoved access from MainThread
203 bool mRemoved;
204 };
205
206 class GetUserMediaNotificationEvent: public nsRunnable
207 {
208 public:
209 enum GetUserMediaStatus {
210 STARTING,
211 STOPPING
212 };
213 GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener,
214 GetUserMediaStatus aStatus,
215 bool aIsAudio, bool aIsVideo, uint64_t aWindowID)
216 : mListener(aListener) , mStatus(aStatus) , mIsAudio(aIsAudio)
217 , mIsVideo(aIsVideo), mWindowID(aWindowID) {}
218
219 GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
220 already_AddRefed<DOMMediaStream> aStream,
221 DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
222 bool aIsAudio, bool aIsVideo, uint64_t aWindowID,
223 already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError)
224 : mStream(aStream), mOnTracksAvailableCallback(aOnTracksAvailableCallback),
225 mStatus(aStatus), mIsAudio(aIsAudio), mIsVideo(aIsVideo), mWindowID(aWindowID),
226 mError(aError) {}
227 virtual ~GetUserMediaNotificationEvent()
228 {
229
230 }
231
232 NS_IMETHOD Run() MOZ_OVERRIDE;
233
234 protected:
235 nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
236 nsRefPtr<DOMMediaStream> mStream;
237 nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
238 GetUserMediaStatus mStatus;
239 bool mIsAudio;
240 bool mIsVideo;
241 uint64_t mWindowID;
242 nsRefPtr<nsIDOMGetUserMediaErrorCallback> mError;
243 };
244
245 typedef enum {
246 MEDIA_START,
247 MEDIA_STOP
248 } MediaOperation;
249
250 class MediaManager;
251 class GetUserMediaRunnable;
252
253 /**
254 * Send an error back to content. The error is the form a string.
255 * Do this only on the main thread. The success callback is also passed here
256 * so it can be released correctly.
257 */
258 class ErrorCallbackRunnable : public nsRunnable
259 {
260 public:
261 ErrorCallbackRunnable(
262 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
263 nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
264 const nsAString& aErrorMsg, uint64_t aWindowID);
265 NS_IMETHOD Run();
266 private:
267 ~ErrorCallbackRunnable();
268
269 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mSuccess;
270 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
271 const nsString mErrorMsg;
272 uint64_t mWindowID;
273 nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
274 };
275
276 class ReleaseMediaOperationResource : public nsRunnable
277 {
278 public:
279 ReleaseMediaOperationResource(already_AddRefed<DOMMediaStream> aStream,
280 DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback):
281 mStream(aStream),
282 mOnTracksAvailableCallback(aOnTracksAvailableCallback) {}
283 NS_IMETHOD Run() MOZ_OVERRIDE {return NS_OK;}
284 private:
285 nsRefPtr<DOMMediaStream> mStream;
286 nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
287 };
288
289 // Generic class for running long media operations like Start off the main
290 // thread, and then (because nsDOMMediaStreams aren't threadsafe),
291 // ProxyReleases mStream since it's cycle collected.
292 class MediaOperationRunnable : public nsRunnable
293 {
294 public:
295 // so we can send Stop without AddRef()ing from the MSG thread
296 MediaOperationRunnable(MediaOperation aType,
297 GetUserMediaCallbackMediaStreamListener* aListener,
298 DOMMediaStream* aStream,
299 DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
300 MediaEngineSource* aAudioSource,
301 MediaEngineSource* aVideoSource,
302 bool aNeedsFinish,
303 uint64_t aWindowID,
304 already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError)
305 : mType(aType)
306 , mStream(aStream)
307 , mOnTracksAvailableCallback(aOnTracksAvailableCallback)
308 , mAudioSource(aAudioSource)
309 , mVideoSource(aVideoSource)
310 , mListener(aListener)
311 , mFinish(aNeedsFinish)
312 , mWindowID(aWindowID)
313 , mError(aError)
314 {}
315
316 ~MediaOperationRunnable()
317 {
318 // MediaStreams can be released on any thread.
319 }
320
321 nsresult returnAndCallbackError(nsresult rv, const char* errorLog)
322 {
323 MM_LOG(("%s , rv=%d", errorLog, rv));
324 NS_DispatchToMainThread(new ReleaseMediaOperationResource(mStream.forget(),
325 mOnTracksAvailableCallback.forget()));
326 nsString log;
327
328 log.AssignASCII(errorLog, strlen(errorLog));
329 nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success;
330 NS_DispatchToMainThread(new ErrorCallbackRunnable(success, mError,
331 log, mWindowID));
332 return NS_OK;
333 }
334
335 NS_IMETHOD
336 Run() MOZ_OVERRIDE
337 {
338 SourceMediaStream *source = mListener->GetSourceStream();
339 // No locking between these is required as all the callbacks for the
340 // same MediaStream will occur on the same thread.
341 if (!source) // means the stream was never Activated()
342 return NS_OK;
343
344 switch (mType) {
345 case MEDIA_START:
346 {
347 NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
348 nsresult rv;
349
350 source->SetPullEnabled(true);
351
352 DOMMediaStream::TrackTypeHints expectedTracks = 0;
353 if (mAudioSource) {
354 rv = mAudioSource->Start(source, kAudioTrack);
355 if (NS_SUCCEEDED(rv)) {
356 expectedTracks |= DOMMediaStream::HINT_CONTENTS_AUDIO;
357 } else {
358 return returnAndCallbackError(rv, "Starting audio failed");
359 }
360 }
361 if (mVideoSource) {
362 rv = mVideoSource->Start(source, kVideoTrack);
363 if (NS_SUCCEEDED(rv)) {
364 expectedTracks |= DOMMediaStream::HINT_CONTENTS_VIDEO;
365 } else {
366 return returnAndCallbackError(rv, "Starting video failed");
367 }
368 }
369
370 mOnTracksAvailableCallback->SetExpectedTracks(expectedTracks);
371
372 MM_LOG(("started all sources"));
373 // Forward mOnTracksAvailableCallback to GetUserMediaNotificationEvent,
374 // because mOnTracksAvailableCallback needs to be added to mStream
375 // on the main thread.
376 nsIRunnable *event =
377 new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING,
378 mStream.forget(),
379 mOnTracksAvailableCallback.forget(),
380 mAudioSource != nullptr,
381 mVideoSource != nullptr,
382 mWindowID, mError.forget());
383 // event must always be released on mainthread due to the JS callbacks
384 // in the TracksAvailableCallback
385 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
386 }
387 break;
388
389 case MEDIA_STOP:
390 {
391 NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
392 if (mAudioSource) {
393 mAudioSource->Stop(source, kAudioTrack);
394 mAudioSource->Deallocate();
395 }
396 if (mVideoSource) {
397 mVideoSource->Stop(source, kVideoTrack);
398 mVideoSource->Deallocate();
399 }
400 // Do this after stopping all tracks with EndTrack()
401 if (mFinish) {
402 source->Finish();
403 }
404 nsIRunnable *event =
405 new GetUserMediaNotificationEvent(mListener,
406 GetUserMediaNotificationEvent::STOPPING,
407 mAudioSource != nullptr,
408 mVideoSource != nullptr,
409 mWindowID);
410 // event must always be released on mainthread due to the JS callbacks
411 // in the TracksAvailableCallback
412 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
413 }
414 break;
415
416 default:
417 MOZ_ASSERT(false,"invalid MediaManager operation");
418 break;
419 }
420 return NS_OK;
421 }
422
423 private:
424 MediaOperation mType;
425 nsRefPtr<DOMMediaStream> mStream;
426 nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
427 nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe
428 nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe
429 nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
430 bool mFinish;
431 uint64_t mWindowID;
432 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mError;
433 };
434
435 typedef nsTArray<nsRefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
436 typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
437
438 class MediaDevice : public nsIMediaDevice
439 {
440 public:
441 NS_DECL_THREADSAFE_ISUPPORTS
442 NS_DECL_NSIMEDIADEVICE
443
444 static MediaDevice* Create(MediaEngineVideoSource* source);
445 static MediaDevice* Create(MediaEngineAudioSource* source);
446
447 virtual ~MediaDevice() {}
448 protected:
449 MediaDevice(MediaEngineSource* aSource);
450 nsString mName;
451 nsString mID;
452 bool mHasFacingMode;
453 dom::VideoFacingModeEnum mFacingMode;
454 nsRefPtr<MediaEngineSource> mSource;
455 };
456
457 class VideoDevice : public MediaDevice
458 {
459 public:
460 VideoDevice(MediaEngineVideoSource* aSource);
461 NS_IMETHOD GetType(nsAString& aType);
462 MediaEngineVideoSource* GetSource();
463 };
464
465 class AudioDevice : public MediaDevice
466 {
467 public:
468 AudioDevice(MediaEngineAudioSource* aSource);
469 NS_IMETHOD GetType(nsAString& aType);
470 MediaEngineAudioSource* GetSource();
471 };
472
473 // we could add MediaManager if needed
474 typedef void (*WindowListenerCallback)(MediaManager *aThis,
475 uint64_t aWindowID,
476 StreamListeners *aListeners,
477 void *aData);
478
479 class MediaManager MOZ_FINAL : public nsIMediaManagerService,
480 public nsIObserver
481 {
482 public:
483 static already_AddRefed<MediaManager> GetInstance();
484
485 // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
486 // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
487 // from MediaManager thread.
488 static MediaManager* Get();
489
490 static bool Exists()
491 {
492 return !!sSingleton;
493 }
494
495 static nsIThread* GetThread() {
496 return Get()->mMediaThread;
497 }
498
499 static nsresult NotifyRecordingStatusChange(nsPIDOMWindow* aWindow,
500 const nsString& aMsg,
501 const bool& aIsAudio,
502 const bool& aIsVideo);
503
504 NS_DECL_THREADSAFE_ISUPPORTS
505 NS_DECL_NSIOBSERVER
506 NS_DECL_NSIMEDIAMANAGERSERVICE
507
508 MediaEngine* GetBackend(uint64_t aWindowId = 0);
509 StreamListeners *GetWindowListeners(uint64_t aWindowId) {
510 NS_ASSERTION(NS_IsMainThread(), "Only access windowlist on main thread");
511
512 return mActiveWindows.Get(aWindowId);
513 }
514 void RemoveWindowID(uint64_t aWindowId) {
515 mActiveWindows.Remove(aWindowId);
516 }
517 bool IsWindowStillActive(uint64_t aWindowId) {
518 return !!GetWindowListeners(aWindowId);
519 }
520 // Note: also calls aListener->Remove(), even if inactive
521 void RemoveFromWindowList(uint64_t aWindowID,
522 GetUserMediaCallbackMediaStreamListener *aListener);
523
524 nsresult GetUserMedia(bool aPrivileged,
525 nsPIDOMWindow* aWindow,
526 const dom::MediaStreamConstraints& aRawConstraints,
527 nsIDOMGetUserMediaSuccessCallback* onSuccess,
528 nsIDOMGetUserMediaErrorCallback* onError);
529
530 nsresult GetUserMediaDevices(nsPIDOMWindow* aWindow,
531 const dom::MediaStreamConstraints& aConstraints,
532 nsIGetUserMediaDevicesSuccessCallback* onSuccess,
533 nsIDOMGetUserMediaErrorCallback* onError,
534 uint64_t aInnerWindowID = 0);
535 void OnNavigation(uint64_t aWindowID);
536
537 MediaEnginePrefs mPrefs;
538
539 private:
540 WindowTable *GetActiveWindows() {
541 NS_ASSERTION(NS_IsMainThread(), "Only access windowlist on main thread");
542 return &mActiveWindows;
543 }
544
545 void GetPref(nsIPrefBranch *aBranch, const char *aPref,
546 const char *aData, int32_t *aVal);
547 void GetPrefBool(nsIPrefBranch *aBranch, const char *aPref,
548 const char *aData, bool *aVal);
549 void GetPrefs(nsIPrefBranch *aBranch, const char *aData);
550
551 // Make private because we want only one instance of this class
552 MediaManager();
553
554 ~MediaManager() {}
555
556 void IterateWindowListeners(nsPIDOMWindow *aWindow,
557 WindowListenerCallback aCallback,
558 void *aData);
559
560 void StopMediaStreams();
561
562 // ONLY access from MainThread so we don't need to lock
563 WindowTable mActiveWindows;
564 nsRefPtrHashtable<nsStringHashKey, GetUserMediaRunnable> mActiveCallbacks;
565 nsClassHashtable<nsUint64HashKey, nsTArray<nsString>> mCallIds;
566 // Always exists
567 nsCOMPtr<nsIThread> mMediaThread;
568
569 Mutex mMutex;
570 // protected with mMutex:
571 RefPtr<MediaEngine> mBackend;
572
573 static StaticRefPtr<MediaManager> sSingleton;
574
575 #ifdef MOZ_B2G_CAMERA
576 nsRefPtr<nsDOMCameraManager> mCameraManager;
577 #endif
578 };
579
580 } // namespace mozilla

mercurial