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