--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -3,16 +3,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MediaManager.h"
#include "AllocationHandle.h"
#include "MediaStreamGraph.h"
+#include "MediaTimer.h"
#include "mozilla/dom/MediaStreamTrack.h"
#include "MediaStreamListener.h"
#include "nsArray.h"
#include "nsContentUtils.h"
#include "nsGlobalWindow.h"
#include "nsHashPropertyBag.h"
#include "nsIEventTarget.h"
#include "nsIUUIDGenerator.h"
@@ -146,26 +147,63 @@ using media::NewRunnableFrom;
using media::NewTaskFrom;
using media::Pledge;
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;
+
+ // true if the application has currently enabled mDevice.
+ // MainThread only.
+ bool mTrackEnabled = true;
+
+ // 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;
+
+ // The underlying device we keep state for. Always non-null.
+ // Threadsafe access, but see method declarations for individual constraints.
+ const RefPtr<MediaDevice> mDevice;
+};
+
class SourceListener : public MediaStreamListener {
public:
SourceListener();
/**
- * Returns the current device for the given track.
- */
- MediaDevice* GetDevice(TrackID aTrackID) const;
-
- /**
* Registers this source listener as belonging to the given window listener.
*/
void Register(GetUserMediaWindowListener* aListener);
/**
* Marks this listener as active and adds itself as a listener to aStream.
*/
void Activate(SourceMediaStream* aStream,
@@ -186,49 +224,66 @@ public:
/**
* Posts a task to stop the device associated with aTrackID and notifies the
* associated window listener that a track was stopped.
* Should this track be the last live one to be stopped, we'll also clean up.
*/
void StopTrack(TrackID aTrackID);
/**
- * Posts a task to disable the device associated with aTrackID and notifies
- * the associated window listener that a track has been disabled.
+ * Gets the main thread MediaTrackSettings from the MediaEngineSource
+ * associated with aTrackID.
*/
- void DisableTrack(TrackID aTrackID);
-
+ void GetSettingsFor(TrackID aTrackID, dom::MediaTrackSettings& aOutSettings) const;
/**
- * Posts a task to enable the device associated with aTrackID and notifies
- * the associated window listener that a track has been enabled.
+ * Posts a task to set the enabled state of the device associated with
+ * aTrackID to aEnabled and notifies the associated window listener that a
+ * track's state has changed.
+ *
+ * Turning the hardware off while the device is disabled is supported for:
+ * - Camera (enabled by default, controlled by pref
+ * "media.getusermedia.camera.off_while_disabled.enabled")
+ * - Microphone (disabled by default, controlled by pref
+ * "media.getusermedia.microphone.off_while_disabled.enabled")
+ * Screen-, app-, or windowsharing is not supported at this time.
+ *
+ * The behavior is also different between disabling and enabling a device.
+ * While enabling is immediate, disabling only happens after a delay.
+ * This is now defaulting to 3 seconds but can be overriden by prefs:
+ * - "media.getusermedia.camera.off_while_disabled.delay_ms" and
+ * - "media.getusermedia.microphone.off_while_disabled.delay_ms".
+ *
+ * The delay is in place to prevent misuse by malicious sites. If a track is
+ * re-enabled before the delay has passed, the device will not be touched
+ * until another disable followed by the full delay happens.
*/
- void EnableTrack(TrackID aTrackID);
+ void SetEnabledFor(TrackID aTrackID, bool aEnabled);
/**
* Stops all screen/app/window/audioCapture sharing, but not camera or
* microphone.
*/
void StopSharing();
MediaStream* Stream() const
{
return mStream;
}
SourceMediaStream* GetSourceStream();
MediaDevice* GetAudioDevice() const
{
- return mAudioDevice;
+ return mAudioDeviceState ? mAudioDeviceState->mDevice.get() : nullptr;
}
MediaDevice* GetVideoDevice() const
{
- return mVideoDevice;
+ return mVideoDeviceState ? mVideoDeviceState->mDevice.get() : nullptr;
}
void NotifyPull(MediaStreamGraph* aGraph,
StreamTime aDesiredTime) override;
void NotifyEvent(MediaStreamGraph* aGraph,
MediaStreamGraphEvent aEvent) override;
@@ -270,48 +325,52 @@ public:
ApplyConstraintsToTrack(nsPIDOMWindowInner* aWindow,
TrackID aTrackID,
const dom::MediaTrackConstraints& aConstraints,
dom::CallerType aCallerType);
PrincipalHandle GetPrincipalHandle() const;
private:
+ /**
+ * Returns a pointer to the device state for aTrackID.
+ *
+ * This is intended for internal use where we need to figure out which state
+ * corresponds to aTrackID, not for availability checks. As such, we assert
+ * that the device does indeed exist.
+ *
+ * Since this is a raw pointer and the state lifetime depends on the
+ * SourceListener's lifetime, it's internal use only.
+ */
+ DeviceState& GetDeviceStateFor(TrackID aTrackID) const;
+
// true after this listener has had all devices stopped. MainThread only.
bool mStopped;
// true after the stream this listener is listening to has finished in the
// MediaStreamGraph. MainThread only.
bool mFinished;
// true after this listener has been removed from its MediaStream.
// MainThread only.
bool mRemoved;
- // true if we have stopped mAudioDevice. MainThread only.
- bool mAudioStopped;
-
- // true if we have stopped mVideoDevice. MainThread only.
- bool mVideoStopped;
-
// never ever indirect off this; just for assertions
PRThread* mMainThreadCheck;
// Set in Register() on main thread, then read from any thread.
PrincipalHandle mPrincipalHandle;
// Weak pointer to the window listener that owns us. MainThread only.
GetUserMediaWindowListener* mWindowListener;
- // Set at Activate on MainThread
-
// Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
- // No locking needed as they're only addrefed except on the MediaManager thread
- RefPtr<MediaDevice> mAudioDevice; // threadsafe refcnt
- RefPtr<MediaDevice> mVideoDevice; // threadsafe refcnt
+ // No locking needed as they're set on Activate() and never assigned to again.
+ UniquePtr<DeviceState> mAudioDeviceState;
+ UniquePtr<DeviceState> mVideoDeviceState;
RefPtr<SourceMediaStream> mStream; // threadsafe refcnt
};
/**
* This class represents a WindowID and handles all MediaStreamListeners
* (here subclassed as SourceListeners) used to feed GetUserMedia source
* streams. It proxies feedback from them into messages for browser chrome.
* The SourceListeners are used to Start() and Stop() the underlying
@@ -334,57 +393,43 @@ public:
{}
/**
* Registers an inactive gUM source listener for this WindowListener.
*/
void Register(SourceListener* aListener)
{
MOZ_ASSERT(NS_IsMainThread());
- if (!aListener || aListener->Activated()) {
- MOZ_ASSERT(false, "Invalid listener");
- return;
- }
- if (mInactiveListeners.Contains(aListener)) {
- MOZ_ASSERT(false, "Already registered");
- return;
- }
- if (mActiveListeners.Contains(aListener)) {
- MOZ_ASSERT(false, "Already activated");
- return;
- }
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(!aListener->Activated());
+ MOZ_ASSERT(!mInactiveListeners.Contains(aListener), "Already registered");
+ MOZ_ASSERT(!mActiveListeners.Contains(aListener), "Already activated");
aListener->Register(this);
mInactiveListeners.AppendElement(aListener);
}
/**
* Activates an already registered and inactive gUM source listener for this
* WindowListener.
*/
void Activate(SourceListener* aListener,
SourceMediaStream* aStream,
MediaDevice* aAudioDevice,
MediaDevice* aVideoDevice)
{
MOZ_ASSERT(NS_IsMainThread());
-
- if (!aListener || aListener->Activated()) {
- MOZ_ASSERT(false, "Cannot activate already activated source listener");
- return;
- }
-
- if (!mInactiveListeners.RemoveElement(aListener)) {
- MOZ_ASSERT(false, "Cannot activate non-registered source listener");
- return;
- }
-
- RefPtr<SourceListener> listener = aListener;
- listener->Activate(aStream, aAudioDevice, aVideoDevice);
- mActiveListeners.AppendElement(listener.forget());
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(!aListener->Activated());
+ MOZ_ASSERT(mInactiveListeners.Contains(aListener), "Must be registered to activate");
+ MOZ_ASSERT(!mActiveListeners.Contains(aListener), "Already activated");
+
+ mInactiveListeners.RemoveElement(aListener);
+ aListener->Activate(aStream, aAudioDevice, aVideoDevice);
+ mActiveListeners.AppendElement(do_AddRef(aListener));
}
// Can be invoked from EITHER MainThread or MSG thread
void Stop()
{
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
for (auto& source : mActiveListeners) {
@@ -700,17 +745,17 @@ MediaDevice::MediaDevice(MediaEngineSour
{
}
/**
* Helper functions that implement the constraints algorithm from
* http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
*/
-bool
+/* static */ bool
MediaDevice::StringsContain(const OwningStringOrStringSequence& aStrings,
nsString aN)
{
return aStrings.IsString() ? aStrings.GetAsString() == aN
: aStrings.GetAsStringSequence().Contains(aN);
}
/* static */ uint32_t
@@ -747,16 +792,18 @@ MediaDevice::FitnessDistance(nsString aN
}
}
uint32_t
MediaDevice::GetBestFitnessDistance(
const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
bool aIsChrome)
{
+ MOZ_ASSERT(MediaManager::IsInMediaThread());
+
nsString mediaSource;
GetMediaSource(mediaSource);
// This code is reused for audio, where the mediaSource constraint does
// not currently have a function, but because it defaults to "camera" in
// webidl, we ignore it for audio here.
if (!mediaSource.EqualsASCII("microphone")) {
for (const auto& constraint : aConstraintSets) {
@@ -770,128 +817,143 @@ MediaDevice::GetBestFitnessDistance(
// Pass in device's origin-specific id for deviceId constraint comparison.
const nsString& id = aIsChrome ? mRawID : mID;
return mSource->GetBestFitnessDistance(aConstraintSets, id);
}
NS_IMETHODIMP
MediaDevice::GetName(nsAString& aName)
{
+ MOZ_ASSERT(NS_IsMainThread());
aName.Assign(mName);
return NS_OK;
}
NS_IMETHODIMP
MediaDevice::GetType(nsAString& aType)
{
+ MOZ_ASSERT(NS_IsMainThread());
aType.Assign(mType);
return NS_OK;
}
NS_IMETHODIMP
MediaDevice::GetId(nsAString& aID)
{
+ MOZ_ASSERT(NS_IsMainThread());
aID.Assign(mID);
return NS_OK;
}
NS_IMETHODIMP
MediaDevice::GetRawId(nsAString& aID)
{
+ MOZ_ASSERT(NS_IsMainThread());
aID.Assign(mRawID);
return NS_OK;
}
NS_IMETHODIMP
MediaDevice::GetScary(bool* aScary)
{
*aScary = mScary;
return NS_OK;
}
void
MediaDevice::GetSettings(dom::MediaTrackSettings& aOutSettings) const
{
+ MOZ_ASSERT(NS_IsMainThread());
mSource->GetSettings(aOutSettings);
}
+ // Threadsafe since mSource is const.
NS_IMETHODIMP
MediaDevice::GetMediaSource(nsAString& aMediaSource)
{
- MediaSourceEnum source = GetMediaSource();
- if (source == MediaSourceEnum::Microphone) {
- aMediaSource.AssignLiteral(u"microphone");
- } else if (source == MediaSourceEnum::AudioCapture) {
- aMediaSource.AssignLiteral(u"audioCapture");
- } else if (source == MediaSourceEnum::Window) { // this will go away
- aMediaSource.AssignLiteral(u"window");
- } else { // all the rest are shared
- aMediaSource.Assign(NS_ConvertUTF8toUTF16(
- dom::MediaSourceEnumValues::strings[uint32_t(source)].value));
- }
+ aMediaSource.Assign(NS_ConvertUTF8toUTF16(
+ dom::MediaSourceEnumValues::strings[uint32_t(GetMediaSource())].value));
return NS_OK;
}
-nsresult MediaDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
- const MediaEnginePrefs &aPrefs,
- const ipc::PrincipalInfo& aPrincipalInfo,
- const char** aOutBadConstraint)
+nsresult
+MediaDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
+ const MediaEnginePrefs &aPrefs,
+ const ipc::PrincipalInfo& aPrincipalInfo,
+ const char** aOutBadConstraint)
{
+ MOZ_ASSERT(MediaManager::IsInMediaThread());
return mSource->Allocate(aConstraints,
aPrefs,
mID,
aPrincipalInfo,
getter_AddRefs(mAllocationHandle),
aOutBadConstraint);
}
-nsresult MediaDevice::SetTrack(const RefPtr<SourceMediaStream>& aStream,
- TrackID aTrackID,
- const PrincipalHandle& aPrincipalHandle)
+nsresult
+MediaDevice::SetTrack(const RefPtr<SourceMediaStream>& aStream,
+ TrackID aTrackID,
+ const PrincipalHandle& aPrincipalHandle)
{
+ MOZ_ASSERT(MediaManager::IsInMediaThread());
return mSource->SetTrack(mAllocationHandle, aStream, aTrackID, aPrincipalHandle);
}
-nsresult MediaDevice::Start()
+nsresult
+MediaDevice::Start()
{
+ MOZ_ASSERT(MediaManager::IsInMediaThread());
return mSource->Start(mAllocationHandle);
}
-nsresult MediaDevice::Reconfigure(const dom::MediaTrackConstraints &aConstraints,
- const MediaEnginePrefs &aPrefs,
- const char** aOutBadConstraint)
+nsresult
+MediaDevice::Reconfigure(const dom::MediaTrackConstraints &aConstraints,
+ const MediaEnginePrefs &aPrefs,
+ const char** aOutBadConstraint)
{
+ MOZ_ASSERT(MediaManager::IsInMediaThread());
return mSource->Reconfigure(mAllocationHandle,
aConstraints,
aPrefs,
mID,
aOutBadConstraint);
}
-nsresult MediaDevice::Stop()
+nsresult
+MediaDevice::Stop()
{
+ MOZ_ASSERT(MediaManager::IsInMediaThread());
return mSource->Stop(mAllocationHandle);
}
-nsresult MediaDevice::Deallocate()
+nsresult
+MediaDevice::Deallocate()
{
+ MOZ_ASSERT(MediaManager::IsInMediaThread());
return mSource->Deallocate(mAllocationHandle);
}
-void MediaDevice::Pull(const RefPtr<SourceMediaStream>& aStream,
- TrackID aTrackID,
- StreamTime aDesiredTime,
- const PrincipalHandle& aPrincipal)
+void
+MediaDevice::Pull(const RefPtr<SourceMediaStream>& aStream,
+ TrackID aTrackID,
+ StreamTime aDesiredTime,
+ const PrincipalHandle& aPrincipal)
{
+ // This is on the graph thread, but mAllocationHandle is safe since we never
+ // change it after it's been set, which is guaranteed to happen before
+ // registering the listener for pulls.
mSource->Pull(mAllocationHandle, aStream, aTrackID, aDesiredTime, aPrincipal);
}
dom::MediaSourceEnum
MediaDevice::GetMediaSource() const
{
+ // Threadsafe because mSource is const. GetMediaSource() might have other
+ // requirements.
return mSource->GetMediaSource();
}
static bool
IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) {
return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
}
@@ -1107,39 +1169,39 @@ public:
return mListener->ApplyConstraintsToTrack(aWindow, mTrackID,
aConstraints, aCallerType);
}
void
GetSettings(dom::MediaTrackSettings& aOutSettings) override
{
if (mListener) {
- mListener->GetDevice(mTrackID)->GetSettings(aOutSettings);
+ mListener->GetSettingsFor(mTrackID, aOutSettings);
}
}
void Stop() override
{
if (mListener) {
mListener->StopTrack(mTrackID);
mListener = nullptr;
}
}
void Disable() override
{
if (mListener) {
- mListener->DisableTrack(mTrackID);
+ mListener->SetEnabledFor(mTrackID, false);
}
}
void Enable() override
{
if (mListener) {
- mListener->EnableTrack(mTrackID);
+ mListener->SetEnabledFor(mTrackID, true);
}
}
protected:
~LocalTrackSource() {}
RefPtr<SourceListener> mListener;
const MediaSourceEnum mSource;
@@ -1161,32 +1223,30 @@ public:
"GetUserMediaStreamRunnable::DOMMediaStreamMainThreadHolder",
DOMLocalMediaStream::CreateSourceStreamAsInput(window, msg,
new FakeTrackSourceGetter(principal)));
stream = domStream->GetInputStream()->AsSourceStream();
if (mAudioDevice) {
nsString audioDeviceName;
mAudioDevice->GetName(audioDeviceName);
- const MediaSourceEnum source =
- mAudioDevice->GetMediaSource();
+ const MediaSourceEnum source = mAudioDevice->GetMediaSource();
RefPtr<MediaStreamTrackSource> audioSource =
new LocalTrackSource(principal, audioDeviceName, mSourceListener,
source, kAudioTrack, mPeerIdentity);
MOZ_ASSERT(IsOn(mConstraints.mAudio));
RefPtr<MediaStreamTrack> track =
domStream->CreateDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioSource,
GetInvariant(mConstraints.mAudio));
domStream->AddTrackInternal(track);
}
if (mVideoDevice) {
nsString videoDeviceName;
mVideoDevice->GetName(videoDeviceName);
- const MediaSourceEnum source =
- mVideoDevice->GetMediaSource();
+ const MediaSourceEnum source = mVideoDevice->GetMediaSource();
RefPtr<MediaStreamTrackSource> videoSource =
new LocalTrackSource(principal, videoDeviceName, mSourceListener,
source, kVideoTrack, mPeerIdentity);
MOZ_ASSERT(IsOn(mConstraints.mVideo));
RefPtr<MediaStreamTrack> track =
domStream->CreateDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoSource,
GetInvariant(mConstraints.mVideo));
domStream->AddTrackInternal(track);
@@ -1333,16 +1393,18 @@ private:
// Source getter returning full list
static void
GetSources(MediaEngine *engine, MediaSourceEnum aSrcType,
nsTArray<RefPtr<MediaDevice>>& aResult,
const char* media_device_name = nullptr)
{
+ MOZ_ASSERT(MediaManager::IsInMediaThread());
+
nsTArray<RefPtr<MediaEngineSource>> sources;
engine->EnumerateDevices(aSrcType, &sources);
/*
* We're allowing multiple tabs to access the same camera for parity
* with Chrome. See bug 811757 for some of the issues surrounding
* this decision. To disallow, we'd filter by IsAvailable() as we used
* to.
@@ -1519,34 +1581,34 @@ public:
if (mAudioDevice) {
auto& constraints = GetInvariant(mConstraints.mAudio);
rv = mAudioDevice->Allocate(constraints, mPrefs, mPrincipalInfo,
&badConstraint);
if (NS_FAILED(rv)) {
errorMsg = "Failed to allocate audiosource";
if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
- nsTArray<RefPtr<MediaDevice>> audios;
- audios.AppendElement(mAudioDevice);
+ nsTArray<RefPtr<MediaDevice>> devices;
+ devices.AppendElement(mAudioDevice);
badConstraint = MediaConstraintsHelper::SelectSettings(
- NormalizedConstraints(constraints), audios, mIsChrome);
+ NormalizedConstraints(constraints), devices, mIsChrome);
}
}
}
if (!errorMsg && mVideoDevice) {
auto& constraints = GetInvariant(mConstraints.mVideo);
rv = mVideoDevice->Allocate(constraints, mPrefs, mPrincipalInfo,
&badConstraint);
if (NS_FAILED(rv)) {
errorMsg = "Failed to allocate videosource";
if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
- nsTArray<RefPtr<MediaDevice>> videos;
- videos.AppendElement(mVideoDevice);
+ nsTArray<RefPtr<MediaDevice>> devices;
+ devices.AppendElement(mVideoDevice);
badConstraint = MediaConstraintsHelper::SelectSettings(
- NormalizedConstraints(constraints), videos, mIsChrome);
+ NormalizedConstraints(constraints), devices, mIsChrome);
}
if (mAudioDevice) {
mAudioDevice->Deallocate();
}
}
}
if (errorMsg) {
LOG(("%s %" PRIu32, errorMsg, static_cast<uint32_t>(rv)));
@@ -3641,81 +3703,64 @@ MediaManager::IsActivelyCapturingOrHasAP
return audio == nsIPermissionManager::ALLOW_ACTION ||
video == nsIPermissionManager::ALLOW_ACTION;
}
SourceListener::SourceListener()
: mStopped(false)
, mFinished(false)
, mRemoved(false)
- , mAudioStopped(false)
- , mVideoStopped(false)
, mMainThreadCheck(nullptr)
, mPrincipalHandle(PRINCIPAL_HANDLE_NONE)
, mWindowListener(nullptr)
{}
-MediaDevice*
-SourceListener::GetDevice(TrackID aTrackID) const
-{
- switch (aTrackID) {
- case kAudioTrack:
- return mAudioDevice;
- case kVideoTrack:
- return mVideoDevice;
- default:
- MOZ_ASSERT(false, "Unknown track id");
- return nullptr;
- }
-}
-
void
SourceListener::Register(GetUserMediaWindowListener* aListener)
{
LOG(("SourceListener %p registering with window listener %p", this, aListener));
- if (mWindowListener) {
- MOZ_ASSERT(false, "Already registered");
- return;
- }
- if (Activated()) {
- MOZ_ASSERT(false, "Already activated");
- return;
- }
- if (!aListener) {
- MOZ_ASSERT(false, "No listener");
- return;
- }
+ MOZ_ASSERT(aListener, "No listener");
+ MOZ_ASSERT(!mWindowListener, "Already registered");
+ MOZ_ASSERT(!Activated(), "Already activated");
+
mPrincipalHandle = aListener->GetPrincipalHandle();
mWindowListener = aListener;
}
void
SourceListener::Activate(SourceMediaStream* aStream,
MediaDevice* aAudioDevice,
MediaDevice* aVideoDevice)
{
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
LOG(("SourceListener %p activating audio=%p video=%p", this, aAudioDevice, aVideoDevice));
- if (mStopped) {
- MOZ_ASSERT(false, "Cannot activate stopped source listener");
- return;
- }
-
- if (Activated()) {
- MOZ_ASSERT(false, "Already activated");
- return;
- }
+ MOZ_ASSERT(!mStopped, "Cannot activate stopped source listener");
+ MOZ_ASSERT(!Activated(), "Already activated");
mMainThreadCheck = GetCurrentVirtualThread();
mStream = aStream;
- mAudioDevice = aAudioDevice;
- mVideoDevice = aVideoDevice;
+ if (aAudioDevice) {
+ mAudioDeviceState =
+ MakeUnique<DeviceState>(
+ aAudioDevice,
+ aAudioDevice->GetMediaSource() == dom::MediaSourceEnum::Microphone &&
+ Preferences::GetBool("media.getusermedia.microphone.off_while_disabled.enabled", false));
+ }
+
+ if (aVideoDevice) {
+ mVideoDeviceState =
+ MakeUnique<DeviceState>(
+ aVideoDevice,
+ aVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
+ Preferences::GetBool("media.getusermedia.camera.off_while_disabled.enabled", true));
+ }
+
mStream->AddListener(this);
}
void
SourceListener::Stop()
{
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
@@ -3726,44 +3771,44 @@ SourceListener::Stop()
LOG(("SourceListener %p stopping", this));
// StopSharing() has some special logic, at least for audio capture.
// It must be called when all tracks have stopped, before setting mStopped.
StopSharing();
mStopped = true;
- if (!Activated()) {
- MOZ_ASSERT(false, "There are no devices or any source stream to stop");
- return;
- }
-
- if (mAudioDevice && !mAudioStopped) {
+ MOZ_ASSERT(Activated(), "There are no devices or any source stream to stop");
+ MOZ_ASSERT(mStream, "Can't end tracks. No source stream.");
+
+ if (mAudioDeviceState && !mAudioDeviceState->mStopped) {
StopTrack(kAudioTrack);
}
- if (mVideoDevice && !mVideoStopped) {
+ if (mVideoDeviceState && !mVideoDeviceState->mStopped) {
StopTrack(kVideoTrack);
}
- RefPtr<SourceMediaStream> source = mStream;
- if (!source) {
- MOZ_ASSERT(false, "Can't end tracks. No source stream.");
- return;
- }
-
- MediaManager::PostTask(NewTaskFrom([source]() {
+ MediaManager::PostTask(NewTaskFrom([source = mStream]() {
MOZ_ASSERT(MediaManager::IsInMediaThread());
source->EndAllTrackAndFinish();
}));
}
void
SourceListener::Remove()
{
MOZ_ASSERT(NS_IsMainThread());
+
+ if (mAudioDeviceState) {
+ mAudioDeviceState->mDisableTimer->Cancel();
+ }
+ if (mVideoDeviceState) {
+ mVideoDeviceState->mDisableTimer->Cancel();
+ }
+
if (!mStream || mRemoved) {
return;
}
LOG(("SourceListener %p removed on purpose, mFinished = %d", this, (int) mFinished));
mRemoved = true; // RemoveListener is async, avoid races
mWindowListener = nullptr;
@@ -3776,196 +3821,234 @@ SourceListener::Remove()
mStream->RemoveListener(this);
}
}
void
SourceListener::StopTrack(TrackID aTrackID)
{
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
-
- RefPtr<MediaDevice> device;
-
- if (!Activated()) {
- MOZ_ASSERT(false, "No device to stop");
+ MOZ_ASSERT(Activated(), "No device to stop");
+ MOZ_ASSERT(aTrackID == kAudioTrack || aTrackID == kVideoTrack,
+ "Unknown track id");
+ DeviceState& state = GetDeviceStateFor(aTrackID);
+
+ LOG(("SourceListener %p stopping %s track %d",
+ this, aTrackID == kAudioTrack ? "audio" : "video", aTrackID));
+
+ if (state.mStopped) {
+ // device already stopped.
return;
}
-
- switch (aTrackID) {
- case kAudioTrack: {
- LOG(("SourceListener %p stopping audio track %d", this, aTrackID));
- if (!mAudioDevice) {
- NS_ASSERTION(false, "Can't stop audio. No device.");
- return;
- }
- if (mAudioStopped) {
- // Audio already stopped
- return;
- }
- device = mAudioDevice;
- mAudioStopped = true;
- break;
- }
- case kVideoTrack: {
- LOG(("SourceListener %p stopping video track %d", this, aTrackID));
- if (!mVideoDevice) {
- NS_ASSERTION(false, "Can't stop video. No device.");
- return;
- }
- if (mVideoStopped) {
- // Video already stopped
- return;
- }
- device = mVideoDevice;
- mVideoStopped = true;
- break;
- }
- default: {
- MOZ_ASSERT(false, "Unknown track id");
- return;
- }
- }
-
- MediaManager::PostTask(NewTaskFrom([device]() {
+ state.mStopped = true;
+
+ state.mDisableTimer->Cancel();
+
+ MediaManager::PostTask(NewTaskFrom([device = state.mDevice]() {
device->Stop();
device->Deallocate();
}));
- if ((!mAudioDevice || mAudioStopped) &&
- (!mVideoDevice || mVideoStopped)) {
+ if ((!mAudioDeviceState || mAudioDeviceState->mStopped) &&
+ (!mVideoDeviceState || mVideoDeviceState->mStopped)) {
LOG(("SourceListener %p this was the last track stopped", this));
Stop();
}
- if (!mWindowListener) {
- MOZ_ASSERT(false, "Should still have window listener");
- return;
- }
+ MOZ_ASSERT(mWindowListener, "Should still have window listener");
mWindowListener->ChromeAffectingStateChanged();
}
void
-SourceListener::DisableTrack(TrackID aTrackID)
+SourceListener::GetSettingsFor(TrackID aTrackID,
+ dom::MediaTrackSettings& aOutSettings) const
{
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
-
- if (!Activated()) {
- MOZ_ASSERT(false, "No device to disable");
- return;
- }
-
- RefPtr<MediaDevice> device;
-
- switch (aTrackID) {
- case kAudioTrack: {
- LOG(("SourceListener %p disabling audio track %d", this, aTrackID));
- if (!mAudioDevice) {
- NS_ASSERTION(false, "Can't disable audio. No device.");
- return;
- }
- if (mAudioStopped) {
- // Audio stopped. Disabling is pointless.
- return;
- }
- device = mAudioDevice;
- break;
- }
- case kVideoTrack: {
- LOG(("SourceListener %p disabling video track %d", this, aTrackID));
- if (!mVideoDevice) {
- NS_ASSERTION(false, "Can't disable video. No device.");
- return;
- }
- if (mVideoStopped) {
- // Video stopped. Disabling is pointless.
- return;
- }
- device = mVideoDevice;
- break;
- }
- default: {
- MOZ_ASSERT(false, "Unknown track id");
- return;
- }
- }
-
- // XXX Later patch
+ GetDeviceStateFor(aTrackID).mDevice->GetSettings(aOutSettings);
}
void
-SourceListener::EnableTrack(TrackID aTrackID)
+SourceListener::SetEnabledFor(TrackID aTrackID, bool aEnable)
{
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
-
- if (!Activated()) {
- MOZ_ASSERT(false, "No device to enable");
+ MOZ_ASSERT(Activated(), "No device to set enabled state for");
+ MOZ_ASSERT(aTrackID == kAudioTrack || aTrackID == kVideoTrack,
+ "Unknown track id");
+
+ if (mRemoved) {
+ return;
+ }
+
+ LOG(("SourceListener %p %s %s track %d",
+ this, aEnable ? "enabling" : "disabling",
+ aTrackID == kAudioTrack ? "audio" : "video", aTrackID));
+
+ DeviceState& state = GetDeviceStateFor(aTrackID);
+
+ 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;
+ }
+
+ if (state.mDeviceEnabled == aEnable) {
+ // Device is already in the desired state.
return;
}
- RefPtr<MediaDevice> device;
-
- switch (aTrackID) {
- case kAudioTrack: {
- LOG(("SourceListener %p enabling audio track %d", this, aTrackID));
- if (!mAudioDevice) {
- NS_ASSERTION(false, "Can't enable audio. No device.");
- return;
+ // All paths from here on must end in setting `state.mOperationInProgress`
+ // to false.
+ state.mOperationInProgress = true;
+
+ RefPtr<MediaTimerPromise> timerPromise;
+ if (aEnable) {
+ timerPromise = MediaTimerPromise::CreateAndResolve(true, __func__);
+ } else {
+ const TimeDuration offDelay = TimeDuration::FromMilliseconds(
+ Preferences::GetUint(
+ aTrackID == kAudioTrack
+ ? "media.getusermedia.microphone.off_while_disabled.delay_ms"
+ : "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 {
+ 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",
+ aTrackID));
+
+ state.mDeviceEnabled = aEnable;
+
+ if (mWindowListener) {
+ mWindowListener->ChromeAffectingStateChanged();
}
- if (mAudioStopped) {
- // Audio stopped. Enabling is pointless.
+
+ if (!state.mOffWhileDisabled) {
+ // If the feature to turn a device off while disabled is itself disabled
+ // we shortcut the device operation and tell the ux-updating code
+ // that everything went fine.
+ return DeviceOperationPromise::CreateAndResolve(NS_OK, __func__);
+ }
+
+ RefPtr<DeviceOperationPromise::Private> promise =
+ new DeviceOperationPromise::Private(__func__);
+ MediaManager::PostTask(NewTaskFrom([self, device = state.mDevice,
+ aEnable, promise]() mutable {
+ promise->Resolve(aEnable ? device->Start() : device->Stop(), __func__);
+ }));
+ RefPtr<DeviceOperationPromise> result = promise.get();
+ return result;
+ }, [](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) {
+ // Device was stopped on main thread during the operation. Nothing to do.
return;
}
- device = mAudioDevice;
- break;
- }
- case kVideoTrack: {
- LOG(("SourceListener %p enabling video track %d", this, aTrackID));
- if (!mVideoDevice) {
- NS_ASSERTION(false, "Can't enable video. No device.");
+
+ LOG(("SourceListener %p %s %s track %d %s",
+ this,
+ aEnable ? "enabling" : "disabling",
+ aTrackID == kAudioTrack ? "audio" : "video",
+ aTrackID,
+ NS_SUCCEEDED(aResult) ? "succeeded" : "failed"));
+
+ if (NS_FAILED(aResult) && aResult != NS_ERROR_ABORT) {
+ // This path handles errors from starting or stopping the device.
+ // NS_ERROR_ABORT are for cases where *we* aborted. They need graceful
+ // handling.
+ MOZ_ASSERT(state.mDeviceEnabled != aEnable,
+ "If operating the device failed, the device's `enabled` "
+ "state must remain at its old value");
+ if (aEnable) {
+ // Starting the device failed. Stopping the track here will make the
+ // MediaStreamTrack end after a pass through the MediaStreamGraph.
+ StopTrack(aTrackID);
+ } else {
+ // Stopping the device failed. This is odd, but not fatal.
+ MOZ_ASSERT_UNREACHABLE("The device should be stoppable");
+
+ // To keep our internal state sane in this case, we disallow future
+ // stops due to disable.
+ state.mOffWhileDisabled = false;
+ }
return;
}
- if (mVideoStopped) {
- // Video stopped. Enabling is pointless.
+
+ // This path is for a device operation aResult that was success or
+ // NS_ERROR_ABORT (*we* canceled the operation).
+ // At this point we have to follow up on the intended state, i.e., update
+ // the device state if the track state changed in the meantime.
+ MOZ_ASSERT_IF(NS_SUCCEEDED(aResult), state.mDeviceEnabled == aEnable);
+
+ if (state.mTrackEnabled == state.mDeviceEnabled) {
+ // Intended state is same as device's current state.
+ // Nothing more to do.
return;
}
- device = mVideoDevice;
- break;
- }
- default: {
- MOZ_ASSERT(false, "Unknown track id");
- return;
- }
- }
-
- // XXX Later patch
+
+ // 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());
MOZ_RELEASE_ASSERT(mWindowListener);
if (mStopped) {
return;
}
LOG(("SourceListener %p StopSharing", this));
- if (mVideoDevice &&
- (mVideoDevice->GetMediaSource() == MediaSourceEnum::Screen ||
- mVideoDevice->GetMediaSource() == MediaSourceEnum::Application ||
- mVideoDevice->GetMediaSource() == MediaSourceEnum::Window)) {
+ if (mVideoDeviceState &&
+ (mVideoDeviceState->mDevice->GetMediaSource() == MediaSourceEnum::Screen ||
+ mVideoDeviceState->mDevice->GetMediaSource() == MediaSourceEnum::Application ||
+ mVideoDeviceState->mDevice->GetMediaSource() == MediaSourceEnum::Window)) {
// We want to stop the whole stream if there's no audio;
// just the video track if we have both.
// StopTrack figures this out for us.
StopTrack(kVideoTrack);
}
- if (mAudioDevice &&
- mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
+ if (mAudioDeviceState &&
+ mAudioDeviceState->mDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
uint64_t windowID = mWindowListener->WindowID();
nsCOMPtr<nsPIDOMWindowInner> window = nsGlobalWindowInner::GetInnerWindowWithId(windowID)->AsInner();
MOZ_RELEASE_ASSERT(window);
window->SetAudioCapture(false);
MediaStreamGraph* graph =
MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER, window);
graph->UnregisterCaptureStreamForWindow(windowID);
mStream->Destroy();
@@ -3974,30 +4057,29 @@ SourceListener::StopSharing()
SourceMediaStream*
SourceListener::GetSourceStream()
{
NS_ASSERTION(mStream,"Getting stream from never-activated SourceListener");
return mStream;
}
+
// Proxy NotifyPull() to sources
void
SourceListener::NotifyPull(MediaStreamGraph* aGraph,
StreamTime aDesiredTime)
{
- // Currently audio sources ignore NotifyPull, but they could
- // watch it especially for fake audio.
- if (mAudioDevice) {
- mAudioDevice->Pull(mStream, kAudioTrack,
- aDesiredTime, mPrincipalHandle);
+ if (mAudioDeviceState) {
+ mAudioDeviceState->mDevice->Pull(mStream, kAudioTrack,
+ aDesiredTime, mPrincipalHandle);
}
- if (mVideoDevice) {
- mVideoDevice->Pull(mStream, kVideoTrack,
- aDesiredTime, mPrincipalHandle);
+ if (mVideoDeviceState) {
+ mVideoDeviceState->mDevice->Pull(mStream, kVideoTrack,
+ aDesiredTime, mPrincipalHandle);
}
}
void
SourceListener::NotifyEvent(MediaStreamGraph* aGraph,
MediaStreamGraphEvent aEvent)
{
nsCOMPtr<nsIEventTarget> target;
@@ -4065,88 +4147,85 @@ SourceListener::NotifyRemoved()
mWindowListener = nullptr;
}
bool
SourceListener::CapturingVideo() const
{
MOZ_ASSERT(NS_IsMainThread());
- return Activated() && mVideoDevice && !mVideoStopped &&
- !mVideoDevice->mSource->IsAvailable() &&
- mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
- (!mVideoDevice->mSource->IsFake() ||
+ return Activated() && mVideoDeviceState &&
+ !mVideoDeviceState->mStopped &&
+ mVideoDeviceState->mDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
+ (!mVideoDeviceState->mDevice->mSource->IsFake() ||
Preferences::GetBool("media.navigator.permission.fake"));
}
bool
SourceListener::CapturingAudio() const
{
MOZ_ASSERT(NS_IsMainThread());
- return Activated() && mAudioDevice && !mAudioStopped &&
- !mAudioDevice->mSource->IsAvailable() &&
- (!mAudioDevice->mSource->IsFake() ||
+ return Activated() && mAudioDeviceState &&
+ !mAudioDeviceState->mStopped &&
+ mAudioDeviceState->mDevice->GetMediaSource() == dom::MediaSourceEnum::Microphone &&
+ (mAudioDeviceState->mDevice->mSource->IsFake() ||
Preferences::GetBool("media.navigator.permission.fake"));
}
bool
SourceListener::CapturingScreen() const
{
MOZ_ASSERT(NS_IsMainThread());
- return Activated() && mVideoDevice && !mVideoStopped &&
- !mVideoDevice->mSource->IsAvailable() &&
- mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen;
+ return Activated() && mVideoDeviceState &&
+ !mVideoDeviceState->mStopped &&
+ mVideoDeviceState->mDevice->GetMediaSource() == dom::MediaSourceEnum::Screen;
}
bool
SourceListener::CapturingWindow() const
{
MOZ_ASSERT(NS_IsMainThread());
- return Activated() && mVideoDevice && !mVideoStopped &&
- !mVideoDevice->mSource->IsAvailable() &&
- mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window;
+ return Activated() && mVideoDeviceState &&
+ !mVideoDeviceState->mStopped &&
+ mVideoDeviceState->mDevice->GetMediaSource() == dom::MediaSourceEnum::Window;
}
bool
SourceListener::CapturingApplication() const
{
MOZ_ASSERT(NS_IsMainThread());
- return Activated() && mVideoDevice && !mVideoStopped &&
- !mVideoDevice->mSource->IsAvailable() &&
- mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application;
+ return Activated() && mVideoDeviceState &&
+ !mVideoDeviceState->mStopped &&
+ mVideoDeviceState->mDevice->GetMediaSource() == dom::MediaSourceEnum::Application;
}
bool
SourceListener::CapturingBrowser() const
{
MOZ_ASSERT(NS_IsMainThread());
- return Activated() && mVideoDevice && !mVideoStopped &&
- !mVideoDevice->mSource->IsAvailable() &&
- mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Browser;
+ return Activated() && mVideoDeviceState &&
+ !mVideoDeviceState->mStopped &&
+ mVideoDeviceState->mDevice->GetMediaSource() == dom::MediaSourceEnum::Browser;
}
already_AddRefed<PledgeVoid>
SourceListener::ApplyConstraintsToTrack(
nsPIDOMWindowInner* aWindow,
TrackID aTrackID,
const MediaTrackConstraints& aConstraintsPassedIn,
dom::CallerType aCallerType)
{
MOZ_ASSERT(NS_IsMainThread());
RefPtr<PledgeVoid> p = new PledgeVoid();
- // XXX to support multiple tracks of a type in a stream, this should key off
- // the TrackID and not just the type
- RefPtr<MediaDevice> audioDevice =
- aTrackID == kAudioTrack ? mAudioDevice.get() : nullptr;
- RefPtr<MediaDevice> videoDevice =
- aTrackID == kVideoTrack ? mVideoDevice.get() : nullptr;
-
- if (mStopped || (!audioDevice && !videoDevice))
- {
+ MOZ_ASSERT(aTrackID == kAudioTrack || aTrackID == kVideoTrack,
+ "Unknown track id");
+
+ DeviceState& state = GetDeviceStateFor(aTrackID);
+ if (mStopped || state.mStopped) {
LOG(("gUM track %d applyConstraints, but we don't have type %s",
aTrackID, aTrackID == kAudioTrack ? "audio" : "video"));
p->Resolve(false);
return p.forget();
}
MediaTrackConstraints c(aConstraintsPassedIn); // use a modifiable copy
MediaConstraintsHelper::ConvertOldWithWarning(c.mMozAutoGainControl,
@@ -4162,40 +4241,29 @@ SourceListener::ApplyConstraintsToTrack(
if (!mgr) {
return p.forget();
}
uint32_t id = mgr->mOutstandingVoidPledges.Append(*p);
uint64_t windowId = aWindow->WindowID();
bool isChrome = (aCallerType == dom::CallerType::System);
MediaManager::PostTask(NewTaskFrom([id, windowId,
- audioDevice, videoDevice,
+ device = state.mDevice,
c, isChrome]() mutable {
MOZ_ASSERT(MediaManager::IsInMediaThread());
MediaManager* mgr = MediaManager::GetIfExists();
MOZ_RELEASE_ASSERT(mgr); // Must exist while media thread is alive
const char* badConstraint = nullptr;
- nsresult rv = NS_OK;
-
- if (audioDevice) {
- rv = audioDevice->Reconfigure(c, mgr->mPrefs, &badConstraint);
- if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
- nsTArray<RefPtr<MediaDevice>> audios;
- audios.AppendElement(audioDevice);
- badConstraint = MediaConstraintsHelper::SelectSettings(
- NormalizedConstraints(c), audios, isChrome);
- }
- } else {
- rv = videoDevice->Reconfigure(c, mgr->mPrefs, &badConstraint);
- if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
- nsTArray<RefPtr<MediaDevice>> videos;
- videos.AppendElement(videoDevice);
- badConstraint = MediaConstraintsHelper::SelectSettings(
- NormalizedConstraints(c), videos, isChrome);
- }
+
+ nsresult rv = device->Reconfigure(c, mgr->mPrefs, &badConstraint);
+ if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
+ nsTArray<RefPtr<MediaDevice>> devices;
+ devices.AppendElement(device);
+ badConstraint = MediaConstraintsHelper::SelectSettings(
+ NormalizedConstraints(c), devices, isChrome);
}
NS_DispatchToMainThread(NewRunnableFrom([id, windowId, rv,
badConstraint]() mutable {
MOZ_ASSERT(NS_IsMainThread());
MediaManager* mgr = MediaManager::GetIfExists();
if (!mgr) {
return NS_OK;
}
@@ -4231,16 +4299,33 @@ SourceListener::ApplyConstraintsToTrack(
}
PrincipalHandle
SourceListener::GetPrincipalHandle() const
{
return mPrincipalHandle;
}
+DeviceState&
+SourceListener::GetDeviceStateFor(TrackID aTrackID) const
+{
+ // XXX to support multiple tracks of a type in a stream, this should key off
+ // the TrackID and not just the type
+ switch (aTrackID) {
+ case kAudioTrack:
+ MOZ_ASSERT(mAudioDeviceState, "No audio device");
+ return *mAudioDeviceState;
+ case kVideoTrack:
+ MOZ_ASSERT(mVideoDeviceState, "No video device");
+ return *mVideoDeviceState;
+ default:
+ MOZ_CRASH("Unknown track id");
+ }
+}
+
// Doesn't kill audio
void
GetUserMediaWindowListener::StopSharing()
{
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
for (auto& source : mActiveListeners) {
source->StopSharing();
@@ -4271,17 +4356,17 @@ GetUserMediaWindowListener::StopRawID(co
}
void
GetUserMediaWindowListener::ChromeAffectingStateChanged()
{
MOZ_ASSERT(NS_IsMainThread());
// We wait until stable state before notifying chrome so chrome only does one
- // update if more tracks are stopped in this event loop.
+ // update if more updates happen in this event loop.
if (mChromeNotificationTaskPosted) {
return;
}
nsCOMPtr<nsIRunnable> runnable =
NewRunnableMethod("GetUserMediaWindowListener::NotifyChrome",
this,
@@ -4300,14 +4385,17 @@ GetUserMediaWindowListener::NotifyChrome
[windowID = mWindowID]() {
nsGlobalWindowInner* window =
nsGlobalWindowInner::GetInnerWindowWithId(windowID);
if (!window) {
MOZ_ASSERT_UNREACHABLE("Should have window");
return;
}
- DebugOnly<nsresult> rv = MediaManager::NotifyRecordingStatusChange(window->AsInner());
- MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to notify chrome");
+ nsresult rv = MediaManager::NotifyRecordingStatusChange(window->AsInner());
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT_UNREACHABLE("Should be able to notify chrome");
+ return;
+ }
}));
}
} // namespace mozilla