зеркало из https://github.com/mozilla/gecko-dev.git
Bug 803799: Start gUM streams in Success callback; add MediaManager mutex r=anant,roc
This commit is contained in:
Родитель
c95ac38959
Коммит
f9529e846d
|
@ -139,6 +139,13 @@ MediaEngineWebRTCAudioSource::NotifyPull(MediaStreamGraph* aGraph,
|
|||
StreamTime aDesiredTime)
|
||||
{
|
||||
// Ignore - we push audio data
|
||||
#ifdef DEBUG
|
||||
static TrackTicks mLastEndTime = 0;
|
||||
TrackTicks target = TimeToTicksRoundUp(SAMPLE_FREQUENCY, aDesiredTime);
|
||||
TrackTicks delta = target - mLastEndTime;
|
||||
LOG(("Audio:NotifyPull: target %lu, delta %lu",(uint32_t) target, (uint32_t) delta));
|
||||
mLastEndTime = target;
|
||||
#endif
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -251,6 +251,7 @@ MediaEngineWebRTCVideoSource::Start(SourceMediaStream* aStream, TrackID aID)
|
|||
mSource->AddTrack(aID, USECS_PER_S, 0, new VideoSegment());
|
||||
mSource->AdvanceKnownTracksTime(STREAM_TIME_MAX);
|
||||
mLastEndTime = 0;
|
||||
mState = kStarted;
|
||||
|
||||
error = mViERender->AddRenderer(mCaptureIndex, webrtc::kVideoI420, (webrtc::ExternalRenderer*)this);
|
||||
if (error == -1) {
|
||||
|
@ -266,7 +267,6 @@ MediaEngineWebRTCVideoSource::Start(SourceMediaStream* aStream, TrackID aID)
|
|||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mState = kStarted;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,10 +62,13 @@ public:
|
|||
nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success(mSuccess);
|
||||
nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
|
||||
|
||||
{
|
||||
MutexAutoLock lock(MediaManager::Get()->GetMutex());
|
||||
WindowTable* activeWindows = MediaManager::Get()->GetActiveWindows();
|
||||
if (activeWindows->Get(mWindowID)) {
|
||||
error->OnError(mErrorMsg);
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -103,11 +106,14 @@ public:
|
|||
nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success(mSuccess);
|
||||
nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
|
||||
|
||||
{
|
||||
MutexAutoLock lock(MediaManager::Get()->GetMutex());
|
||||
WindowTable* activeWindows = MediaManager::Get()->GetActiveWindows();
|
||||
if (activeWindows->Get(mWindowID)) {
|
||||
// XPConnect is a magical unicorn.
|
||||
success->OnSuccess(mFile);
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -247,6 +253,8 @@ public:
|
|||
nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
|
||||
(nsGlobalWindow::GetInnerWindowWithId(mWindowID));
|
||||
WindowTable* activeWindows = MediaManager::Get()->GetActiveWindows();
|
||||
{
|
||||
MutexAutoLock lock(MediaManager::Get()->GetMutex());
|
||||
|
||||
if (!stream) {
|
||||
if (activeWindows->Get(mWindowID)) {
|
||||
|
@ -256,30 +264,44 @@ public:
|
|||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
}
|
||||
if (window && window->GetExtantDoc()) {
|
||||
stream->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal());
|
||||
}
|
||||
|
||||
// Ensure there's a thread for gum to proxy to off main thread
|
||||
nsIThread *mediaThread = MediaManager::GetThread();
|
||||
|
||||
// Add our listener. We'll call Start() on the source when get a callback
|
||||
// that the MediaStream has started consuming. The listener is freed
|
||||
// when the page is invalidated (on navigation or close).
|
||||
GetUserMediaCallbackMediaStreamListener* listener =
|
||||
new GetUserMediaCallbackMediaStreamListener(stream, mAudioSource,
|
||||
new GetUserMediaCallbackMediaStreamListener(mediaThread, stream,
|
||||
mAudioSource,
|
||||
mVideoSource);
|
||||
stream->GetStream()->AddListener(listener);
|
||||
|
||||
// No need for locking because we always do this in the main thread.
|
||||
mListeners->AppendElement(listener);
|
||||
|
||||
// Dispatch to the media thread to ask it to start the sources,
|
||||
// because that can take a while
|
||||
nsRefPtr<MediaOperationRunnable> runnable(
|
||||
new MediaOperationRunnable(MEDIA_START, stream,
|
||||
mAudioSource, mVideoSource));
|
||||
mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
|
||||
|
||||
// We're in the main thread, so no worries here either.
|
||||
nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> success(mSuccess);
|
||||
nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
|
||||
|
||||
{
|
||||
MutexAutoLock lock(MediaManager::Get()->GetMutex());
|
||||
if (activeWindows->Get(mWindowID)) {
|
||||
LOG(("Returning success for getUserMedia()"));
|
||||
success->OnSuccess(stream);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -759,6 +781,9 @@ MediaManager::GetUserMedia(bool aPrivileged, nsPIDOMWindow* aWindow,
|
|||
// Store the WindowID in a hash table and mark as active. The entry is removed
|
||||
// when this window is closed or navigated away from.
|
||||
uint64_t windowID = aWindow->WindowID();
|
||||
nsRefPtr<GetUserMediaRunnable> gUMRunnable;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
StreamListeners* listeners = mActiveWindows.Get(windowID);
|
||||
if (!listeners) {
|
||||
listeners = new StreamListeners;
|
||||
|
@ -779,7 +804,6 @@ MediaManager::GetUserMedia(bool aPrivileged, nsPIDOMWindow* aWindow,
|
|||
*
|
||||
* If a fake stream was requested, we force the use of the default backend.
|
||||
*/
|
||||
nsRefPtr<GetUserMediaRunnable> gUMRunnable;
|
||||
if (fake) {
|
||||
// Fake stream from default backend.
|
||||
gUMRunnable = new GetUserMediaRunnable(
|
||||
|
@ -801,6 +825,8 @@ MediaManager::GetUserMedia(bool aPrivileged, nsPIDOMWindow* aWindow,
|
|||
windowID
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef ANDROID
|
||||
if (picture) {
|
||||
|
@ -811,11 +837,7 @@ MediaManager::GetUserMedia(bool aPrivileged, nsPIDOMWindow* aWindow,
|
|||
#else
|
||||
// XXX No full support for picture in Desktop yet (needs proper UI)
|
||||
if (aPrivileged || fake) {
|
||||
if (!mMediaThread) {
|
||||
nsresult rv = NS_NewThread(getter_AddRefs(mMediaThread));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
LOG(("New Media thread for gum"));
|
||||
}
|
||||
(void) MediaManager::GetThread();
|
||||
mMediaThread->Dispatch(gUMRunnable, NS_DISPATCH_NORMAL);
|
||||
} else {
|
||||
// Ask for user permission, and dispatch runnable (or not) when a response
|
||||
|
@ -836,7 +858,10 @@ MediaManager::GetUserMedia(bool aPrivileged, nsPIDOMWindow* aWindow,
|
|||
NS_ConvertUTF8toUTF16 callID(buffer);
|
||||
|
||||
// Store the current callback.
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mActiveCallbacks.Put(callID, gUMRunnable);
|
||||
}
|
||||
|
||||
// Construct JSON structure with both the windowID and the callID.
|
||||
nsAutoString data;
|
||||
|
@ -912,6 +937,8 @@ MediaManager::OnNavigation(uint64_t aWindowID)
|
|||
{
|
||||
// Invalidate this window. The runnables check this value before making
|
||||
// a call to content.
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
StreamListeners* listeners = mActiveWindows.Get(aWindowID);
|
||||
if (!listeners) {
|
||||
return;
|
||||
|
@ -928,6 +955,7 @@ MediaManager::OnNavigation(uint64_t aWindowID)
|
|||
|
||||
mActiveWindows.Remove(aWindowID);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
|
@ -942,9 +970,12 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
|
|||
obs->RemoveObserver(this, "getUserMedia:response:deny");
|
||||
|
||||
// Close off any remaining active windows.
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mActiveWindows.Clear();
|
||||
mActiveCallbacks.Clear();
|
||||
sSingleton = nullptr;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -952,18 +983,15 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
|
|||
if (!strcmp(aTopic, "getUserMedia:response:allow")) {
|
||||
nsString key(aData);
|
||||
nsRefPtr<nsRunnable> runnable;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (!mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// Reuse the same thread to save memory.
|
||||
if (!mMediaThread) {
|
||||
LOG(("New Media thread for gum on allow"));
|
||||
nsresult rv = NS_NewThread(getter_AddRefs(mMediaThread));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
LOG(("Reused Media thread for gum on allow"));
|
||||
}
|
||||
(void) MediaManager::GetThread();
|
||||
|
||||
if (aSubject) {
|
||||
// A particular device was chosen by the user.
|
||||
|
@ -985,20 +1013,25 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
|
|||
}
|
||||
|
||||
mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mActiveCallbacks.Remove(key);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!strcmp(aTopic, "getUserMedia:response:deny")) {
|
||||
nsString key(aData);
|
||||
nsRefPtr<nsRunnable> runnable;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (mActiveCallbacks.Get(key, getter_AddRefs(runnable))) {
|
||||
GetUserMediaRunnable* gUMRunnable =
|
||||
static_cast<GetUserMediaRunnable*>(runnable.get());
|
||||
gUMRunnable->Denied();
|
||||
mActiveCallbacks.Remove(key);
|
||||
}
|
||||
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,64 +65,67 @@ class GetUserMediaNotificationEvent: public nsRunnable
|
|||
GetUserMediaStatus mStatus;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
typedef enum {
|
||||
MEDIA_START,
|
||||
MEDIA_STOP,
|
||||
MEDIA_RELEASE
|
||||
} MediaOperation;
|
||||
|
||||
// Generic class for running long media operations off the main thread, and
|
||||
// then (because nsDOMMediaStreams aren't threadsafe), re-sends itseld to
|
||||
// MainThread to release mStream. This is part of the reason we use an
|
||||
// operation type - we can change it to repost the runnable to MainThread
|
||||
// to do operations with the nsDOMMediaStreams, while we can't assign or
|
||||
// copy a nsRefPtr to a nsDOMMediaStream
|
||||
class MediaOperationRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
GetUserMediaCallbackMediaStreamListener(nsDOMMediaStream* aStream,
|
||||
MediaOperationRunnable(MediaOperation aType,
|
||||
nsDOMMediaStream* aStream,
|
||||
MediaEngineSource* aAudioSource,
|
||||
MediaEngineSource* aVideoSource)
|
||||
: mAudioSource(aAudioSource)
|
||||
: mType(aType)
|
||||
, mAudioSource(aAudioSource)
|
||||
, mVideoSource(aVideoSource)
|
||||
, mStream(aStream)
|
||||
, mValid(true) {}
|
||||
{}
|
||||
|
||||
void
|
||||
Invalidate()
|
||||
MediaOperationRunnable(MediaOperation aType,
|
||||
SourceMediaStream* aStream,
|
||||
MediaEngineSource* aAudioSource,
|
||||
MediaEngineSource* aVideoSource)
|
||||
: mType(aType)
|
||||
, mAudioSource(aAudioSource)
|
||||
, mVideoSource(aVideoSource)
|
||||
, mStream(nullptr)
|
||||
, mSourceStream(aStream)
|
||||
{}
|
||||
|
||||
NS_IMETHOD
|
||||
Run()
|
||||
{
|
||||
if (!mValid) {
|
||||
return;
|
||||
// No locking between these is required as all the callbacks (other
|
||||
// than MEDIA_RELEASE) for the same MediaStream will occur on the same
|
||||
// thread.
|
||||
if (mStream) {
|
||||
mSourceStream = mStream->GetStream()->AsSourceStream();
|
||||
}
|
||||
|
||||
mValid = false;
|
||||
if (mAudioSource) {
|
||||
mAudioSource->Stop();
|
||||
mAudioSource->Deallocate();
|
||||
}
|
||||
if (mVideoSource) {
|
||||
mVideoSource->Stop();
|
||||
mVideoSource->Deallocate();
|
||||
}
|
||||
// Do this after stopping all tracks with EndTrack()
|
||||
mStream->GetStream()->AsSourceStream()->Finish();
|
||||
|
||||
nsCOMPtr<GetUserMediaNotificationEvent> event =
|
||||
new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STOPPING);
|
||||
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void
|
||||
NotifyConsumptionChanged(MediaStreamGraph* aGraph, Consumption aConsuming)
|
||||
switch (mType) {
|
||||
case MEDIA_START:
|
||||
{
|
||||
if (aConsuming == CONSUMED) {
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
|
||||
nsresult rv;
|
||||
|
||||
SourceMediaStream* stream = mStream->GetStream()->AsSourceStream();
|
||||
stream->SetPullEnabled(true);
|
||||
mSourceStream->SetPullEnabled(true);
|
||||
|
||||
if (mAudioSource) {
|
||||
rv = mAudioSource->Start(stream, kAudioTrack);
|
||||
rv = mAudioSource->Start(mSourceStream, kAudioTrack);
|
||||
if (NS_FAILED(rv)) {
|
||||
MM_LOG(("Starting audio failed, rv=%d",rv));
|
||||
}
|
||||
}
|
||||
if (mVideoSource) {
|
||||
rv = mVideoSource->Start(stream, kVideoTrack);
|
||||
rv = mVideoSource->Start(mSourceStream, kVideoTrack);
|
||||
if (NS_FAILED(rv)) {
|
||||
MM_LOG(("Starting video failed, rv=%d",rv));
|
||||
}
|
||||
|
@ -133,11 +136,81 @@ public:
|
|||
new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING);
|
||||
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case MEDIA_STOP:
|
||||
{
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
|
||||
if (mAudioSource) {
|
||||
mAudioSource->Stop();
|
||||
mAudioSource->Deallocate();
|
||||
}
|
||||
if (mVideoSource) {
|
||||
mVideoSource->Stop();
|
||||
mVideoSource->Deallocate();
|
||||
}
|
||||
// Do this after stopping all tracks with EndTrack()
|
||||
mSourceStream->Finish();
|
||||
|
||||
nsCOMPtr<GetUserMediaNotificationEvent> event =
|
||||
new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STOPPING);
|
||||
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
break;
|
||||
case MEDIA_RELEASE:
|
||||
// We go to MainThread to die
|
||||
break;
|
||||
}
|
||||
if (mType != MEDIA_RELEASE) {
|
||||
// nsDOMMediaStreams aren't thread-safe... sigh.
|
||||
mType = MEDIA_RELEASE;
|
||||
NS_DispatchToMainThread(this);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// NOT_CONSUMED
|
||||
Invalidate();
|
||||
private:
|
||||
MediaOperation mType;
|
||||
nsRefPtr<MediaEngineSource> mAudioSource;
|
||||
nsRefPtr<MediaEngineSource> mVideoSource;
|
||||
nsCOMPtr<nsDOMMediaStream> mStream;
|
||||
SourceMediaStream *mSourceStream;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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:
|
||||
GetUserMediaCallbackMediaStreamListener(nsIThread *aThread,
|
||||
nsDOMMediaStream* aStream,
|
||||
MediaEngineSource* aAudioSource,
|
||||
MediaEngineSource* aVideoSource)
|
||||
: mMediaThread(aThread)
|
||||
, mAudioSource(aAudioSource)
|
||||
, mVideoSource(aVideoSource)
|
||||
, mStream(aStream)
|
||||
, mValid(true) {}
|
||||
|
||||
void
|
||||
Invalidate()
|
||||
{
|
||||
nsRefPtr<MediaOperationRunnable> runnable;
|
||||
|
||||
// We can't take a chance on blocking here, so proxy this to another
|
||||
// thread.
|
||||
// XXX FIX! I'm cheating and passing a raw pointer to the sourcestream
|
||||
// which is valid as long as the mStream pointer here is. Need a better solution.
|
||||
runnable = new MediaOperationRunnable(MEDIA_STOP,
|
||||
mStream->GetStream()->AsSourceStream(),
|
||||
mAudioSource, mVideoSource);
|
||||
mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -156,6 +229,7 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIThread> mMediaThread;
|
||||
nsRefPtr<MediaEngineSource> mAudioSource;
|
||||
nsRefPtr<MediaEngineSource> mVideoSource;
|
||||
nsCOMPtr<nsDOMMediaStream> mStream;
|
||||
|
@ -204,6 +278,17 @@ public:
|
|||
}
|
||||
return sSingleton;
|
||||
}
|
||||
static Mutex& GetMutex() {
|
||||
return Get()->mMutex;
|
||||
}
|
||||
static nsIThread* GetThread() {
|
||||
MutexAutoLock lock(Get()->mMutex); // only need to call Get() once
|
||||
if (!sSingleton->mMediaThread) {
|
||||
NS_NewThread(getter_AddRefs(sSingleton->mMediaThread));
|
||||
MM_LOG(("New Media thread for gum"));
|
||||
}
|
||||
return sSingleton->mMediaThread;
|
||||
}
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
@ -223,17 +308,19 @@ public:
|
|||
private:
|
||||
// Make private because we want only one instance of this class
|
||||
MediaManager()
|
||||
: mBackend(nullptr)
|
||||
: mMutex("mozilla::MediaManager")
|
||||
, mBackend(nullptr)
|
||||
, mMediaThread(nullptr) {
|
||||
mActiveWindows.Init();
|
||||
mActiveCallbacks.Init();
|
||||
};
|
||||
MediaManager(MediaManager const&) {};
|
||||
|
||||
~MediaManager() {
|
||||
delete mBackend;
|
||||
};
|
||||
|
||||
Mutex mMutex;
|
||||
// protected with mMutex:
|
||||
MediaEngine* mBackend;
|
||||
nsCOMPtr<nsIThread> mMediaThread;
|
||||
WindowTable mActiveWindows;
|
||||
|
|
Загрузка…
Ссылка в новой задаче