mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 14:45:29 +00:00
455 lines
13 KiB
C++
455 lines
13 KiB
C++
/* 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/dom/ContentChild.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 "nsObserverService.h"
|
|
#include "nsIPrefService.h"
|
|
#include "nsIPrefBranch.h"
|
|
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsIDOMNavigatorUserMedia.h"
|
|
#include "nsXULAppAPI.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "prlog.h"
|
|
|
|
#ifdef MOZ_WEBRTC
|
|
#include "mtransport/runnable_utils.h"
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
|
|
#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<SourceMediaStream> 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
|
|
bool CapturingVideo()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
|
return mVideoSource && !mStopped;
|
|
}
|
|
bool CapturingAudio()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
|
return mAudioSource && !mStopped;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (mAudioSource) {
|
|
#ifdef MOZ_WEBRTC
|
|
// Right now these configs are only of use if webrtc is available
|
|
RUN_ON_THREAD(mMediaThread,
|
|
WrapRunnable(nsRefPtr<MediaEngineSource>(mAudioSource), // threadsafe
|
|
&MediaEngineSource::Config,
|
|
aEchoOn, aEcho, aAgcOn, aAGC, aNoiseOn, aNoise),
|
|
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<nsIThread> 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<MediaEngineSource> mAudioSource; // threadsafe refcnt
|
|
nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe refcnt
|
|
nsRefPtr<SourceMediaStream> 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)
|
|
: mListener(aListener), mStatus(aStatus) {}
|
|
|
|
GetUserMediaNotificationEvent(GetUserMediaStatus aStatus)
|
|
: mListener(nullptr), mStatus(aStatus) {}
|
|
|
|
NS_IMETHOD
|
|
Run()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (!obs) {
|
|
NS_WARNING("Could not get the Observer service for GetUserMedia recording notification.");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
nsString msg;
|
|
switch (mStatus) {
|
|
case STARTING:
|
|
msg = NS_LITERAL_STRING("starting");
|
|
break;
|
|
case STOPPING:
|
|
msg = NS_LITERAL_STRING("shutdown");
|
|
if (mListener) {
|
|
mListener->SetStopped();
|
|
}
|
|
break;
|
|
}
|
|
obs->NotifyObservers(nullptr,
|
|
"recording-device-events",
|
|
msg.get());
|
|
// Forward recording events to parent process.
|
|
// The events are gathered in chrome process and used for recording indicator
|
|
if (XRE_GetProcessType() != GeckoProcessType_Default) {
|
|
unused << mozilla::dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(msg);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
protected:
|
|
nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
|
|
GetUserMediaStatus mStatus;
|
|
};
|
|
|
|
typedef enum {
|
|
MEDIA_START,
|
|
MEDIA_STOP
|
|
} MediaOperation;
|
|
|
|
// 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,
|
|
MediaEngineSource* aAudioSource,
|
|
MediaEngineSource* aVideoSource,
|
|
bool aNeedsFinish)
|
|
: mType(aType)
|
|
, mAudioSource(aAudioSource)
|
|
, mVideoSource(aVideoSource)
|
|
, mListener(aListener)
|
|
, mFinish(aNeedsFinish)
|
|
{}
|
|
|
|
~MediaOperationRunnable()
|
|
{
|
|
// MediaStreams can be released on any thread.
|
|
}
|
|
|
|
NS_IMETHOD
|
|
Run()
|
|
{
|
|
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);
|
|
|
|
if (mAudioSource) {
|
|
rv = mAudioSource->Start(source, kAudioTrack);
|
|
if (NS_FAILED(rv)) {
|
|
MM_LOG(("Starting audio failed, rv=%d",rv));
|
|
}
|
|
}
|
|
if (mVideoSource) {
|
|
rv = mVideoSource->Start(source, kVideoTrack);
|
|
if (NS_FAILED(rv)) {
|
|
MM_LOG(("Starting video failed, rv=%d",rv));
|
|
}
|
|
}
|
|
|
|
MM_LOG(("started all sources"));
|
|
nsRefPtr<GetUserMediaNotificationEvent> event =
|
|
new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING);
|
|
|
|
|
|
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();
|
|
}
|
|
nsRefPtr<GetUserMediaNotificationEvent> event =
|
|
new GetUserMediaNotificationEvent(mListener, GetUserMediaNotificationEvent::STOPPING);
|
|
|
|
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
MOZ_ASSERT(false,"invalid MediaManager operation");
|
|
break;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
MediaOperation mType;
|
|
nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe
|
|
nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe
|
|
nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
|
|
bool mFinish;
|
|
};
|
|
|
|
typedef nsTArray<nsRefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
|
|
typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
|
|
|
|
class MediaDevice : public nsIMediaDevice
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIMEDIADEVICE
|
|
|
|
MediaDevice(MediaEngineVideoSource* aSource) {
|
|
mSource = aSource;
|
|
mType.Assign(NS_LITERAL_STRING("video"));
|
|
mSource->GetName(mName);
|
|
mSource->GetUUID(mID);
|
|
}
|
|
MediaDevice(MediaEngineAudioSource* aSource) {
|
|
mSource = aSource;
|
|
mType.Assign(NS_LITERAL_STRING("audio"));
|
|
mSource->GetName(mName);
|
|
mSource->GetUUID(mID);
|
|
}
|
|
virtual ~MediaDevice() {}
|
|
|
|
MediaEngineSource* GetSource();
|
|
private:
|
|
nsString mName;
|
|
nsString mType;
|
|
nsString mID;
|
|
nsRefPtr<MediaEngineSource> mSource;
|
|
};
|
|
|
|
class MediaManager MOZ_FINAL : public nsIMediaManagerService,
|
|
public nsIObserver
|
|
{
|
|
public:
|
|
static already_AddRefed<MediaManager> 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 nsIThread* GetThread() {
|
|
return Get()->mMediaThread;
|
|
}
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIOBSERVER
|
|
NS_DECL_NSIMEDIAMANAGERSERVICE
|
|
|
|
MediaEngine* GetBackend();
|
|
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,
|
|
nsIMediaStreamOptions* aParams,
|
|
nsIDOMGetUserMediaSuccessCallback* onSuccess,
|
|
nsIDOMGetUserMediaErrorCallback* onError);
|
|
nsresult GetUserMediaDevices(nsPIDOMWindow* aWindow,
|
|
nsIGetUserMediaDevicesSuccessCallback* onSuccess,
|
|
nsIDOMGetUserMediaErrorCallback* onError);
|
|
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 GetPrefs(nsIPrefBranch *aBranch, const char *aData);
|
|
|
|
// Make private because we want only one instance of this class
|
|
MediaManager();
|
|
|
|
~MediaManager() {
|
|
delete mBackend;
|
|
}
|
|
|
|
nsresult MediaCaptureWindowStateInternal(nsIDOMWindow* aWindow, bool* aVideo,
|
|
bool* aAudio);
|
|
|
|
// ONLY access from MainThread so we don't need to lock
|
|
WindowTable mActiveWindows;
|
|
nsRefPtrHashtable<nsStringHashKey, nsRunnable> mActiveCallbacks;
|
|
// Always exists
|
|
nsCOMPtr<nsIThread> mMediaThread;
|
|
|
|
Mutex mMutex;
|
|
// protected with mMutex:
|
|
MediaEngine* mBackend;
|
|
|
|
static StaticRefPtr<MediaManager> sSingleton;
|
|
};
|
|
|
|
} // namespace mozilla
|