From 95251bed50fe7a012924431763934fdb417b9e3c Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Fri, 3 May 2013 17:07:37 +1200 Subject: [PATCH] Bug 866514. Part 2: Delay delivering getUserMedia stream result until the DOM object has asynchronously acquired the desired tracks. r=jesup --- dom/media/MediaManager.cpp | 73 +++++++++++++++++++++++++++----------- dom/media/MediaManager.h | 37 +++++++++++++++---- 2 files changed, 83 insertions(+), 27 deletions(-) diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index 32cc6cae59af..c1b31ab76e8c 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -326,6 +326,41 @@ public: ~GetUserMediaStreamRunnable() {} + class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback + { + public: + TracksAvailableCallback(MediaManager* aManager, + nsIDOMGetUserMediaSuccessCallback* aSuccess, + uint64_t aWindowID, + DOMMediaStream* aStream) + : mWindowID(aWindowID), mSuccess(aSuccess), mManager(aManager), + mStream(aStream) {} + virtual void NotifyTracksAvailable(DOMMediaStream* aStream) MOZ_OVERRIDE + { + // We're in the main thread, so no worries here. + if (!(mManager->IsWindowStillActive(mWindowID))) { + return; + } + + // This is safe since we're on main-thread, and the windowlist can only + // be invalidated from the main-thread (see OnNavigation) + LOG(("Returning success for getUserMedia()")); + mSuccess->OnSuccess(aStream); + } + uint64_t mWindowID; + nsRefPtr mSuccess; + nsRefPtr mManager; + // Keep the DOMMediaStream alive until the NotifyTracksAvailable callback + // has fired, otherwise we might immediately destroy the DOMMediaStream and + // shut down the underlying MediaStream prematurely. + // This creates a cycle which is broken when NotifyTracksAvailable + // is fired (which will happen unless the browser shuts down, + // since we only add this callback when we've successfully appended + // the desired tracks in the MediaStreamGraph) or when + // DOMMediaStream::NotifyMediaStreamGraphShutdown is called. + nsRefPtr mStream; + }; + NS_IMETHOD Run() { @@ -342,13 +377,14 @@ public: } // Create a media stream. - uint32_t hints = (mAudioSource ? DOMMediaStream::HINT_CONTENTS_AUDIO : 0); - hints |= (mVideoSource ? DOMMediaStream::HINT_CONTENTS_VIDEO : 0); + DOMMediaStream::TrackTypeHints hints = + (mAudioSource ? DOMMediaStream::HINT_CONTENTS_AUDIO : 0) | + (mVideoSource ? DOMMediaStream::HINT_CONTENTS_VIDEO : 0); nsRefPtr trackunion = nsDOMUserMediaStream::CreateTrackUnionStream(window, hints); if (!trackunion) { - nsCOMPtr error(mError); + nsCOMPtr error = mError.forget(); LOG(("Returning error for getUserMedia() - no stream")); error->OnError(NS_LITERAL_STRING("NO_STREAM")); return NS_OK; @@ -372,11 +408,17 @@ public: // when the page is invalidated (on navigation or close). mListener->Activate(stream.forget(), mAudioSource, mVideoSource); + TracksAvailableCallback* tracksAvailableCallback = + new TracksAvailableCallback(mManager, mSuccess, mWindowID, trackunion); + // Dispatch to the media thread to ask it to start the sources, - // because that can take a while + // because that can take a while. + // Pass ownership of trackunion to the MediaOperationRunnable + // to ensure it's kept alive until the MediaOperationRunnable runs (at least). nsIThread *mediaThread = MediaManager::GetThread(); nsRefPtr runnable( - new MediaOperationRunnable(MEDIA_START, mListener, + new MediaOperationRunnable(MEDIA_START, mListener, trackunion, + tracksAvailableCallback, mAudioSource, mVideoSource, false)); mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); @@ -407,24 +449,14 @@ public: } #endif - // We're in the main thread, so no worries here either. - nsCOMPtr success(mSuccess); - nsCOMPtr error(mError); - - if (!(mManager->IsWindowStillActive(mWindowID))) { - return NS_OK; - } - // This is safe since we're on main-thread, and the windowlist can only - // be invalidated from the main-thread (see OnNavigation) - LOG(("Returning success for getUserMedia()")); - success->OnSuccess(static_cast(trackunion)); - + // We won't need mError now. + mError = nullptr; return NS_OK; } private: - already_AddRefed mSuccess; - already_AddRefed mError; + nsRefPtr mSuccess; + nsRefPtr mError; nsRefPtr mAudioSource; nsRefPtr mVideoSource; uint64_t mWindowID; @@ -1498,7 +1530,8 @@ GetUserMediaCallbackMediaStreamListener::Invalidate() // Pass a ref to us (which is threadsafe) so it can query us for the // source stream info. runnable = new MediaOperationRunnable(MEDIA_STOP, - this, mAudioSource, mVideoSource, + this, nullptr, nullptr, + mAudioSource, mVideoSource, mFinished); mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL); } diff --git a/dom/media/MediaManager.h b/dom/media/MediaManager.h index 01f77231eb0d..f0a3c9ede3f9 100644 --- a/dom/media/MediaManager.h +++ b/dom/media/MediaManager.h @@ -22,6 +22,7 @@ #include "mozilla/Attributes.h" #include "mozilla/StaticPtr.h" #include "prlog.h" +#include "DOMMediaStream.h" #ifdef MOZ_WEBRTC #include "mtransport/runnable_utils.h" @@ -200,8 +201,11 @@ class GetUserMediaNotificationEvent: public nsRunnable GetUserMediaStatus aStatus) : mListener(aListener), mStatus(aStatus) {} - GetUserMediaNotificationEvent(GetUserMediaStatus aStatus) - : mListener(nullptr), mStatus(aStatus) {} + GetUserMediaNotificationEvent(GetUserMediaStatus aStatus, + already_AddRefed aStream, + DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback) + : mStream(aStream), mOnTracksAvailableCallback(aOnTracksAvailableCallback), + mStatus(aStatus) {} NS_IMETHOD Run() @@ -216,6 +220,7 @@ class GetUserMediaNotificationEvent: public nsRunnable switch (mStatus) { case STARTING: msg = NS_LITERAL_STRING("starting"); + mStream->OnTracksAvailable(mOnTracksAvailableCallback.forget()); break; case STOPPING: msg = NS_LITERAL_STRING("shutdown"); @@ -237,6 +242,8 @@ class GetUserMediaNotificationEvent: public nsRunnable protected: nsRefPtr mListener; // threadsafe + nsRefPtr mStream; + nsAutoPtr mOnTracksAvailableCallback; GetUserMediaStatus mStatus; }; @@ -254,10 +261,14 @@ 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) : mType(aType) + , mStream(aStream) + , mOnTracksAvailableCallback(aOnTracksAvailableCallback) , mAudioSource(aAudioSource) , mVideoSource(aVideoSource) , mListener(aListener) @@ -286,24 +297,34 @@ public: source->SetPullEnabled(true); + DOMMediaStream::TrackTypeHints expectedTracks = 0; if (mAudioSource) { rv = mAudioSource->Start(source, kAudioTrack); - if (NS_FAILED(rv)) { + if (NS_SUCCEEDED(rv)) { + expectedTracks |= DOMMediaStream::HINT_CONTENTS_AUDIO; + } else { MM_LOG(("Starting audio failed, rv=%d",rv)); } } if (mVideoSource) { rv = mVideoSource->Start(source, kVideoTrack); - if (NS_FAILED(rv)) { + if (NS_SUCCEEDED(rv)) { + expectedTracks |= DOMMediaStream::HINT_CONTENTS_VIDEO; + } else { MM_LOG(("Starting video failed, rv=%d",rv)); } } + mOnTracksAvailableCallback->SetExpectedTracks(expectedTracks); + MM_LOG(("started all sources")); + // Forward mOnTracksAvailableCallback to GetUserMediaNotificationEvent, + // because mOnTracksAvailableCallback needs to be added to mStream + // on the main thread. nsRefPtr event = - new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING); - - + new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING, + mStream.forget(), + mOnTracksAvailableCallback.forget()); NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); } break; @@ -339,6 +360,8 @@ public: private: MediaOperation mType; + nsRefPtr mStream; + nsAutoPtr mOnTracksAvailableCallback; nsRefPtr mAudioSource; // threadsafe nsRefPtr mVideoSource; // threadsafe nsRefPtr mListener; // threadsafe