Fixing camera led always on
This commit fixes a race condition in the "getUserMedia" call that could lead to the webcam being in an always on state. If we open and close the webcam really quickly, the camera close was sometimes passing BEFORE the open was fully resolved (because getUserMedia can take a few 100ms to answer properly). As a result, the webcam would stay open. To solve this, we are putting all calls to getUserMedia in a resolve chain (`currentGetUserMediaPromise`) Closes #2149
This commit is contained in:
parent
06d3332499
commit
ae9170ba85
@ -414,6 +414,13 @@ async function toggleConstraints(track: MediaStreamTrack, constraints: MediaTrac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This promise is important to queue the calls to "getUserMedia"
|
||||||
|
// Otherwise, this can happen:
|
||||||
|
// User requests a start then a stop of the camera quickly
|
||||||
|
// The promise to start the cam starts. Before the promise is fulfilled, the camera is stopped.
|
||||||
|
// Then, the MediaStream of the camera start resolves (resulting in the LED being turned on instead of off)
|
||||||
|
let currentGetUserMediaPromise: Promise<MediaStream | undefined> = Promise.resolve(undefined);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A store containing the MediaStream object (or null if nothing requested, or Error if an error occurred)
|
* A store containing the MediaStream object (or null if nothing requested, or Error if an error occurred)
|
||||||
*/
|
*/
|
||||||
@ -422,19 +429,25 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
|
|||||||
($mediaStreamConstraintsStore, set) => {
|
($mediaStreamConstraintsStore, set) => {
|
||||||
const constraints = { ...$mediaStreamConstraintsStore };
|
const constraints = { ...$mediaStreamConstraintsStore };
|
||||||
|
|
||||||
async function initStream(constraints: MediaStreamConstraints) {
|
function initStream(constraints: MediaStreamConstraints): Promise<MediaStream | undefined> {
|
||||||
try {
|
currentGetUserMediaPromise = currentGetUserMediaPromise.then(() => {
|
||||||
|
return navigator.mediaDevices
|
||||||
|
.getUserMedia(constraints)
|
||||||
|
.then((stream) => {
|
||||||
|
// Close old stream
|
||||||
if (currentStream) {
|
if (currentStream) {
|
||||||
//we need stop all tracks to make sure the old stream will be garbage collected
|
//we need stop all tracks to make sure the old stream will be garbage collected
|
||||||
//currentStream.getTracks().forEach((t) => t.stop());
|
currentStream.getTracks().forEach((t) => t.stop());
|
||||||
}
|
}
|
||||||
currentStream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
||||||
|
currentStream = stream;
|
||||||
set({
|
set({
|
||||||
type: "success",
|
type: "success",
|
||||||
stream: currentStream,
|
stream: currentStream,
|
||||||
});
|
});
|
||||||
return;
|
return stream;
|
||||||
} catch (e) {
|
})
|
||||||
|
.catch((e) => {
|
||||||
if (constraints.video !== false || constraints.audio !== false) {
|
if (constraints.video !== false || constraints.audio !== false) {
|
||||||
console.info(
|
console.info(
|
||||||
"Error. Unable to get microphone and/or camera access. Trying audio only.",
|
"Error. Unable to get microphone and/or camera access. Trying audio only.",
|
||||||
@ -465,7 +478,10 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
|
|||||||
error: e instanceof Error ? e : new Error("An unknown error happened"),
|
error: e instanceof Error ? e : new Error("An unknown error happened"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
return undefined;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return currentGetUserMediaPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navigator.mediaDevices === undefined) {
|
if (navigator.mediaDevices === undefined) {
|
||||||
@ -491,17 +507,21 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyMicrophoneConstraints(currentStream, constraints.audio || false).catch((e) => console.error(e));
|
|
||||||
applyCameraConstraints(currentStream, constraints.video || false).catch((e) => console.error(e));
|
|
||||||
|
|
||||||
if (implementCorrectTrackBehavior) {
|
|
||||||
//on good navigators like firefox, we can instantiate the stream once and simply disable or enable the tracks as needed
|
|
||||||
if (currentStream === null) {
|
if (currentStream === null) {
|
||||||
// we need to assign a first value to the stream because getUserMedia is async
|
// we need to assign a first value to the stream because getUserMedia is async
|
||||||
set({
|
set({
|
||||||
type: "success",
|
type: "success",
|
||||||
stream: null,
|
stream: null,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await applyMicrophoneConstraints(currentStream, constraints.audio || false).catch((e) => console.error(e));
|
||||||
|
await applyCameraConstraints(currentStream, constraints.video || false).catch((e) => console.error(e));
|
||||||
|
|
||||||
|
if (implementCorrectTrackBehavior) {
|
||||||
|
//on good navigators like firefox, we can instantiate the stream once and simply disable or enable the tracks as needed
|
||||||
|
if (currentStream === null) {
|
||||||
initStream(constraints).catch((e) => {
|
initStream(constraints).catch((e) => {
|
||||||
set({
|
set({
|
||||||
type: "error",
|
type: "error",
|
||||||
@ -512,13 +532,24 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
|
|||||||
} else {
|
} else {
|
||||||
//on bad navigators like chrome, we have to stop the tracks when we mute and reinstantiate the stream when we need to unmute
|
//on bad navigators like chrome, we have to stop the tracks when we mute and reinstantiate the stream when we need to unmute
|
||||||
if (constraints.audio === false && constraints.video === false) {
|
if (constraints.audio === false && constraints.video === false) {
|
||||||
|
currentGetUserMediaPromise = currentGetUserMediaPromise.then(() => {
|
||||||
|
if (currentStream) {
|
||||||
|
//we need stop all tracks to make sure the old stream will be garbage collected
|
||||||
|
currentStream.getTracks().forEach((t) => t.stop());
|
||||||
|
}
|
||||||
|
|
||||||
currentStream = null;
|
currentStream = null;
|
||||||
set({
|
set({
|
||||||
type: "success",
|
type: "success",
|
||||||
stream: null,
|
stream: null,
|
||||||
});
|
});
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
} //we reemit the stream if it was muted just to be sure
|
} //we reemit the stream if it was muted just to be sure
|
||||||
else if (constraints.audio /* && !oldConstraints.audio*/ || (!oldConstraints.video && constraints.video)) {
|
else if (
|
||||||
|
constraints.audio /* && !oldConstraints.audio*/ ||
|
||||||
|
(!oldConstraints.video && constraints.video)
|
||||||
|
) {
|
||||||
initStream(constraints).catch((e) => {
|
initStream(constraints).catch((e) => {
|
||||||
set({
|
set({
|
||||||
type: "error",
|
type: "error",
|
||||||
@ -531,6 +562,7 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
|
|||||||
audio: !!constraints.audio,
|
audio: !!constraints.audio,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
})().catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user