зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1652884 - Add observer messages to mute/unmute all camera tracks. r=pehrsons
Add UA (user agent) muting, a spec-supported feature that somewhat mirrors track enabling/disabling, except only the browser controls it. The effect on track sinks is additive: must be unmuted and enabled for there to be output. Fire mute/unmute events on JS, and observably set track.muted independent of track.enabled (reusing existing infrastructure already in use by RTCPeerConnection tracks). Low-level: add mDeviceMuted and SetMutedFor() modeled after mDeviceEnabled and SetEnabledFor() as parallel device state for both camera and microphone for symmetry and maintenance. High-level: Only expose messages to mute/unmute camera at the moment, since that is what is immediately required for Android in bug 1564451. Differential Revision: https://phabricator.services.mozilla.com/D84222
This commit is contained in:
Родитель
9014bab5e1
Коммит
27cba52636
|
@ -122,6 +122,20 @@ class WebRTCChild extends JSWindowActorChild {
|
||||||
aMessage.data
|
aMessage.data
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "webrtc:MuteCamera":
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"getUserMedia:muteVideo",
|
||||||
|
aMessage.data
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "webrtc:UnmuteCamera":
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"getUserMedia:unmuteVideo",
|
||||||
|
aMessage.data
|
||||||
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,10 +210,16 @@ struct DeviceState {
|
||||||
// MainThread only.
|
// MainThread only.
|
||||||
bool mStopped = false;
|
bool mStopped = false;
|
||||||
|
|
||||||
// true if mDevice is currently enabled, i.e., turned on and capturing.
|
// true if mDevice is currently enabled.
|
||||||
|
// A device must be both enabled and unmuted to be turned on and capturing.
|
||||||
// MainThread only.
|
// MainThread only.
|
||||||
bool mDeviceEnabled = false;
|
bool mDeviceEnabled = false;
|
||||||
|
|
||||||
|
// true if mDevice is currently muted.
|
||||||
|
// A device that is either muted or disabled is turned off and not capturing.
|
||||||
|
// MainThread only.
|
||||||
|
bool mDeviceMuted = false;
|
||||||
|
|
||||||
// true if the application has currently enabled mDevice.
|
// true if the application has currently enabled mDevice.
|
||||||
// MainThread only.
|
// MainThread only.
|
||||||
bool mTrackEnabled = false;
|
bool mTrackEnabled = false;
|
||||||
|
@ -228,7 +234,7 @@ struct DeviceState {
|
||||||
bool mOperationInProgress = false;
|
bool mOperationInProgress = false;
|
||||||
|
|
||||||
// true if we are allowed to turn off the underlying source while all tracks
|
// true if we are allowed to turn off the underlying source while all tracks
|
||||||
// are disabled.
|
// are disabled. Only affects disabling; always turns off on user-agent mute.
|
||||||
// MainThread only.
|
// MainThread only.
|
||||||
bool mOffWhileDisabled = false;
|
bool mOffWhileDisabled = false;
|
||||||
|
|
||||||
|
@ -378,12 +384,31 @@ class SourceListener : public SupportsWeakPtr {
|
||||||
*/
|
*/
|
||||||
void SetEnabledFor(MediaTrack* aTrack, bool aEnabled);
|
void SetEnabledFor(MediaTrack* aTrack, bool aEnabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a task to set the muted state of the device associated with
|
||||||
|
* aTrackSource to aMuted and notifies the associated window listener that a
|
||||||
|
* track's state has changed.
|
||||||
|
*
|
||||||
|
* Turning the hardware off while the device is muted 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.
|
||||||
|
*/
|
||||||
|
void SetMutedFor(LocalTrackSource* aTrackSource, bool aMuted);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops all screen/app/window/audioCapture sharing, but not camera or
|
* Stops all screen/app/window/audioCapture sharing, but not camera or
|
||||||
* microphone.
|
* microphone.
|
||||||
*/
|
*/
|
||||||
void StopSharing();
|
void StopSharing();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutes or unmutes the associated video device if it is a camera.
|
||||||
|
*/
|
||||||
|
void MuteOrUnmuteCamera(bool aMute);
|
||||||
|
|
||||||
MediaDevice* GetAudioDevice() const {
|
MediaDevice* GetAudioDevice() const {
|
||||||
return mAudioDeviceState ? mAudioDeviceState->mDevice.get() : nullptr;
|
return mAudioDeviceState ? mAudioDeviceState->mDevice.get() : nullptr;
|
||||||
}
|
}
|
||||||
|
@ -411,6 +436,15 @@ class SourceListener : public SupportsWeakPtr {
|
||||||
private:
|
private:
|
||||||
virtual ~SourceListener() = default;
|
virtual ~SourceListener() = default;
|
||||||
|
|
||||||
|
using DeviceOperationPromise =
|
||||||
|
MozPromise<nsresult, bool, /* IsExclusive = */ true>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a task to start or stop the device associated with aTrack, based on
|
||||||
|
* a passed-in boolean. Private method used by SetEnabledFor and SetMutedFor.
|
||||||
|
*/
|
||||||
|
RefPtr<DeviceOperationPromise> UpdateDevice(MediaTrack* aTrack, bool aOn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a pointer to the device state for aTrack.
|
* Returns a pointer to the device state for aTrack.
|
||||||
*
|
*
|
||||||
|
@ -638,6 +672,8 @@ class GetUserMediaWindowListener {
|
||||||
|
|
||||||
void StopRawID(const nsString& removedDeviceID);
|
void StopRawID(const nsString& removedDeviceID);
|
||||||
|
|
||||||
|
void MuteOrUnmuteCameras(bool aMute);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by one of our SourceListeners when one of its tracks has changed so
|
* Called by one of our SourceListeners when one of its tracks has changed so
|
||||||
* that chrome state is affected.
|
* that chrome state is affected.
|
||||||
|
@ -771,6 +807,10 @@ class LocalTrackSource : public MediaStreamTrackSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Mute() { MutedChanged(true); }
|
||||||
|
|
||||||
|
void Unmute() { MutedChanged(false); }
|
||||||
|
|
||||||
const MediaSourceEnum mSource;
|
const MediaSourceEnum mSource;
|
||||||
const RefPtr<MediaTrack> mTrack;
|
const RefPtr<MediaTrack> mTrack;
|
||||||
const RefPtr<const PeerIdentity> mPeerIdentity;
|
const RefPtr<const PeerIdentity> mPeerIdentity;
|
||||||
|
@ -1995,6 +2035,8 @@ MediaManager* MediaManager::Get() {
|
||||||
obs->AddObserver(sSingleton, "getUserMedia:response:noOSPermission",
|
obs->AddObserver(sSingleton, "getUserMedia:response:noOSPermission",
|
||||||
false);
|
false);
|
||||||
obs->AddObserver(sSingleton, "getUserMedia:revoke", false);
|
obs->AddObserver(sSingleton, "getUserMedia:revoke", false);
|
||||||
|
obs->AddObserver(sSingleton, "getUserMedia:muteVideo", false);
|
||||||
|
obs->AddObserver(sSingleton, "getUserMedia:unmuteVideo", false);
|
||||||
}
|
}
|
||||||
// else MediaManager won't work properly and will leak (see bug 837874)
|
// else MediaManager won't work properly and will leak (see bug 837874)
|
||||||
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
||||||
|
@ -3392,6 +3434,16 @@ void MediaManager::OnNavigation(uint64_t aWindowID) {
|
||||||
MOZ_ASSERT(!GetWindowListener(aWindowID));
|
MOZ_ASSERT(!GetWindowListener(aWindowID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MediaManager::OnCameraMute(bool aMute) {
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
LOG("OnCameraMute for all windows");
|
||||||
|
// This is safe since we're on main-thread, and the windowlist can only
|
||||||
|
// be added to from the main-thread
|
||||||
|
for (auto iter = mActiveWindows.Iter(); !iter.Done(); iter.Next()) {
|
||||||
|
iter.UserData()->MuteOrUnmuteCameras(aMute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MediaManager::AddWindowID(uint64_t aWindowId,
|
void MediaManager::AddWindowID(uint64_t aWindowId,
|
||||||
RefPtr<GetUserMediaWindowListener> aListener) {
|
RefPtr<GetUserMediaWindowListener> aListener) {
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
@ -3519,6 +3571,8 @@ void MediaManager::Shutdown() {
|
||||||
obs->RemoveObserver(this, "getUserMedia:response:deny");
|
obs->RemoveObserver(this, "getUserMedia:response:deny");
|
||||||
obs->RemoveObserver(this, "getUserMedia:response:noOSPermission");
|
obs->RemoveObserver(this, "getUserMedia:response:noOSPermission");
|
||||||
obs->RemoveObserver(this, "getUserMedia:revoke");
|
obs->RemoveObserver(this, "getUserMedia:revoke");
|
||||||
|
obs->RemoveObserver(this, "getUserMedia:muteVideo");
|
||||||
|
obs->RemoveObserver(this, "getUserMedia:unmuteVideo");
|
||||||
|
|
||||||
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
|
||||||
if (prefs) {
|
if (prefs) {
|
||||||
|
@ -3670,6 +3724,23 @@ bool IsGUMResponseNoAccess(const char* aTopic,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static MediaSourceEnum ParseScreenColonWindowID(const char16_t* aData,
|
||||||
|
uint64_t* aWindowIDOut) {
|
||||||
|
MOZ_ASSERT(aWindowIDOut);
|
||||||
|
// may be windowid or screen:windowid
|
||||||
|
const nsDependentString data(aData);
|
||||||
|
if (Substring(data, 0, strlen("screen:")).EqualsLiteral("screen:")) {
|
||||||
|
nsresult rv;
|
||||||
|
*aWindowIDOut = Substring(data, strlen("screen:")).ToInteger64(&rv);
|
||||||
|
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||||
|
return MediaSourceEnum::Screen;
|
||||||
|
}
|
||||||
|
nsresult rv;
|
||||||
|
*aWindowIDOut = data.ToInteger64(&rv);
|
||||||
|
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||||
|
return MediaSourceEnum::Camera;
|
||||||
|
}
|
||||||
|
|
||||||
nsresult MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
|
nsresult MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
|
||||||
const char16_t* aData) {
|
const char16_t* aData) {
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
@ -3774,28 +3845,20 @@ nsresult MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
|
|
||||||
} else if (!strcmp(aTopic, "getUserMedia:revoke")) {
|
} else if (!strcmp(aTopic, "getUserMedia:revoke")) {
|
||||||
nsresult rv;
|
uint64_t windowID;
|
||||||
// may be windowid or screen:windowid
|
if (ParseScreenColonWindowID(aData, &windowID) == MediaSourceEnum::Screen) {
|
||||||
const nsDependentString data(aData);
|
LOG("Revoking ScreenCapture access for window %" PRIu64, windowID);
|
||||||
if (Substring(data, 0, strlen("screen:")).EqualsLiteral("screen:")) {
|
StopScreensharing(windowID);
|
||||||
uint64_t windowID = Substring(data, strlen("screen:")).ToInteger64(&rv);
|
|
||||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
||||||
if (NS_SUCCEEDED(rv)) {
|
|
||||||
LOG("Revoking Screen/windowCapture access for window %" PRIu64,
|
|
||||||
windowID);
|
|
||||||
StopScreensharing(windowID);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
uint64_t windowID = data.ToInteger64(&rv);
|
LOG("Revoking MediaCapture access for window %" PRIu64, windowID);
|
||||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
OnNavigation(windowID);
|
||||||
if (NS_SUCCEEDED(rv)) {
|
|
||||||
LOG("Revoking MediaCapture access for window %" PRIu64, windowID);
|
|
||||||
OnNavigation(windowID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
|
} else if (!strcmp(aTopic, "getUserMedia:muteVideo") ||
|
||||||
|
!strcmp(aTopic, "getUserMedia:unmuteVideo")) {
|
||||||
|
OnCameraMute(!strcmp(aTopic, "getUserMedia:muteVideo"));
|
||||||
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4275,6 +4338,80 @@ void SourceListener::GetSettingsFor(MediaTrack* aTrack,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool SameGroupAsCurrentAudioOutput(const nsString& aGroupId) {
|
||||||
|
CubebDeviceEnumerator* enumerator = CubebDeviceEnumerator::GetInstance();
|
||||||
|
// Get the current graph's device info. This is always the
|
||||||
|
// default audio output device for now.
|
||||||
|
RefPtr<AudioDeviceInfo> outputDevice =
|
||||||
|
enumerator->DefaultDevice(CubebDeviceEnumerator::Side::OUTPUT);
|
||||||
|
return outputDevice && outputDevice->GroupID().Equals(aGroupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SourceListener::UpdateDevice(MediaTrack* aTrack, bool aOn)
|
||||||
|
-> RefPtr<DeviceOperationPromise> {
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
RefPtr<SourceListener> self = this;
|
||||||
|
DeviceState& state = GetDeviceStateFor(aTrack);
|
||||||
|
nsString groupId;
|
||||||
|
state.mDevice->GetRawGroupId(groupId);
|
||||||
|
|
||||||
|
return MediaManager::Dispatch<DeviceOperationPromise>(
|
||||||
|
__func__,
|
||||||
|
[self, device = state.mDevice, aOn,
|
||||||
|
groupId](MozPromiseHolder<DeviceOperationPromise>& h) {
|
||||||
|
if (device->mKind == dom::MediaDeviceKind::Audioinput && !aOn &&
|
||||||
|
SameGroupAsCurrentAudioOutput(groupId)) {
|
||||||
|
// Don't turn off the microphone of a device that is on the
|
||||||
|
// same physical device as the output.
|
||||||
|
//
|
||||||
|
// Also don't take this branch when turning on, in case the
|
||||||
|
// default audio output device has changed. The AudioInput
|
||||||
|
// source start/stop are idempotent, so this works.
|
||||||
|
LOG("Not turning device off, as it matches audio output (%s)",
|
||||||
|
NS_ConvertUTF16toUTF8(groupId).get());
|
||||||
|
h.Resolve(NS_OK, __func__);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG("Turning %s device (%s)", aOn ? "on" : "off",
|
||||||
|
NS_ConvertUTF16toUTF8(groupId).get());
|
||||||
|
h.Resolve(aOn ? device->Start() : device->Stop(), __func__);
|
||||||
|
})
|
||||||
|
->Then(
|
||||||
|
GetMainThreadSerialEventTarget(), __func__,
|
||||||
|
[self, this, &state, track = RefPtr<MediaTrack>(aTrack),
|
||||||
|
aOn](nsresult aResult) {
|
||||||
|
if (state.mStopped) {
|
||||||
|
// Device was stopped on main thread during the operation. Done.
|
||||||
|
return DeviceOperationPromise::CreateAndResolve(aResult,
|
||||||
|
__func__);
|
||||||
|
}
|
||||||
|
LOG("SourceListener %p turning %s %s input device for track %p %s",
|
||||||
|
this, aOn ? "on" : "off",
|
||||||
|
&state == mAudioDeviceState.get() ? "audio" : "video",
|
||||||
|
track.get(), 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.
|
||||||
|
if (aOn) {
|
||||||
|
// Starting the device failed. Stopping the track here will make
|
||||||
|
// the MediaStreamTrack end after a pass through the
|
||||||
|
// MediaTrackGraph.
|
||||||
|
StopTrack(track);
|
||||||
|
} else {
|
||||||
|
// Stopping the device failed. This is odd, but not fatal.
|
||||||
|
MOZ_ASSERT_UNREACHABLE("The device should be stoppable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DeviceOperationPromise::CreateAndResolve(aResult, __func__);
|
||||||
|
},
|
||||||
|
[]() {
|
||||||
|
MOZ_ASSERT_UNREACHABLE("Unexpected and unhandled reject");
|
||||||
|
return DeviceOperationPromise::CreateAndReject(false, __func__);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void SourceListener::SetEnabledFor(MediaTrack* aTrack, bool aEnable) {
|
void SourceListener::SetEnabledFor(MediaTrack* aTrack, bool aEnable) {
|
||||||
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
|
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
|
||||||
MOZ_ASSERT(Activated(), "No device to set enabled state for");
|
MOZ_ASSERT(Activated(), "No device to set enabled state for");
|
||||||
|
@ -4327,8 +4464,6 @@ void SourceListener::SetEnabledFor(MediaTrack* aTrack, bool aEnable) {
|
||||||
timerPromise = state.mDisableTimer->WaitFor(delay, __func__);
|
timerPromise = state.mDisableTimer->WaitFor(delay, __func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef MozPromise<nsresult, bool, /* IsExclusive = */ true>
|
|
||||||
DeviceOperationPromise;
|
|
||||||
RefPtr<SourceListener> self = this;
|
RefPtr<SourceListener> self = this;
|
||||||
timerPromise
|
timerPromise
|
||||||
->Then(
|
->Then(
|
||||||
|
@ -4357,53 +4492,14 @@ void SourceListener::SetEnabledFor(MediaTrack* aTrack, bool aEnable) {
|
||||||
if (mWindowListener) {
|
if (mWindowListener) {
|
||||||
mWindowListener->ChromeAffectingStateChanged();
|
mWindowListener->ChromeAffectingStateChanged();
|
||||||
}
|
}
|
||||||
if (!state.mOffWhileDisabled) {
|
if (!state.mOffWhileDisabled || state.mDeviceMuted) {
|
||||||
// If the feature to turn a device off while disabled is itself
|
// If the feature to turn a device off while disabled is itself
|
||||||
// disabled we shortcut the device operation and tell the
|
// disabled, or the device is currently user agent muted, then
|
||||||
|
// we shortcut the device operation and tell the
|
||||||
// ux-updating code that everything went fine.
|
// ux-updating code that everything went fine.
|
||||||
return DeviceOperationPromise::CreateAndResolve(NS_OK, __func__);
|
return DeviceOperationPromise::CreateAndResolve(NS_OK, __func__);
|
||||||
}
|
}
|
||||||
|
return UpdateDevice(track, aEnable);
|
||||||
nsString inputDeviceGroupId;
|
|
||||||
state.mDevice->GetRawGroupId(inputDeviceGroupId);
|
|
||||||
|
|
||||||
return MediaManager::Dispatch<DeviceOperationPromise>(
|
|
||||||
__func__,
|
|
||||||
[self, device = state.mDevice, aEnable, inputDeviceGroupId](
|
|
||||||
MozPromiseHolder<DeviceOperationPromise>& h) {
|
|
||||||
// Only take this branch when muting, to avoid muting, in case
|
|
||||||
// the default audio output device has changed and we need to
|
|
||||||
// really call `Start` on the source. The AudioInput source
|
|
||||||
// start/stop are idempotent, so this works.
|
|
||||||
if (device->mKind == dom::MediaDeviceKind::Audioinput &&
|
|
||||||
!aEnable) {
|
|
||||||
// Don't turn off the microphone of a device that is on the
|
|
||||||
// same physical device as the output.
|
|
||||||
CubebDeviceEnumerator* enumerator =
|
|
||||||
CubebDeviceEnumerator::GetInstance();
|
|
||||||
// Get the current graph's device info. This is always the
|
|
||||||
// default audio output device for now.
|
|
||||||
RefPtr<AudioDeviceInfo> outputDevice =
|
|
||||||
enumerator->DefaultDevice(
|
|
||||||
CubebDeviceEnumerator::Side::OUTPUT);
|
|
||||||
if (outputDevice &&
|
|
||||||
outputDevice->GroupID().Equals(inputDeviceGroupId)) {
|
|
||||||
LOG("Device group id match when %s, "
|
|
||||||
"not turning the input device off (%s)",
|
|
||||||
aEnable ? "unmuting" : "muting",
|
|
||||||
NS_ConvertUTF16toUTF8(outputDevice->GroupID()).get());
|
|
||||||
h.Resolve(NS_OK, __func__);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG("Device group id don't match when %s, "
|
|
||||||
"not turning the audio input device off (%s)",
|
|
||||||
aEnable ? "unmuting" : "muting",
|
|
||||||
NS_ConvertUTF16toUTF8(inputDeviceGroupId).get());
|
|
||||||
h.Resolve(aEnable ? device->Start() : device->Stop(),
|
|
||||||
__func__);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[]() {
|
[]() {
|
||||||
// Timer was canceled by us. We signal this with NS_ERROR_ABORT.
|
// Timer was canceled by us. We signal this with NS_ERROR_ABORT.
|
||||||
|
@ -4425,28 +4521,10 @@ void SourceListener::SetEnabledFor(MediaTrack* aTrack, bool aEnable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG("SourceListener %p %s %s track for track %p %s", this,
|
if (NS_FAILED(aResult) && aResult != NS_ERROR_ABORT && !aEnable) {
|
||||||
aEnable ? "enabling" : "disabling",
|
// To keep our internal state sane in this case, we disallow
|
||||||
&state == mAudioDeviceState.get() ? "audio" : "video",
|
// future stops due to disable.
|
||||||
track.get(), NS_SUCCEEDED(aResult) ? "succeeded" : "failed");
|
state.mOffWhileDisabled = false;
|
||||||
|
|
||||||
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.
|
|
||||||
if (aEnable) {
|
|
||||||
// Starting the device failed. Stopping the track here will make
|
|
||||||
// the MediaStreamTrack end after a pass through the
|
|
||||||
// MediaTrackGraph.
|
|
||||||
StopTrack(track);
|
|
||||||
} 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4456,22 +4534,58 @@ void SourceListener::SetEnabledFor(MediaTrack* aTrack, bool aEnable) {
|
||||||
// update the device state if the track state changed in the
|
// update the device state if the track state changed in the
|
||||||
// meantime.
|
// meantime.
|
||||||
|
|
||||||
if (state.mTrackEnabled == state.mDeviceEnabled) {
|
if (state.mTrackEnabled != state.mDeviceEnabled) {
|
||||||
// Intended state is same as device's current state.
|
// Track state changed during this operation. We'll start over.
|
||||||
// Nothing more to do.
|
SetEnabledFor(track, state.mTrackEnabled);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track state changed during this operation. We'll start over.
|
|
||||||
if (state.mTrackEnabled) {
|
|
||||||
SetEnabledFor(track, true);
|
|
||||||
} else {
|
|
||||||
SetEnabledFor(track, false);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]() { MOZ_ASSERT_UNREACHABLE("Unexpected and unhandled reject"); });
|
[]() { MOZ_ASSERT_UNREACHABLE("Unexpected and unhandled reject"); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SourceListener::SetMutedFor(LocalTrackSource* aTrackSource, bool aMute) {
|
||||||
|
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
|
||||||
|
MOZ_ASSERT(Activated(), "No device to set muted state for");
|
||||||
|
|
||||||
|
MediaTrack* track = aTrackSource->mTrack;
|
||||||
|
DeviceState& state = GetDeviceStateFor(track);
|
||||||
|
|
||||||
|
LOG("SourceListener %p %s %s track for track %p", this,
|
||||||
|
aMute ? "muting" : "unmuting",
|
||||||
|
&state == mAudioDeviceState.get() ? "audio" : "video", track);
|
||||||
|
|
||||||
|
if (state.mStopped) {
|
||||||
|
// Device terminally stopped. Updating device state is pointless.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.mDeviceMuted == aMute) {
|
||||||
|
// Device is already in the desired state.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG("SourceListener %p %s %s track for track %p - starting device operation",
|
||||||
|
this, aMute ? "muting" : "unmuting",
|
||||||
|
&state == mAudioDeviceState.get() ? "audio" : "video", track);
|
||||||
|
|
||||||
|
state.mDeviceMuted = aMute;
|
||||||
|
|
||||||
|
if (mWindowListener) {
|
||||||
|
mWindowListener->ChromeAffectingStateChanged();
|
||||||
|
}
|
||||||
|
// Update trackSource to fire mute/unmute events on all its tracks
|
||||||
|
if (aMute) {
|
||||||
|
aTrackSource->Mute();
|
||||||
|
} else {
|
||||||
|
aTrackSource->Unmute();
|
||||||
|
}
|
||||||
|
if (state.mOffWhileDisabled && !state.mDeviceEnabled &&
|
||||||
|
state.mDevice->mKind == dom::MediaDeviceKind::Videoinput) {
|
||||||
|
// Camera is already off. TODO: Revisit once we support UA-muting mics.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UpdateDevice(track, !aMute);
|
||||||
|
}
|
||||||
|
|
||||||
void SourceListener::StopSharing() {
|
void SourceListener::StopSharing() {
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
|
@ -4499,6 +4613,22 @@ void SourceListener::StopSharing() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SourceListener::MuteOrUnmuteCamera(bool aMute) {
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
|
if (mStopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MOZ_RELEASE_ASSERT(mWindowListener);
|
||||||
|
LOG("SourceListener %p MuteOrUnmuteCamera", this);
|
||||||
|
|
||||||
|
if (mVideoDeviceState && (mVideoDeviceState->mDevice->GetMediaSource() ==
|
||||||
|
MediaSourceEnum::Camera)) {
|
||||||
|
SetMutedFor(mVideoDeviceState->mTrackSource, aMute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool SourceListener::CapturingVideo() const {
|
bool SourceListener::CapturingVideo() const {
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
return Activated() && mVideoDeviceState && !mVideoDeviceState->mStopped &&
|
return Activated() && mVideoDeviceState && !mVideoDeviceState->mStopped &&
|
||||||
|
@ -4540,9 +4670,9 @@ CaptureState SourceListener::CapturingSource(MediaSourceEnum aSource) const {
|
||||||
return CaptureState::Off;
|
return CaptureState::Off;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source is a match and is active
|
// Source is a match and is active and unmuted
|
||||||
|
|
||||||
if (state.mDeviceEnabled) {
|
if (state.mDeviceEnabled && !state.mDeviceMuted) {
|
||||||
return CaptureState::Enabled;
|
return CaptureState::Enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4648,6 +4778,16 @@ void GetUserMediaWindowListener::StopRawID(const nsString& removedDeviceID) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GetUserMediaWindowListener::MuteOrUnmuteCameras(bool aMute) {
|
||||||
|
MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
|
||||||
|
|
||||||
|
for (auto& source : mActiveListeners) {
|
||||||
|
if (source->GetVideoDevice()) {
|
||||||
|
source->MuteOrUnmuteCamera(aMute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GetUserMediaWindowListener::ChromeAffectingStateChanged() {
|
void GetUserMediaWindowListener::ChromeAffectingStateChanged() {
|
||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
|
|
|
@ -254,6 +254,7 @@ class MediaManager final : public nsIMediaManagerService, public nsIObserver {
|
||||||
const nsString& aDeviceId);
|
const nsString& aDeviceId);
|
||||||
|
|
||||||
void OnNavigation(uint64_t aWindowID);
|
void OnNavigation(uint64_t aWindowID);
|
||||||
|
void OnCameraMute(bool aMute);
|
||||||
bool IsActivelyCapturingOrHasAPermission(uint64_t aWindowId);
|
bool IsActivelyCapturingOrHasAPermission(uint64_t aWindowId);
|
||||||
|
|
||||||
MediaEventSource<void>& DeviceListChangeEvent() {
|
MediaEventSource<void>& DeviceListChangeEvent() {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче