--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -30,16 +30,17 @@
#include "nsICryptoHash.h"
#include "nsICryptoHMAC.h"
#include "nsIKeyModule.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsIInputStream.h"
#include "nsILineInputStream.h"
#include "nsPIDOMWindow.h"
#include "mozilla/EventStateManager.h"
+#include "mozilla/MozPromise.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Types.h"
#include "mozilla/PeerIdentity.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/MediaStreamBinding.h"
@@ -151,49 +152,48 @@ using media::Refcountable;
static Atomic<bool> sHasShutdown;
typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
struct DeviceState {
DeviceState(const RefPtr<MediaDevice>& aDevice, bool aOffWhileDisabled)
: mOffWhileDisabled(aOffWhileDisabled)
- , mDisableTimer(new MediaTimer())
, mDevice(aDevice)
{
MOZ_ASSERT(mDevice);
}
// true if we have stopped mDevice, this is a terminal state.
// MainThread only.
bool mStopped = false;
// true if mDevice is currently enabled, i.e., turned on and capturing.
// MainThread only.
- bool mDeviceEnabled = true;
+ bool mDeviceEnabled = false;
// true if the application has currently enabled mDevice.
// MainThread only.
- bool mTrackEnabled = true;
+ bool mTrackEnabled = false;
// true if an operation to Start() or Stop() mDevice has been dispatched to
// the media thread and is not finished yet.
// MainThread only.
bool mOperationInProgress = false;
// true if we are allowed to turn off the underlying source while all tracks
// are disabled.
// MainThread only.
bool mOffWhileDisabled = false;
// Timer triggered by a MediaStreamTrackSource signaling that all tracks got
// disabled. When the timer fires we initiate Stop()ing mDevice.
// If set we allow dynamically stopping and starting mDevice.
// Any thread.
- const RefPtr<MediaTimer> mDisableTimer;
+ const RefPtr<MediaTimer> mDisableTimer = new MediaTimer();
// The underlying device we keep state for. Always non-null.
// Threadsafe access, but see method declarations for individual constraints.
const RefPtr<MediaDevice> mDevice;
};
/**
* This mimics the capture state from nsIMediaManagerService.
@@ -234,16 +234,18 @@ FromCaptureState(CaptureState aState)
* don't hold a reference to it during late shutdown.
*
* There's also a hard reference to the SourceListener through its
* SourceStreamListener and the MediaStreamGraph. MediaStreamGraph
* clears this on XPCOM_WILL_SHUTDOWN, before MediaManager enters shutdown.
*/
class SourceListener : public SupportsWeakPtr<SourceListener> {
public:
+ typedef MozPromise<bool /* aIgnored */, RefPtr<MediaMgrError>, true> InitPromise;
+
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(SourceListener)
NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION(SourceListener)
SourceListener();
/**
* Registers this source listener as belonging to the given window listener.
*/
@@ -252,16 +254,21 @@ public:
/**
* Marks this listener as active and adds itself as a listener to aStream.
*/
void Activate(SourceMediaStream* aStream,
MediaDevice* aAudioDevice,
MediaDevice* aVideoDevice);
/**
+ * Posts a task to initialize and start all associated devices.
+ */
+ RefPtr<InitPromise> InitializeAsync();
+
+ /**
* Stops all live tracks, finishes the associated MediaStream and cleans up.
*/
void Stop();
/**
* Removes this SourceListener from its associated MediaStream and marks it
* removed. Also removes the weak reference to the associated window listener.
*/
@@ -1369,99 +1376,42 @@ public:
mOnSuccess,
mWindowID,
domStream))));
// Dispatch to the media thread to ask it to start the sources,
// because that can take a while.
// Pass ownership of domStream through the lambda to the nested chrome
// notification lambda to ensure it's kept alive until that lambda runs or is discarded.
- RefPtr<GetUserMediaStreamRunnable> self = this;
- MediaManager::PostTask(NewTaskFrom([self, domStream, callback]() mutable {
- MOZ_ASSERT(MediaManager::IsInMediaThread());
- RefPtr<SourceMediaStream> source =
- self->mSourceListener->GetSourceStream();
-
- RefPtr<MediaMgrError> error = nullptr;
- if (self->mAudioDevice) {
- nsresult rv = self->mAudioDevice->SetTrack(source,
- kAudioTrack,
- self->mSourceListener->GetPrincipalHandle());
- if (NS_SUCCEEDED(rv)) {
- rv = self->mAudioDevice->Start();
- } else {
- nsString log;
- if (rv == NS_ERROR_NOT_AVAILABLE) {
- log.AssignASCII("Concurrent mic process limit.");
- error = new MediaMgrError(NS_LITERAL_STRING("NotReadableError"), log);
- } else {
- log.AssignASCII("Starting audio failed");
- error = new MediaMgrError(NS_LITERAL_STRING("InternalError"), log);
- }
- }
- }
-
- if (!error && self->mVideoDevice) {
- nsresult rv = self->mVideoDevice->SetTrack(source,
- kVideoTrack,
- self->mSourceListener->GetPrincipalHandle());
- if (NS_SUCCEEDED(rv)) {
- rv = self->mVideoDevice->Start();
- }
- if (NS_FAILED(rv)) {
- nsString log;
- log.AssignASCII("Starting video failed");
- error = new MediaMgrError(NS_LITERAL_STRING("InternalError"), log);
- }
- }
-
- if (error) {
- // Dispatch the error callback on main thread.
- NS_DispatchToMainThread(MakeAndAddRef<ErrorCallbackRunnable>(
- self->mOnFailure, *error, self->mWindowID));
- return NS_OK;
- }
-
- // Start() queued the tracks to be added synchronously to avoid races
- source->FinishAddTracks();
-
- source->AdvanceKnownTracksTime(STREAM_TIME_MAX);
-
- LOG(("started all sources"));
-
- // onTracksAvailableCallback must be added to domStream on the main thread.
- uint64_t windowID = self->mWindowID;
- NS_DispatchToMainThread(NS_NewRunnableFunction("MediaManager::NotifyChromeOfStart",
- [source, domStream, callback, windowID]() mutable {
- source->SetPullEnabled(true);
-
- MediaManager* manager = MediaManager::GetIfExists();
- if (!manager) {
+ mSourceListener->InitializeAsync()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [manager = mManager, domStream, callback,
+ windowListener = mWindowListener]()
+ {
+ // Initiating and starting devices succeeded.
+ // onTracksAvailableCallback must be added to domStream on main thread.
+ domStream->OnTracksAvailable(callback->release());
+ windowListener->ChromeAffectingStateChanged();
+ manager->SendPendingGUMRequest();
+ },[manager = mManager, windowID = mWindowID,
+ onFailure = Move(mOnFailure)](const RefPtr<MediaMgrError>& error)
+ {
+ // Initiating and starting devices failed.
+
+ // Only run if the window is still active for our window listener.
+ if (!(manager->IsWindowStillActive(windowID))) {
return;
}
-
- nsGlobalWindowInner* window =
- nsGlobalWindowInner::GetInnerWindowWithId(windowID);
- if (!window) {
- MOZ_ASSERT_UNREACHABLE("Should have window");
- return;
+ // This is safe since we're on main-thread, and the windowlist can only
+ // be invalidated from the main-thread (see OnNavigation)
+ if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(windowID)) {
+ auto streamError = MakeRefPtr<MediaStreamError>(window->AsInner(), *error);
+ onFailure->OnError(streamError);
}
-
- domStream->OnTracksAvailable(callback->release());
-
- nsresult rv = MediaManager::NotifyRecordingStatusChange(window->AsInner());
- if (NS_FAILED(rv)) {
- MOZ_ASSERT_UNREACHABLE("Should be able to notify chrome");
- return;
- }
-
- manager->SendPendingGUMRequest();
- }));
- return NS_OK;
- }));
+ });
if (!IsPincipalInfoPrivate(mPrincipalInfo)) {
// Call GetPrincipalKey again, this time w/persist = true, to promote
// deviceIds to persistent, in case they're not already. Fire'n'forget.
RefPtr<Pledge<nsCString>> p =
media::GetPrincipalKey(mPrincipalInfo, true);
}
return NS_OK;
@@ -3869,16 +3819,119 @@ SourceListener::Activate(SourceMediaStre
aVideoDevice,
aVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
Preferences::GetBool("media.getusermedia.camera.off_while_disabled.enabled", true));
}
mStream->AddListener(mStreamListener);
}
+RefPtr<SourceListener::InitPromise>
+SourceListener::InitializeAsync()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+ MOZ_DIAGNOSTIC_ASSERT(!mStopped);
+
+ RefPtr<InitPromise> init = MediaManager::PostTask<InitPromise>(__func__,
+ [ stream = mStream
+ , principal = GetPrincipalHandle()
+ , audioDevice = mAudioDeviceState ? mAudioDeviceState->mDevice : nullptr
+ , videoDevice = mVideoDeviceState ? mVideoDeviceState->mDevice : nullptr
+ ](MozPromiseHolder<InitPromise>& aHolder)
+ {
+ if (audioDevice) {
+ nsresult rv = audioDevice->SetTrack(stream, kAudioTrack, principal);
+ if (NS_SUCCEEDED(rv)) {
+ rv = audioDevice->Start();
+ }
+ if (NS_FAILED(rv)) {
+ nsString log;
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ log.AssignASCII("Concurrent mic process limit.");
+ aHolder.Reject(MakeRefPtr<MediaMgrError>(
+ NS_LITERAL_STRING("NotReadableError"), log), __func__);
+ return;
+ }
+ log.AssignASCII("Starting audio failed");
+ aHolder.Reject(MakeRefPtr<MediaMgrError>(
+ NS_LITERAL_STRING("InternalError"), log), __func__);
+ return;
+ }
+ }
+
+ if (videoDevice) {
+ nsresult rv = videoDevice->SetTrack(stream, kVideoTrack, principal);
+ if (NS_SUCCEEDED(rv)) {
+ rv = videoDevice->Start();
+ }
+ if (NS_FAILED(rv)) {
+ if (audioDevice) {
+ if (NS_WARN_IF(NS_FAILED(audioDevice->Stop()))) {
+ MOZ_ASSERT_UNREACHABLE("Stopping audio failed");
+ }
+ }
+ nsString log;
+ log.AssignASCII("Starting video failed");
+ aHolder.Reject(MakeRefPtr<MediaMgrError>(NS_LITERAL_STRING("InternalError"), log), __func__);
+ return;
+ }
+ }
+
+ // Start() queued the tracks to be added synchronously to avoid races
+ stream->FinishAddTracks();
+ stream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
+ LOG(("started all sources"));
+
+ aHolder.Resolve(true, __func__);
+ });
+
+ return init->Then(GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr<SourceListener>(this), this]()
+ {
+ if (mStopped) {
+ // We were shut down during the async init
+ return InitPromise::CreateAndResolve(true, __func__);
+ }
+
+ mStream->SetPullEnabled(true);
+
+ for (DeviceState* state : {mAudioDeviceState.get(),
+ mVideoDeviceState.get()}) {
+ if (!state) {
+ continue;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!state->mTrackEnabled);
+ MOZ_DIAGNOSTIC_ASSERT(!state->mDeviceEnabled);
+ MOZ_DIAGNOSTIC_ASSERT(!state->mStopped);
+
+ state->mDeviceEnabled = true;
+ state->mTrackEnabled = true;
+ }
+ return InitPromise::CreateAndResolve(true, __func__);
+ }, [self = RefPtr<SourceListener>(this), this](RefPtr<MediaMgrError>&& aResult)
+ {
+ if (mStopped) {
+ return InitPromise::CreateAndReject(Move(aResult), __func__);
+ }
+
+ for (DeviceState* state : {mAudioDeviceState.get(),
+ mVideoDeviceState.get()}) {
+ if (!state) {
+ continue;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!state->mTrackEnabled);
+ MOZ_DIAGNOSTIC_ASSERT(!state->mDeviceEnabled);
+ MOZ_DIAGNOSTIC_ASSERT(!state->mStopped);
+
+ state->mStopped = true;
+ }
+ return InitPromise::CreateAndReject(Move(aResult), __func__);
+ });
+}
+
void
SourceListener::Stop()
{
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
if (mStopped) {
return;
}
@@ -4001,17 +4054,16 @@ SourceListener::SetEnabledFor(TrackID aT
state.mTrackEnabled = aEnable;
if (state.mStopped) {
// Device terminally stopped. Updating device state is pointless.
return;
}
-
if (state.mOperationInProgress) {
// If a timer is in progress, it needs to be canceled now so the next
// DisableTrack() gets a fresh start. Canceling will trigger another
// operation.
state.mDisableTimer->Cancel();
return;
}
@@ -4035,17 +4087,17 @@ SourceListener::SetEnabledFor(TrackID aT
: "media.getusermedia.camera.off_while_disabled.delay_ms",
3000));
timerPromise = state.mDisableTimer->WaitFor(offDelay, __func__);
}
typedef MozPromise<nsresult, bool, /* IsExclusive = */ true> DeviceOperationPromise;
RefPtr<SourceListener> self = this;
timerPromise->Then(GetMainThreadSerialEventTarget(), __func__,
- [self, this, &state, aTrackID, aEnable](bool aDummy) mutable {
+ [self, this, &state, aTrackID, aEnable]() mutable {
MOZ_ASSERT(state.mDeviceEnabled != aEnable,
"Device operation hasn't started");
MOZ_ASSERT(state.mOperationInProgress,
"It's our responsibility to reset the inProgress state");
LOG(("SourceListener %p %s %s track %d - starting device operation",
this, aEnable ? "enabling" : "disabling",
aTrackID == kAudioTrack ? "audio" : "video",
@@ -4064,17 +4116,17 @@ SourceListener::SetEnabledFor(TrackID aT
return DeviceOperationPromise::CreateAndResolve(NS_OK, __func__);
}
return MediaManager::PostTask<DeviceOperationPromise>(__func__,
[self, device = state.mDevice, aEnable]
(MozPromiseHolder<DeviceOperationPromise>& h) {
h.Resolve(aEnable ? device->Start() : device->Stop(), __func__);
});
- }, [](bool aDummy) {
+ }, []() {
// Timer was canceled by us. We signal this with NS_ERROR_ABORT.
return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT, __func__);
})->Then(GetMainThreadSerialEventTarget(), __func__,
[self, this, &state, aTrackID, aEnable](nsresult aResult) mutable {
MOZ_ASSERT(state.mOperationInProgress);
state.mOperationInProgress = false;
if (state.mStopped) {
@@ -4124,17 +4176,17 @@ SourceListener::SetEnabledFor(TrackID aT
}
// Track state changed during this operation. We'll start over.
if (state.mTrackEnabled) {
SetEnabledFor(aTrackID, true);
} else {
SetEnabledFor(aTrackID, false);
}
- }, [](bool aDummy) {
+ }, []() {
MOZ_ASSERT_UNREACHABLE("Unexpected and unhandled reject");
});
}
void
SourceListener::StopSharing()
{
MOZ_ASSERT(NS_IsMainThread());