Bug 826576: Manage the lifetimes of GUMCMSL objects (with inactive Listeners) r=roc

This commit is contained in:
Randell Jesup 2013-01-06 21:31:32 -05:00
parent 307173aba3
commit 5cb83be2c9
2 changed files with 137 additions and 47 deletions

View File

@ -192,6 +192,29 @@ private:
nsTArray<nsCOMPtr<nsIMediaDevice> > mDevices;
};
// Handle removing GetUserMediaCallbackMediaStreamListener from main thread
class GetUserMediaListenerRemove: public nsRunnable
{
public:
GetUserMediaListenerRemove(uint64_t aWindowID,
GetUserMediaCallbackMediaStreamListener *aListener)
: mWindowID(aWindowID)
, mListener(aListener) {}
NS_IMETHOD
Run()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
nsRefPtr<MediaManager> manager(MediaManager::GetInstance());
manager->RemoveFromWindowList(mWindowID, mListener);
return NS_OK;
}
protected:
uint64_t mWindowID;
nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
};
/**
* nsIMediaDevice implementation.
*/
@ -286,6 +309,7 @@ public:
already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
uint64_t aWindowID,
GetUserMediaCallbackMediaStreamListener* aListener,
MediaEngineSource* aAudioSource,
MediaEngineSource* aVideoSource)
: mSuccess(aSuccess)
@ -293,6 +317,7 @@ public:
, mAudioSource(aAudioSource)
, mVideoSource(aVideoSource)
, mWindowID(aWindowID)
, mListener(aListener)
, mManager(MediaManager::GetInstance()) {}
~GetUserMediaStreamRunnable() {}
@ -306,7 +331,7 @@ public:
// be invalidated from the main-thread (see OnNavigation)
StreamListeners* listeners = mManager->GetWindowListeners(mWindowID);
if (!listeners) {
// This window is no longer live.
// This window is no longer live. mListener has already been removed
return NS_OK;
}
@ -339,27 +364,19 @@ public:
trackunion->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
// The listener was added at the begining in an inactive state.
// Activate 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(mediaThread, stream.forget(),
port.forget(),
mAudioSource,
mVideoSource);
listener->Stream()->AddListener(listener);
// No need for locking because we always do this in the main thread.
listeners->AppendElement(listener);
mListener->Activate(stream.forget(), port.forget(),
mAudioSource, mVideoSource);
// Dispatch to the media thread to ask it to start the sources,
// because that can take a while
nsIThread *mediaThread = MediaManager::GetThread();
nsRefPtr<MediaOperationRunnable> runnable(
new MediaOperationRunnable(MEDIA_START, listener,
mAudioSource, mVideoSource));
new MediaOperationRunnable(MEDIA_START, mListener,
mAudioSource, mVideoSource, false));
mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
// We're in the main thread, so no worries here either.
@ -383,6 +400,7 @@ private:
nsRefPtr<MediaEngineSource> mAudioSource;
nsRefPtr<MediaEngineSource> mVideoSource;
uint64_t mWindowID;
nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
};
@ -405,13 +423,15 @@ public:
GetUserMediaRunnable(bool aAudio, bool aVideo, bool aPicture,
already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
uint64_t aWindowID, MediaDevice* aAudioDevice, MediaDevice* aVideoDevice)
uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
MediaDevice* aAudioDevice, MediaDevice* aVideoDevice)
: mAudio(aAudio)
, mVideo(aVideo)
, mPicture(aPicture)
, mSuccess(aSuccess)
, mError(aError)
, mWindowID(aWindowID)
, mListener(aListener)
, mDeviceChosen(true)
, mBackendChosen(false)
, mManager(MediaManager::GetInstance())
@ -427,13 +447,14 @@ public:
GetUserMediaRunnable(bool aAudio, bool aVideo, bool aPicture,
already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
uint64_t aWindowID)
uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener)
: mAudio(aAudio)
, mVideo(aVideo)
, mPicture(aPicture)
, mSuccess(aSuccess)
, mError(aError)
, mWindowID(aWindowID)
, mListener(aListener)
, mDeviceChosen(false)
, mBackendChosen(false)
, mManager(MediaManager::GetInstance()) {}
@ -445,13 +466,15 @@ public:
GetUserMediaRunnable(bool aAudio, bool aVideo,
already_AddRefed<nsIDOMGetUserMediaSuccessCallback> aSuccess,
already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
uint64_t aWindowID, MediaEngine* aBackend)
uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener,
MediaEngine* aBackend)
: mAudio(aAudio)
, mVideo(aVideo)
, mPicture(false)
, mSuccess(aSuccess)
, mError(aError)
, mWindowID(aWindowID)
, mListener(aListener)
, mDeviceChosen(false)
, mBackendChosen(true)
, mBackend(aBackend)
@ -503,16 +526,26 @@ public:
nsresult
Denied()
{
// We add a disabled listener to the StreamListeners array until accepted
// If this was the only active MediaStream, remove the window from the list.
if (NS_IsMainThread()) {
// This is safe since we're on main-thread, and the window can only
// be invalidated from the main-thread (see OnNavigation)
nsCOMPtr<nsIDOMGetUserMediaErrorCallback> error(mError);
error->OnError(NS_LITERAL_STRING("PERMISSION_DENIED"));
// Should happen *after* error runs for consistency, but may not matter
nsRefPtr<MediaManager> manager(MediaManager::GetInstance());
manager->RemoveFromWindowList(mWindowID, mListener);
} else {
// This will re-check the window being alive on main-thread
// Note: we must remove the listener on MainThread as well
NS_DispatchToMainThread(new ErrorCallbackRunnable(
mSuccess, mError, NS_LITERAL_STRING("PERMISSION_DENIED"), mWindowID
));
// MUST happen after ErrorCallbackRunnable Run()s, as it checks the active window list
NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, mListener));
}
return NS_OK;
@ -637,7 +670,7 @@ public:
}
NS_DispatchToMainThread(new GetUserMediaStreamRunnable(
mSuccess, mError, mWindowID, aAudioSource, aVideoSource
mSuccess, mError, mWindowID, mListener, aAudioSource, aVideoSource
));
return;
}
@ -678,6 +711,7 @@ private:
already_AddRefed<nsIDOMGetUserMediaSuccessCallback> mSuccess;
already_AddRefed<nsIDOMGetUserMediaErrorCallback> mError;
uint64_t mWindowID;
nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
nsRefPtr<MediaDevice> mAudioDevice;
nsRefPtr<MediaDevice> mVideoDevice;
@ -876,6 +910,15 @@ MediaManager::GetUserMedia(bool aPrivileged, nsPIDOMWindow* aWindow,
listeners = new StreamListeners;
GetActiveWindows()->Put(windowID, listeners);
}
// Ensure there's a thread for gum to proxy to off main thread
nsIThread *mediaThread = MediaManager::GetThread();
// Create a disabled listener to act as a placeholder
GetUserMediaCallbackMediaStreamListener* listener =
new GetUserMediaCallbackMediaStreamListener(mediaThread, windowID);
// No need for locking because we always do this in the main thread.
listeners->AppendElement(listener);
// Developer preference for turning off permission check.
if (Preferences::GetBool("media.navigator.permission.disabled", false)) {
@ -894,20 +937,20 @@ MediaManager::GetUserMedia(bool aPrivileged, nsPIDOMWindow* aWindow,
if (fake) {
// Fake stream from default backend.
gUMRunnable = new GetUserMediaRunnable(
audio, video, onSuccess.forget(), onError.forget(), windowID,
audio, video, onSuccess.forget(), onError.forget(), windowID, listener,
new MediaEngineDefault()
);
} else if (audiodevice || videodevice) {
// Stream from provided device.
gUMRunnable = new GetUserMediaRunnable(
audio, video, picture, onSuccess.forget(), onError.forget(), windowID,
audio, video, picture, onSuccess.forget(), onError.forget(), windowID, listener,
static_cast<MediaDevice*>(audiodevice.get()),
static_cast<MediaDevice*>(videodevice.get())
);
} else {
// Stream from default device from WebRTC backend.
gUMRunnable = new GetUserMediaRunnable(
audio, video, picture, onSuccess.forget(), onError.forget(), windowID
audio, video, picture, onSuccess.forget(), onError.forget(), windowID, listener
);
}
@ -1026,12 +1069,33 @@ MediaManager::OnNavigation(uint64_t aWindowID)
for (uint32_t i = 0; i < length; i++) {
nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener =
listeners->ElementAt(i);
listener->Invalidate();
listener->Invalidate(true);
listener->Remove();
}
listeners->Clear();
GetActiveWindows()->Remove(aWindowID);
RemoveWindowID(aWindowID);
// listeners has been deleted
}
void
MediaManager::RemoveFromWindowList(uint64_t aWindowID,
GetUserMediaCallbackMediaStreamListener *aListener)
{
NS_ASSERTION(NS_IsMainThread(), "RemoveFromWindowList called off main thread");
// This is defined as safe on an inactive GUMCMSListener
aListener->Remove(); // really queues the remove
StreamListeners* listeners = GetWindowListeners(aWindowID);
if (!listeners) {
return;
}
listeners->RemoveElement(aListener);
if (listeners->Length() == 0) {
RemoveWindowID(aWindowID);
// listeners has been deleted here
}
}
nsresult
@ -1169,7 +1233,7 @@ MediaManager::GetActiveMediaCaptureWindows(nsISupportsArray **aArray)
}
void
GetUserMediaCallbackMediaStreamListener::Invalidate()
GetUserMediaCallbackMediaStreamListener::Invalidate(bool aNeedsFinish)
{
nsRefPtr<MediaOperationRunnable> runnable;
// We can't take a chance on blocking here, so proxy this to another
@ -1177,8 +1241,16 @@ 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, mAudioSource, mVideoSource,
aNeedsFinish);
mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
}
void
GetUserMediaCallbackMediaStreamListener::NotifyFinished(MediaStreamGraph* aGraph)
{
Invalidate(false);
NS_DispatchToMainThread(new GetUserMediaListenerRemove(mWindowID, this));
}
} // namespace mozilla

