--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -183,17 +183,17 @@ NS_IMPL_RELEASE_INHERITED(MediaRecorder,
*
* Lifetime of MediaRecorder and Session objects.
* 1) MediaRecorder creates a Session in MediaRecorder::Start function and holds
* a reference to Session. Then the Session registers itself to a
* ShutdownBlocker and also holds a reference to MediaRecorder.
* Therefore, the reference dependency in gecko is:
* ShutdownBlocker -> Session <-> MediaRecorder, note that there is a cycle
* reference between Session and MediaRecorder.
- * 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being called
+ * 2) A Session is destroyed by Destroy() after MediaRecorder::Stop is called
* _and_ all encoded media data been passed to OnDataAvailable handler.
* 3) MediaRecorder::Stop is called by user or the document is going to
* inactive or invisible.
*/
class MediaRecorder::Session: public PrincipalChangeObserver<MediaStreamTrack>,
public DOMMediaStream::TrackListener
{
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Session)
@@ -201,22 +201,21 @@ class MediaRecorder::Session: public Pri
// Main thread task.
// Create a blob event and send back to client.
class PushBlobRunnable : public Runnable
, public MutableBlobStorageCallback
{
public:
NS_DECL_ISUPPORTS_INHERITED
- // aDestroyRunnable can be null. If it's not, it will be dispatched after
- // the PushBlobRunnable::Run().
- PushBlobRunnable(Session* aSession, Runnable* aDestroyRunnable)
+ // aShouldDestroy says if we should destroy after pushing the blob.
+ PushBlobRunnable(Session* aSession, bool aShouldDestroy)
: Runnable("dom::MediaRecorder::Session::PushBlobRunnable")
, mSession(aSession)
- , mDestroyRunnable(aDestroyRunnable)
+ , mShouldDestroy(aShouldDestroy)
{ }
NS_IMETHOD Run() override
{
LOG(LogLevel::Debug, ("Session.PushBlobRunnable s=(%p)", mSession.get()));
MOZ_ASSERT(NS_IsMainThread());
mSession->GetBlobWhenReady(this);
@@ -237,31 +236,35 @@ class MediaRecorder::Session: public Pri
return;
}
nsresult rv = recorder->CreateAndDispatchBlobEvent(aBlob);
if (NS_FAILED(rv)) {
recorder->NotifyError(aRv);
}
- if (mDestroyRunnable &&
- NS_FAILED(NS_DispatchToMainThread(mDestroyRunnable.forget()))) {
- MOZ_ASSERT(false, "NS_DispatchToMainThread failed");
+ if (mShouldDestroy) {
+ if (NS_FAILED(NS_DispatchToMainThread(
+ NewRunnableMethod("dom::MediaRecorder:Session::Destroy",
+ mSession,
+ &Session::Destroy)))) {
+ MOZ_ASSERT(false, "NS_DispatchToMainThread Destroy failed");
+ }
}
}
private:
~PushBlobRunnable() = default;
RefPtr<Session> mSession;
- // The generation of the blob is async. In order to avoid dispatching the
- // DestroyRunnable before pushing the blob event, we store the runnable
- // here.
- RefPtr<Runnable> mDestroyRunnable;
+ // The generation of the blob is async. In order to avoid dispatching a
+ // Destroy runnable before pushing the blob event, we store if we should
+ // destroy and do so after the blob is pushed
+ bool mShouldDestroy;
};
class StoreEncodedBufferRunnable final : public Runnable
{
RefPtr<Session> mSession;
nsTArray<nsTArray<uint8_t>> mBuffer;
public:
@@ -352,82 +355,16 @@ class MediaRecorder::Session: public Pri
virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
{
mSession->MediaStreamReady(aStream);
}
private:
RefPtr<Session> mSession;
};
- // Main thread task.
- // To delete RecordingSession object.
- class DestroyRunnable : public Runnable
- {
- public:
- explicit DestroyRunnable(Session* aSession)
- : Runnable("dom::MediaRecorder::Session::DestroyRunnable")
- , mSession(aSession)
- {
- }
-
- explicit DestroyRunnable(already_AddRefed<Session> aSession)
- : Runnable("dom::MediaRecorder::Session::DestroyRunnable")
- , mSession(aSession)
- {
- }
-
- NS_IMETHOD Run() override
- {
- LOG(LogLevel::Debug, ("Session.DestroyRunnable session refcnt = (%d) stopIssued %d s=(%p)",
- (int)mSession->mRefCnt, mSession->mStopIssued, mSession.get()));
- MOZ_ASSERT(NS_IsMainThread() && mSession);
- RefPtr<MediaRecorder> recorder = mSession->mRecorder;
- if (!recorder) {
- return NS_OK;
- }
- // SourceMediaStream is ended, and send out TRACK_EVENT_END notification.
- // Read Thread will be terminate soon.
- // We need to switch MediaRecorder to "Stop" state first to make sure
- // MediaRecorder is not associated with this Session anymore, then, it's
- // safe to delete this Session.
- // Also avoid to run if this session already call stop before
- if (!mSession->mStopIssued) {
- recorder->StopForSessionDestruction();
- if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(mSession.forget())))) {
- MOZ_ASSERT(false, "NS_DispatchToMainThread failed");
- }
- return NS_OK;
- }
-
- // Dispatch stop event and clear MIME type.
- mSession->mMimeType = NS_LITERAL_STRING("");
- recorder->SetMimeType(mSession->mMimeType);
- recorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
-
- RefPtr<Session> session = mSession.forget();
- session->Shutdown()->Then(
- GetCurrentThreadSerialEventTarget(), __func__,
- [session]() {
- gSessions.RemoveEntry(session);
- if (gSessions.Count() == 0 &&
- gMediaRecorderShutdownBlocker) {
- // All sessions finished before shutdown, no need to keep the blocker.
- RefPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
- barrier->RemoveBlocker(gMediaRecorderShutdownBlocker);
- gMediaRecorderShutdownBlocker = nullptr;
- }
- },
- []() { MOZ_CRASH("Not reached"); });
- return NS_OK;
- }
-
- private:
- // Call mSession::Release automatically while DestroyRunnable be destroy.
- RefPtr<Session> mSession;
- };
class EncoderListener : public MediaEncoderListener
{
public:
EncoderListener(TaskQueue* aEncoderThread, Session* aSession)
: mEncoderThread(aEncoderThread)
, mSession(aSession)
{}
@@ -472,17 +409,16 @@ class MediaRecorder::Session: public Pri
protected:
RefPtr<TaskQueue> mEncoderThread;
RefPtr<Session> mSession;
};
friend class EncoderErrorNotifierRunnable;
friend class PushBlobRunnable;
- friend class DestroyRunnable;
friend class TracksAvailableCallback;
public:
Session(MediaRecorder* aRecorder, int32_t aTimeSlice)
: mRecorder(aRecorder)
, mTimeSlice(aTimeSlice)
, mStopIssued(false)
, mIsStartEventFired(false)
@@ -566,17 +502,16 @@ public:
mStopIssued = true;
if (mEncoder) {
mEncoder->Stop();
}
if (mNeedSessionEndTask) {
LOG(LogLevel::Debug, ("Session.Stop mNeedSessionEndTask %p", this));
- // End the Session directly if there is no ExtractRunnable.
DoSessionEndTask(NS_OK);
}
}
nsresult Pause()
{
LOG(LogLevel::Debug, ("Session.Pause"));
MOZ_ASSERT(NS_IsMainThread());
@@ -602,17 +537,17 @@ public:
return NS_OK;
}
nsresult RequestData()
{
LOG(LogLevel::Debug, ("Session.RequestData"));
MOZ_ASSERT(NS_IsMainThread());
- if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this, nullptr)))) {
+ if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this, false)))) {
MOZ_ASSERT(false, "RequestData NS_DispatchToMainThread failed");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
void
@@ -653,28 +588,28 @@ public:
return InvokeAsync(mEncoderThread, __func__,
[encoder, encodedBufferSize, aMallocSizeOf]() {
return SizeOfPromise::CreateAndResolve(
encodedBufferSize + encoder->SizeOfExcludingThis(aMallocSizeOf), __func__);
});
}
private:
- // Only DestroyRunnable is allowed to delete Session object on main thread.
+ // Only Destroy() is allowed to delete Session object on main thread.
virtual ~Session()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mShutdownPromise);
LOG(LogLevel::Debug, ("Session.~Session (%p)", this));
}
// Pull encoded media data from MediaEncoder and put into MutableBlobStorage.
// Destroy this session object in the end of this function.
// If the bool aForceFlush is true, we will force to dispatch a
// PushBlobRunnable to main thread.
- void Extract(bool aForceFlush, Runnable* aDestroyRunnable)
+ void Extract(bool aForceFlush, bool aShouldDestroy)
{
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
LOG(LogLevel::Debug, ("Session.Extract %p", this));
AUTO_PROFILER_LABEL("MediaRecorder::Session::Extract", OTHER);
// Pull encoded media data from MediaEncoder
@@ -694,28 +629,86 @@ private:
// need a flush.
bool pushBlob = aForceFlush;
if (!pushBlob &&
mTimeSlice > 0 &&
(TimeStamp::Now()-mLastBlobTimeStamp).ToMilliseconds() > mTimeSlice) {
pushBlob = true;
}
if (pushBlob) {
- if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this, aDestroyRunnable)))) {
+ if (NS_FAILED(NS_DispatchToMainThread(
+ new PushBlobRunnable(this, aShouldDestroy)))) {
MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
} else {
mLastBlobTimeStamp = TimeStamp::Now();
}
- } else if (aDestroyRunnable) {
- if (NS_FAILED(NS_DispatchToMainThread(aDestroyRunnable))) {
- MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
+ } else if (aShouldDestroy) {
+ if (NS_FAILED(NS_DispatchToMainThread(NewRunnableMethod(
+ "dom::MediaRecorder:Session::Destroy", this, &Session::Destroy)))) {
+ MOZ_ASSERT(false, "NS_DispatchToMainThread Destroy failed");
}
}
}
+ // Perform the steps needed to destroy a session. May reenter as this
+ // function needs to stop the recorder before the session can be properly
+ // shut down.
+ void Destroy()
+ {
+ LOG(LogLevel::Debug,
+ ("Session.Destroy session refcnt = (%d) stopIssued %d s=(%p)",
+ (int)mRefCnt,
+ mStopIssued,
+ this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mRecorder) {
+ // We're defensive just in case, but this shouldn't happen
+ MOZ_ASSERT_UNREACHABLE("Destroy() called on already shutdown session");
+ return;
+ }
+
+ // SourceMediaStream is ended, and send out TRACK_EVENT_END notification.
+ // Read Thread will be terminate soon.
+ // We need to switch MediaRecorder to "Stop" state first to make sure
+ // MediaRecorder is not associated with this Session anymore, then, it's
+ // safe to delete this Session.
+ if (!mStopIssued) {
+ // Need to stop our session before we destroy it
+ mRecorder->StopForSessionDestruction();
+ // Queue up another Destroy call for after the stop has taken place
+ if (NS_FAILED(NS_DispatchToMainThread(NewRunnableMethod(
+ "dom::MediaRecorder:Session::Destroy", this, &Session::Destroy)))) {
+ MOZ_ASSERT(false, "NS_DispatchToMainThread Destroy failed");
+ }
+ return;
+ }
+
+ // Dispatch stop event and clear MIME type.
+ mMimeType = NS_LITERAL_STRING("");
+ mRecorder->SetMimeType(mMimeType);
+ mRecorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
+
+ RefPtr<Session> self = this;
+
+ Shutdown()->Then(
+ GetCurrentThreadSerialEventTarget(),
+ __func__,
+ [self]() {
+ gSessions.RemoveEntry(self);
+ if (gSessions.Count() == 0 && gMediaRecorderShutdownBlocker) {
+ // All sessions finished before shutdown, no need to keep the blocker.
+ RefPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
+ barrier->RemoveBlocker(gMediaRecorderShutdownBlocker);
+ gMediaRecorderShutdownBlocker = nullptr;
+ }
+ },
+ []() { MOZ_ASSERT_UNREACHABLE("Unexpected reject"); });
+ }
+
void MediaStreamReady(DOMMediaStream* aStream) {
MOZ_RELEASE_ASSERT(aStream);
if (mStopIssued) {
return;
}
mMediaStream = aStream;
@@ -939,19 +932,18 @@ private:
mEncoder->ConnectAudioNode(mRecorder->mAudioNode,
mRecorder->mAudioNodeOutput);
}
for (auto& track : mMediaStreamTracks) {
mEncoder->ConnectMediaStreamTrack(track);
}
- // Set mNeedSessionEndTask to false because the
- // ExtractRunnable/DestroyRunnable will take the response to
- // end the session.
+ // Since the encoder is initialized, we don't need an explicit end task.
+ // The shutdown of the encoder will make sure this session is destroyed.
mNeedSessionEndTask = false;
}
// application should get blob and onstop event
void DoSessionEndTask(nsresult rv)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mIsStartEventFired) {
@@ -963,26 +955,26 @@ private:
mRecorder->ForceInactive();
NS_DispatchToMainThread(
NewRunnableMethod<nsresult>("dom::MediaRecorder::NotifyError",
mRecorder,
&MediaRecorder::NotifyError,
rv));
}
- RefPtr<Runnable> destroyRunnable = new DestroyRunnable(this);
-
if (rv != NS_ERROR_DOM_SECURITY_ERR) {
// Don't push a blob if there was a security error.
- if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this, destroyRunnable)))) {
+ if (NS_FAILED(
+ NS_DispatchToMainThread(new PushBlobRunnable(this, true)))) {
MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
}
} else {
- if (NS_FAILED(NS_DispatchToMainThread(destroyRunnable))) {
- MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
+ if (NS_FAILED(NS_DispatchToMainThread(NewRunnableMethod(
+ "dom::MediaRecorder:Session::Destroy", this, &Session::Destroy)))) {
+ MOZ_ASSERT(false, "NS_DispatchToMainThread Destroy failed");
}
}
mNeedSessionEndTask = false;
}
void MediaEncoderInitialized()
{
@@ -1006,38 +998,35 @@ private:
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
if (!mIsStartEventFired) {
NS_DispatchToMainThread(
new DispatchStartEventRunnable(this, NS_LITERAL_STRING("start")));
mIsStartEventFired = true;
}
- Extract(false, nullptr);
+ Extract(false, false);
}
void MediaEncoderError()
{
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
NS_DispatchToMainThread(
NewRunnableMethod<nsresult>(
"dom::MediaRecorder::Session::DoSessionEndTask",
this, &Session::DoSessionEndTask, NS_ERROR_FAILURE));
}
void MediaEncoderShutdown()
{
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
MOZ_ASSERT(mEncoder->IsShutdown());
- // For the stop event. Let's the creation of the blob to dispatch this runnable.
- RefPtr<Runnable> destroyRunnable = new DestroyRunnable(this);
-
// Forces the last blob even if it's not time for it yet.
- Extract(true, destroyRunnable);
+ Extract(true, true);
// Clean up.
mEncoderListener->Forget();
DebugOnly<bool> unregistered =
mEncoder->UnregisterListener(mEncoderListener);
MOZ_ASSERT(unregistered);
}
@@ -1148,19 +1137,20 @@ private:
// onDataAvailable handler. "mTimeSlice < 0" means Session object does not
// push encoded data to onDataAvailable, instead, it passive wait the client
// side pull encoded data by calling requestData API.
const int32_t mTimeSlice;
// Indicate this session's stop has been called.
bool mStopIssued;
// Indicate the session had fire start event. Encoding thread only.
bool mIsStartEventFired;
- // False if the InitEncoder called successfully, ensure the
- // ExtractRunnable/DestroyRunnable will end the session.
- // Main thread only.
+ // False if InitEncoder called successfully. This is because the media
+ // encoder will trigger destruction of a session when it is shutdown. If the
+ // encoder is not initialized then we need to do an explicit session end task
+ // to instead cause the session's destruction.
bool mNeedSessionEndTask;
};
NS_IMPL_ISUPPORTS_INHERITED0(MediaRecorder::Session::PushBlobRunnable, Runnable)
MediaRecorder::~MediaRecorder()
{
LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this));