michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "MediaEngine.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/unused.h" michael@0: #include "nsIMediaManager.h" michael@0: michael@0: #include "nsHashKeys.h" michael@0: #include "nsGlobalWindow.h" michael@0: #include "nsClassHashtable.h" michael@0: #include "nsRefPtrHashtable.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPrefBranch.h" michael@0: michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIDOMNavigatorUserMedia.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "mozilla/dom/MediaStreamBinding.h" michael@0: #include "mozilla/dom/MediaStreamTrackBinding.h" michael@0: #include "prlog.h" michael@0: #include "DOMMediaStream.h" michael@0: michael@0: #ifdef MOZ_WEBRTC michael@0: #include "mtransport/runnable_utils.h" michael@0: #endif michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: #include "DOMCameraManager.h" michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: class MediaStreamConstraints; michael@0: class NavigatorUserMediaSuccessCallback; michael@0: class NavigatorUserMediaErrorCallback; michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* GetMediaManagerLog(); michael@0: #define MM_LOG(msg) PR_LOG(GetMediaManagerLog(), PR_LOG_DEBUG, msg) michael@0: #else michael@0: #define MM_LOG(msg) michael@0: #endif michael@0: michael@0: /** michael@0: * This class is an implementation of MediaStreamListener. This is used michael@0: * to Start() and Stop() the underlying MediaEngineSource when MediaStreams michael@0: * are assigned and deassigned in content. michael@0: */ michael@0: class GetUserMediaCallbackMediaStreamListener : public MediaStreamListener michael@0: { michael@0: public: michael@0: // Create in an inactive state michael@0: GetUserMediaCallbackMediaStreamListener(nsIThread *aThread, michael@0: uint64_t aWindowID) michael@0: : mMediaThread(aThread) michael@0: , mWindowID(aWindowID) michael@0: , mStopped(false) michael@0: , mFinished(false) michael@0: , mLock("mozilla::GUMCMSL") michael@0: , mRemoved(false) {} michael@0: michael@0: ~GetUserMediaCallbackMediaStreamListener() michael@0: { michael@0: // It's OK to release mStream on any thread; they have thread-safe michael@0: // refcounts. michael@0: } michael@0: michael@0: void Activate(already_AddRefed aStream, michael@0: MediaEngineSource* aAudioSource, michael@0: MediaEngineSource* aVideoSource) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: mStream = aStream; michael@0: mAudioSource = aAudioSource; michael@0: mVideoSource = aVideoSource; michael@0: mLastEndTimeAudio = 0; michael@0: mLastEndTimeVideo = 0; michael@0: michael@0: mStream->AddListener(this); michael@0: } michael@0: michael@0: MediaStream *Stream() // Can be used to test if Activate was called michael@0: { michael@0: return mStream; michael@0: } michael@0: SourceMediaStream *GetSourceStream() michael@0: { michael@0: NS_ASSERTION(mStream,"Getting stream from never-activated GUMCMSListener"); michael@0: if (!mStream) { michael@0: return nullptr; michael@0: } michael@0: return mStream->AsSourceStream(); michael@0: } michael@0: michael@0: // mVideo/AudioSource are set by Activate(), so we assume they're capturing michael@0: // if set and represent a real capture device. michael@0: bool CapturingVideo() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: return mVideoSource && !mStopped && michael@0: (!mVideoSource->IsFake() || michael@0: Preferences::GetBool("media.navigator.permission.fake")); michael@0: } michael@0: bool CapturingAudio() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: return mAudioSource && !mStopped && michael@0: (!mAudioSource->IsFake() || michael@0: Preferences::GetBool("media.navigator.permission.fake")); michael@0: } michael@0: michael@0: void SetStopped() michael@0: { michael@0: mStopped = true; michael@0: } michael@0: michael@0: // implement in .cpp to avoid circular dependency with MediaOperationRunnable michael@0: // Can be invoked from EITHER MainThread or MSG thread michael@0: void Invalidate(); michael@0: michael@0: void michael@0: AudioConfig(bool aEchoOn, uint32_t aEcho, michael@0: bool aAgcOn, uint32_t aAGC, michael@0: bool aNoiseOn, uint32_t aNoise, michael@0: int32_t aPlayoutDelay) michael@0: { michael@0: if (mAudioSource) { michael@0: #ifdef MOZ_WEBRTC michael@0: // Right now these configs are only of use if webrtc is available michael@0: RUN_ON_THREAD(mMediaThread, michael@0: WrapRunnable(nsRefPtr(mAudioSource), // threadsafe michael@0: &MediaEngineSource::Config, michael@0: aEchoOn, aEcho, aAgcOn, aAGC, aNoiseOn, aNoise, aPlayoutDelay), michael@0: NS_DISPATCH_NORMAL); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: void michael@0: Remove() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); michael@0: // allow calling even if inactive (!mStream) for easier cleanup michael@0: // Caller holds strong reference to us, so no death grip required michael@0: MutexAutoLock lock(mLock); // protect access to mRemoved michael@0: if (mStream && !mRemoved) { michael@0: MM_LOG(("Listener removed on purpose, mFinished = %d", (int) mFinished)); michael@0: mRemoved = true; // RemoveListener is async, avoid races michael@0: // If it's destroyed, don't call - listener will be removed and we'll be notified! michael@0: if (!mStream->IsDestroyed()) { michael@0: mStream->RemoveListener(this); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Proxy NotifyPull() to sources michael@0: virtual void michael@0: NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) MOZ_OVERRIDE michael@0: { michael@0: // Currently audio sources ignore NotifyPull, but they could michael@0: // watch it especially for fake audio. michael@0: if (mAudioSource) { michael@0: mAudioSource->NotifyPull(aGraph, mStream, kAudioTrack, aDesiredTime, mLastEndTimeAudio); michael@0: } michael@0: if (mVideoSource) { michael@0: mVideoSource->NotifyPull(aGraph, mStream, kVideoTrack, aDesiredTime, mLastEndTimeVideo); michael@0: } michael@0: } michael@0: michael@0: virtual void michael@0: NotifyFinished(MediaStreamGraph* aGraph) MOZ_OVERRIDE; michael@0: michael@0: virtual void michael@0: NotifyRemoved(MediaStreamGraph* aGraph) MOZ_OVERRIDE; michael@0: michael@0: private: michael@0: // Set at construction michael@0: nsCOMPtr mMediaThread; michael@0: uint64_t mWindowID; michael@0: michael@0: bool mStopped; // MainThread only michael@0: michael@0: // Set at Activate on MainThread michael@0: michael@0: // Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread michael@0: // No locking needed as they're only addrefed except on the MediaManager thread michael@0: nsRefPtr mAudioSource; // threadsafe refcnt michael@0: nsRefPtr mVideoSource; // threadsafe refcnt michael@0: nsRefPtr mStream; // threadsafe refcnt michael@0: TrackTicks mLastEndTimeAudio; michael@0: TrackTicks mLastEndTimeVideo; michael@0: bool mFinished; michael@0: michael@0: // Accessed from MainThread and MSG thread michael@0: Mutex mLock; // protects mRemoved access from MainThread michael@0: bool mRemoved; michael@0: }; michael@0: michael@0: class GetUserMediaNotificationEvent: public nsRunnable michael@0: { michael@0: public: michael@0: enum GetUserMediaStatus { michael@0: STARTING, michael@0: STOPPING michael@0: }; michael@0: GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener, michael@0: GetUserMediaStatus aStatus, michael@0: bool aIsAudio, bool aIsVideo, uint64_t aWindowID) michael@0: : mListener(aListener) , mStatus(aStatus) , mIsAudio(aIsAudio) michael@0: , mIsVideo(aIsVideo), mWindowID(aWindowID) {} michael@0: michael@0: GetUserMediaNotificationEvent(GetUserMediaStatus aStatus, michael@0: already_AddRefed aStream, michael@0: DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback, michael@0: bool aIsAudio, bool aIsVideo, uint64_t aWindowID, michael@0: already_AddRefed aError) michael@0: : mStream(aStream), mOnTracksAvailableCallback(aOnTracksAvailableCallback), michael@0: mStatus(aStatus), mIsAudio(aIsAudio), mIsVideo(aIsVideo), mWindowID(aWindowID), michael@0: mError(aError) {} michael@0: virtual ~GetUserMediaNotificationEvent() michael@0: { michael@0: michael@0: } michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE; michael@0: michael@0: protected: michael@0: nsRefPtr mListener; // threadsafe michael@0: nsRefPtr mStream; michael@0: nsAutoPtr mOnTracksAvailableCallback; michael@0: GetUserMediaStatus mStatus; michael@0: bool mIsAudio; michael@0: bool mIsVideo; michael@0: uint64_t mWindowID; michael@0: nsRefPtr mError; michael@0: }; michael@0: michael@0: typedef enum { michael@0: MEDIA_START, michael@0: MEDIA_STOP michael@0: } MediaOperation; michael@0: michael@0: class MediaManager; michael@0: class GetUserMediaRunnable; michael@0: michael@0: /** michael@0: * Send an error back to content. The error is the form a string. michael@0: * Do this only on the main thread. The success callback is also passed here michael@0: * so it can be released correctly. michael@0: */ michael@0: class ErrorCallbackRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: ErrorCallbackRunnable( michael@0: nsCOMPtr& aSuccess, michael@0: nsCOMPtr& aError, michael@0: const nsAString& aErrorMsg, uint64_t aWindowID); michael@0: NS_IMETHOD Run(); michael@0: private: michael@0: ~ErrorCallbackRunnable(); michael@0: michael@0: nsCOMPtr mSuccess; michael@0: nsCOMPtr mError; michael@0: const nsString mErrorMsg; michael@0: uint64_t mWindowID; michael@0: nsRefPtr mManager; // get ref to this when creating the runnable michael@0: }; michael@0: michael@0: class ReleaseMediaOperationResource : public nsRunnable michael@0: { michael@0: public: michael@0: ReleaseMediaOperationResource(already_AddRefed aStream, michael@0: DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback): michael@0: mStream(aStream), michael@0: mOnTracksAvailableCallback(aOnTracksAvailableCallback) {} michael@0: NS_IMETHOD Run() MOZ_OVERRIDE {return NS_OK;} michael@0: private: michael@0: nsRefPtr mStream; michael@0: nsAutoPtr mOnTracksAvailableCallback; michael@0: }; michael@0: michael@0: // Generic class for running long media operations like Start off the main michael@0: // thread, and then (because nsDOMMediaStreams aren't threadsafe), michael@0: // ProxyReleases mStream since it's cycle collected. michael@0: class MediaOperationRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: // so we can send Stop without AddRef()ing from the MSG thread michael@0: MediaOperationRunnable(MediaOperation aType, michael@0: GetUserMediaCallbackMediaStreamListener* aListener, michael@0: DOMMediaStream* aStream, michael@0: DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback, michael@0: MediaEngineSource* aAudioSource, michael@0: MediaEngineSource* aVideoSource, michael@0: bool aNeedsFinish, michael@0: uint64_t aWindowID, michael@0: already_AddRefed aError) michael@0: : mType(aType) michael@0: , mStream(aStream) michael@0: , mOnTracksAvailableCallback(aOnTracksAvailableCallback) michael@0: , mAudioSource(aAudioSource) michael@0: , mVideoSource(aVideoSource) michael@0: , mListener(aListener) michael@0: , mFinish(aNeedsFinish) michael@0: , mWindowID(aWindowID) michael@0: , mError(aError) michael@0: {} michael@0: michael@0: ~MediaOperationRunnable() michael@0: { michael@0: // MediaStreams can be released on any thread. michael@0: } michael@0: michael@0: nsresult returnAndCallbackError(nsresult rv, const char* errorLog) michael@0: { michael@0: MM_LOG(("%s , rv=%d", errorLog, rv)); michael@0: NS_DispatchToMainThread(new ReleaseMediaOperationResource(mStream.forget(), michael@0: mOnTracksAvailableCallback.forget())); michael@0: nsString log; michael@0: michael@0: log.AssignASCII(errorLog, strlen(errorLog)); michael@0: nsCOMPtr success; michael@0: NS_DispatchToMainThread(new ErrorCallbackRunnable(success, mError, michael@0: log, mWindowID)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD michael@0: Run() MOZ_OVERRIDE michael@0: { michael@0: SourceMediaStream *source = mListener->GetSourceStream(); michael@0: // No locking between these is required as all the callbacks for the michael@0: // same MediaStream will occur on the same thread. michael@0: if (!source) // means the stream was never Activated() michael@0: return NS_OK; michael@0: michael@0: switch (mType) { michael@0: case MEDIA_START: michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread"); michael@0: nsresult rv; michael@0: michael@0: source->SetPullEnabled(true); michael@0: michael@0: DOMMediaStream::TrackTypeHints expectedTracks = 0; michael@0: if (mAudioSource) { michael@0: rv = mAudioSource->Start(source, kAudioTrack); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: expectedTracks |= DOMMediaStream::HINT_CONTENTS_AUDIO; michael@0: } else { michael@0: return returnAndCallbackError(rv, "Starting audio failed"); michael@0: } michael@0: } michael@0: if (mVideoSource) { michael@0: rv = mVideoSource->Start(source, kVideoTrack); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: expectedTracks |= DOMMediaStream::HINT_CONTENTS_VIDEO; michael@0: } else { michael@0: return returnAndCallbackError(rv, "Starting video failed"); michael@0: } michael@0: } michael@0: michael@0: mOnTracksAvailableCallback->SetExpectedTracks(expectedTracks); michael@0: michael@0: MM_LOG(("started all sources")); michael@0: // Forward mOnTracksAvailableCallback to GetUserMediaNotificationEvent, michael@0: // because mOnTracksAvailableCallback needs to be added to mStream michael@0: // on the main thread. michael@0: nsIRunnable *event = michael@0: new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING, michael@0: mStream.forget(), michael@0: mOnTracksAvailableCallback.forget(), michael@0: mAudioSource != nullptr, michael@0: mVideoSource != nullptr, michael@0: mWindowID, mError.forget()); michael@0: // event must always be released on mainthread due to the JS callbacks michael@0: // in the TracksAvailableCallback michael@0: NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: break; michael@0: michael@0: case MEDIA_STOP: michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread"); michael@0: if (mAudioSource) { michael@0: mAudioSource->Stop(source, kAudioTrack); michael@0: mAudioSource->Deallocate(); michael@0: } michael@0: if (mVideoSource) { michael@0: mVideoSource->Stop(source, kVideoTrack); michael@0: mVideoSource->Deallocate(); michael@0: } michael@0: // Do this after stopping all tracks with EndTrack() michael@0: if (mFinish) { michael@0: source->Finish(); michael@0: } michael@0: nsIRunnable *event = michael@0: new GetUserMediaNotificationEvent(mListener, michael@0: GetUserMediaNotificationEvent::STOPPING, michael@0: mAudioSource != nullptr, michael@0: mVideoSource != nullptr, michael@0: mWindowID); michael@0: // event must always be released on mainthread due to the JS callbacks michael@0: // in the TracksAvailableCallback michael@0: NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: MOZ_ASSERT(false,"invalid MediaManager operation"); michael@0: break; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: MediaOperation mType; michael@0: nsRefPtr mStream; michael@0: nsAutoPtr mOnTracksAvailableCallback; michael@0: nsRefPtr mAudioSource; // threadsafe michael@0: nsRefPtr mVideoSource; // threadsafe michael@0: nsRefPtr mListener; // threadsafe michael@0: bool mFinish; michael@0: uint64_t mWindowID; michael@0: nsCOMPtr mError; michael@0: }; michael@0: michael@0: typedef nsTArray > StreamListeners; michael@0: typedef nsClassHashtable WindowTable; michael@0: michael@0: class MediaDevice : public nsIMediaDevice michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIMEDIADEVICE michael@0: michael@0: static MediaDevice* Create(MediaEngineVideoSource* source); michael@0: static MediaDevice* Create(MediaEngineAudioSource* source); michael@0: michael@0: virtual ~MediaDevice() {} michael@0: protected: michael@0: MediaDevice(MediaEngineSource* aSource); michael@0: nsString mName; michael@0: nsString mID; michael@0: bool mHasFacingMode; michael@0: dom::VideoFacingModeEnum mFacingMode; michael@0: nsRefPtr mSource; michael@0: }; michael@0: michael@0: class VideoDevice : public MediaDevice michael@0: { michael@0: public: michael@0: VideoDevice(MediaEngineVideoSource* aSource); michael@0: NS_IMETHOD GetType(nsAString& aType); michael@0: MediaEngineVideoSource* GetSource(); michael@0: }; michael@0: michael@0: class AudioDevice : public MediaDevice michael@0: { michael@0: public: michael@0: AudioDevice(MediaEngineAudioSource* aSource); michael@0: NS_IMETHOD GetType(nsAString& aType); michael@0: MediaEngineAudioSource* GetSource(); michael@0: }; michael@0: michael@0: // we could add MediaManager if needed michael@0: typedef void (*WindowListenerCallback)(MediaManager *aThis, michael@0: uint64_t aWindowID, michael@0: StreamListeners *aListeners, michael@0: void *aData); michael@0: michael@0: class MediaManager MOZ_FINAL : public nsIMediaManagerService, michael@0: public nsIObserver michael@0: { michael@0: public: michael@0: static already_AddRefed GetInstance(); michael@0: michael@0: // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager michael@0: // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread michael@0: // from MediaManager thread. michael@0: static MediaManager* Get(); michael@0: michael@0: static bool Exists() michael@0: { michael@0: return !!sSingleton; michael@0: } michael@0: michael@0: static nsIThread* GetThread() { michael@0: return Get()->mMediaThread; michael@0: } michael@0: michael@0: static nsresult NotifyRecordingStatusChange(nsPIDOMWindow* aWindow, michael@0: const nsString& aMsg, michael@0: const bool& aIsAudio, michael@0: const bool& aIsVideo); michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: NS_DECL_NSIMEDIAMANAGERSERVICE michael@0: michael@0: MediaEngine* GetBackend(uint64_t aWindowId = 0); michael@0: StreamListeners *GetWindowListeners(uint64_t aWindowId) { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only access windowlist on main thread"); michael@0: michael@0: return mActiveWindows.Get(aWindowId); michael@0: } michael@0: void RemoveWindowID(uint64_t aWindowId) { michael@0: mActiveWindows.Remove(aWindowId); michael@0: } michael@0: bool IsWindowStillActive(uint64_t aWindowId) { michael@0: return !!GetWindowListeners(aWindowId); michael@0: } michael@0: // Note: also calls aListener->Remove(), even if inactive michael@0: void RemoveFromWindowList(uint64_t aWindowID, michael@0: GetUserMediaCallbackMediaStreamListener *aListener); michael@0: michael@0: nsresult GetUserMedia(bool aPrivileged, michael@0: nsPIDOMWindow* aWindow, michael@0: const dom::MediaStreamConstraints& aRawConstraints, michael@0: nsIDOMGetUserMediaSuccessCallback* onSuccess, michael@0: nsIDOMGetUserMediaErrorCallback* onError); michael@0: michael@0: nsresult GetUserMediaDevices(nsPIDOMWindow* aWindow, michael@0: const dom::MediaStreamConstraints& aConstraints, michael@0: nsIGetUserMediaDevicesSuccessCallback* onSuccess, michael@0: nsIDOMGetUserMediaErrorCallback* onError, michael@0: uint64_t aInnerWindowID = 0); michael@0: void OnNavigation(uint64_t aWindowID); michael@0: michael@0: MediaEnginePrefs mPrefs; michael@0: michael@0: private: michael@0: WindowTable *GetActiveWindows() { michael@0: NS_ASSERTION(NS_IsMainThread(), "Only access windowlist on main thread"); michael@0: return &mActiveWindows; michael@0: } michael@0: michael@0: void GetPref(nsIPrefBranch *aBranch, const char *aPref, michael@0: const char *aData, int32_t *aVal); michael@0: void GetPrefBool(nsIPrefBranch *aBranch, const char *aPref, michael@0: const char *aData, bool *aVal); michael@0: void GetPrefs(nsIPrefBranch *aBranch, const char *aData); michael@0: michael@0: // Make private because we want only one instance of this class michael@0: MediaManager(); michael@0: michael@0: ~MediaManager() {} michael@0: michael@0: void IterateWindowListeners(nsPIDOMWindow *aWindow, michael@0: WindowListenerCallback aCallback, michael@0: void *aData); michael@0: michael@0: void StopMediaStreams(); michael@0: michael@0: // ONLY access from MainThread so we don't need to lock michael@0: WindowTable mActiveWindows; michael@0: nsRefPtrHashtable mActiveCallbacks; michael@0: nsClassHashtable> mCallIds; michael@0: // Always exists michael@0: nsCOMPtr mMediaThread; michael@0: michael@0: Mutex mMutex; michael@0: // protected with mMutex: michael@0: RefPtr mBackend; michael@0: michael@0: static StaticRefPtr sSingleton; michael@0: michael@0: #ifdef MOZ_B2G_CAMERA michael@0: nsRefPtr mCameraManager; michael@0: #endif michael@0: }; michael@0: michael@0: } // namespace mozilla