View File

@ -69,18 +69,11 @@ class GetUserMediaNotificationEvent: public nsRunnable
class GetUserMediaCallbackMediaStreamListener : public MediaStreamListener
{
public:
// Create in an inactive state
GetUserMediaCallbackMediaStreamListener(nsIThread *aThread,
already_AddRefed<SourceMediaStream> aStream,
already_AddRefed<MediaInputPort> aPort,
MediaEngineSource* aAudioSource,
MediaEngineSource* aVideoSource)
uint64_t aWindowID)
: mMediaThread(aThread)
, mAudioSource(aAudioSource)
, mVideoSource(aVideoSource)
, mStream(aStream)
, mPort(aPort)
, mLastEndTimeAudio(0)
, mLastEndTimeVideo(0) {}
, mWindowID(aWindowID) {}
~GetUserMediaCallbackMediaStreamListener()
{
@ -88,24 +81,41 @@ public:
// refcounts.
}
void Activate(already_AddRefed<SourceMediaStream> aStream,
already_AddRefed<MediaInputPort> aPort,
MediaEngineSource* aAudioSource,
MediaEngineSource* aVideoSource)
{
mStream = aStream; // also serves as IsActive();
mPort = aPort;
mAudioSource = aAudioSource;
mVideoSource = aVideoSource;
mLastEndTimeAudio = 0;
mLastEndTimeVideo = 0;
mStream->AddListener(this);
}
MediaStream *Stream()
{
return mStream;
}
SourceMediaStream *GetSourceStream()
{
MOZ_ASSERT(mStream);
return mStream->AsSourceStream();
}
void
Invalidate(); // implement in .cpp to avoid circular dependency with MediaOperationRunnable
// implement in .cpp to avoid circular dependency with MediaOperationRunnable
void Invalidate(bool aNeedsFinish);
void
Remove()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
// Caller holds strong reference to us, so no death grip required
mStream->RemoveListener(this);
if (mStream) // allow even if inactive for easier cleanup
mStream->RemoveListener(this);
}
// Proxy NotifyPull() to sources
@ -123,14 +133,11 @@ public:
}
void
NotifyFinished(MediaStreamGraph* aGraph)
{
Invalidate();
// XXX right now this calls Finish, which isn't ideal but doesn't hurt
}
NotifyFinished(MediaStreamGraph* aGraph);
private:
nsCOMPtr<nsIThread> mMediaThread;
uint64_t mWindowID;
nsRefPtr<MediaEngineSource> mAudioSource;
nsRefPtr<MediaEngineSource> mVideoSource;
nsRefPtr<SourceMediaStream> mStream;
@ -154,11 +161,13 @@ public:
MediaOperationRunnable(MediaOperation aType,
GetUserMediaCallbackMediaStreamListener* aListener,
MediaEngineSource* aAudioSource,
MediaEngineSource* aVideoSource)
MediaEngineSource* aVideoSource,
bool aNeedsFinish)
: mType(aType)
, mAudioSource(aAudioSource)
, mVideoSource(aVideoSource)
, mListener(aListener)
, mFinish(aNeedsFinish)
{}
~MediaOperationRunnable()
@ -218,7 +227,9 @@ public:
mVideoSource->Deallocate();
}
// Do this after stopping all tracks with EndTrack()
source->Finish();
if (mFinish) {
source->Finish();
}
// the TrackUnion destination of the port will autofinish
nsRefPtr<GetUserMediaNotificationEvent> event =
@ -240,6 +251,7 @@ private:
nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe
nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe
nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
bool mFinish;
};
typedef nsTArray<nsRefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
@ -309,9 +321,15 @@ public:
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,