Web camera light stays on after stream is stopped - javascript

I am building a project that captures an image from the webcam in the browser. After the image is taken, I no longer need to use the camera, so I am trying to stop it with the following function:
function stopCamera(container) {
console.log("Stopping the camera.");
video = container.querySelector('.video-streamer');
console.log(video);
video.srcObject = null;
navigator.mediaDevices.getUserMedia({ video: true }).then(
function (stream) {
console.log(stream.getTracks().length);
stream.getTracks().forEach(function (track) {
console.log("Found a stream that needs to be stopped.")
track.stop();
});
console.log(stream.getTracks().length);
}).catch(
function (error) {
console.log('getUserMedia() error', error);
});
}
However, even after the function is called, the webcam access light stays on, and I see that the browser (both Firefox and Chrome) still show that the page is using the camera.
What is missing in the code above?

navigator.mediaDevices.getUserMedia returns a new stream (a clone), not the existing stream.
You have to stop all tracks from all stream clones returned from different calls to getUserMedia, before the light goes out.
In your case, that includes the tracks of the stream you're already playing. Use the following:
function stopCamera(container) {
const video = container.querySelector('.video-streamer');
for (const track of video.srcObject.getTracks()) {
track.stop();
}
video.srcObject = null;
}
Once all tracks are stopped, the light should go out instantly.
If you neglect to do this, the light should still go out 3-10 seconds after video.srcObject = null thanks to garbage collection (assuming it was the lone held reference to the stream).
If you've created any track clones, you need to stop them too.

Related

Webcam light doesnt turn off after setting the video constraint to false in webRtc

I am using WebRTC and socket.io for video calling.
The call is working fine but when I run the turn off camera function, the video stops but the light of my webcam remains on.
Here is the function for turning off the camera:
const mute= ()=>{
setMuteCamera(true)
navigator.mediaDevices
.getUserMedia({ video: false, audio: true })
.then((stream) => {
setStream(stream);
myVideo.current.srcObject = stream;
if (stream!= null) {
stream?.getTracks().forEach((track) => {
track.stop()
})
}
})
}
Every time you call navigator.mediaDevices.getUserMedia() a new MediaStream is created, with new MediaStreamTracks. Closing these new MediaStreamTracks won't close the previous ones.
So what you need is to keep track of your previous MediaStream, and close the VideoTrack from it.
Given your current code it seems that you do set it as myVideo.current.srcObject, if so, you can access it from there:
const muteVideoTrack = () => {
myVideo.current.srcObject.getVideoTracks().forEach((track) => track.stop());
};
Or you could also simply store this MediaStream in any variable accessible to your method.
This will let the MediaStream capture only the microphone. If you wanted to completely stop the whole MediaStream, then call getTracks() instead of getVideoTracks(), and optionally set the srcObject to null.

How to manipulate webcam video before streaming over WebRTC even when the tab is inactive?

I have been through many similar QnAs and articles. Overall it seems the only solution is to connect the userMedia stream to video then use a timer function to take snapshot from the video and put it on a canvas. Now manipulate the stuff on canvas and stream that.
The problem is the timer function. If I use setTimeout or requestAnimationFrame then the video stream becomes choppy or completely stops if the user moves away from the tab. I somehow worked around that using Web worker (using below code).
(function () {
let blob = new Blob([`
onmessage = function(oEvent) {
setTimeout(() => postMessage('tick'), oEvent[1]);
};
`],{type: 'text/javascript'});
window.myTimeout = function (cb, t) {
const worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(oEvent) {
cb();
worker.terminate();
};
worker.postMessage(['start', t]);
};
}());
However this works well on Chrome but on Safari (OSX) this fails completely. Safari seems to pause the JS engine completely. This issue does not happen if I stream userMedia directly.

How can I catch an issue of MediaRecorder.ondataavailble no longer being called?

I am capturing a user's audio and video with the navigator.mediaDevices.getUserMedia() and then using MediaRecorder and its ondataavailable to store that video and audio blob locally to upload later.
Now Im dealing with an issue where for some reason the ondataavailable stops being called midway through the recording. I'm not sure why and I get no alerts that anything went wrong. So first, does anyone know why this might happen and how to catch the errors?
Second, I have tried to reproduce. By doing something like this.
navigator.mediaDevices.getUserMedia({ audio: true, video: true })
.then(function(camera) {
local_media_stream = camera;
camera.getVideoTracks()[0].onended = function() { console.log("VIDEO ENDED") }
camera.getAudioTracks()[0].onended = function() { console.log("Audio ENDED") }
camera.onended = function() { console.log("--- ENDED") }
camera.onremovetrack = (event) => { console.log(`${event.track.kind} track removed`); };
}).catch(function(error) {
alert('Unable to capture your camera. Please check logs.' + error);
console.error(error);
});
And recording the stream with
recorder = new MediaRecorder(local_media_stream, {
mimeType: encoding_options,
audioBitsPerSecond: 128000,
videoBitsPerSecond: bits_per_second,
});
recorder.ondataavailable = function(e) {
save_blob(e.data, blob_index)
blob_index++;
}
recorder.onstop = function(e) {
console.log("recorder stopped");
console.log(e)
}
recorder.onerror = function(error) {
console.log("recorder error");
alert(error)
throw error;
}
recorder.onstart = function() {
console.log('started');
};
recorder.onpause = function() {
console.log('paused');
};
recorder.onresume = function() {
console.log('resumed');
};
recorder.start(15000)
Then I try to kill the stream manually to hopefully reproduce whatever issue is occurring by doing
local_media_stream.getVideoTracks()[0].stop()
Now ondataavailable is no longer called but none of the onended events were called. The recording is still going and the local_media_stream is still active.
If I kill the audio too
local_media_stream.getAudioTracks()[0].stop()
Now the local_media_stream is not active but still no event was called telling me the stream stopped and the recorder is still going but ondatavailable is never being called.
What can I do? I want to know that the local stream is being recorded successfully and if not be alerted so I can at least inform the user that the recording is no longer saving.
MediaRecorder has a recorder.stop() method. I don't see you calling it in your example code. Try calling it.
When you call track[n].stop() on the tracks of your media stream, you tell them to stop feeding data to MediaRecorder. So, unsurprisingly, MediaRecorder stops generating its coded output stream.
You also might, if you're running on Google Chrome, try a shorter timeslice than your recorder.start(15000). Or force the delivery of your dataavailable event by using recorder.requestData().
Edit When you call .requestData(), it invokes the ondataavailable event handler. (And, if you specified a timeslice in your .start() call the handler is called automatically.) Each call to that handler delivers the coded media data since the previous call. If you need the whole data stream you can accumulate it in your handler. But when you do that, of course, it needs to go into the browser's RAM heap, so you can't just keep accumulating it indefinitely.
Stopping both tracks should stop your recorder, it does for me in both FF and Chrome: https://jsfiddle.net/gpt51d6y/
But that's very improbable your users are calling stop() themselves.
Most probably the problem isn't in the MediaRecorder, but before that in the MediaStream's tracks.
A MediaRecorder for which all its tracks are muted won't emit new dataavailable event, because from its perspective, there is still something that might be going on, the track may unmute at any time.
Think for instance of a microphone with an hardware push-to-talk feature, the track from this microphone would get muted every time the user releases such button, but the MediaRecorder, even though it won't record anything during this time, still has to increment its internal timing, so that the "gaps" don't get "glued" in the final media. However, since it had nothing passed to its graph, it won't either emit new dataavailable events, it will simply adjust the timestamp in the next chunk that will get emitted.
For your case of trying to find where the problem comes from, you can try to listen for the MediaRecorder's error event, it may fire in some cases.
But you should also definitely add events to every tracks of its stream, and don't forget the mute event:
recorder.stream.getTracks().forEach( (track) => {
track.onmute = track.onended = console.warn;
};

JavaScript/ HTML video tag in Safari. Block now playing controls [duplicate]

Safari on iOS puts a scrubber on its lock screen for simple HTMLAudioElements. For example:
const a = new Audio();
a.src = 'https://example.com/audio.m4a'
a.play();
JSFiddle: https://jsfiddle.net/0seckLfd/
The lock screen will allow me to choose a position in the currently playing audio file.
How can I disable the ability for the user to scrub the file on the lock screen? The metadata showing is fine, and being able to pause/play is also acceptable, but I'm also fine with disabling it all if I need to.
DISABLE Player on lock screen completely
if you want to completely remove the lock screen player you could do something like
const a = new Audio();
document.querySelector('button').addEventListener('click', (e) => {
a.src = 'http://sprott.physics.wisc.edu/wop/sounds/Bicycle%20Race-Full.m4a'
a.play();
});
document.addEventListener('visibilitychange', () => {
if (document.hidden) a.src = undefined
})
https://jsfiddle.net/5s8c9eL0/3/
that is stoping the player when changing tab or locking screen
(code to be cleaned improved depending on your needs)
From my understanding you can't block/hide the scrubbing commands unless you can tag the audio as a live stream. That being said, you can use js to refuse scrubbing server-side. Reference the answer here. Although that answer speaks of video, it also works with audio.
The lock screen / control center scrubber can also be avoided by using Web Audio API.
This is an example of preloading a sound and playing it, with commentary and error handling:
try {
// <audio> element is simpler for sound effects,
// but in iOS/iPad it shows up in the Control Center, as if it's music you'd want to play/pause/etc.
// Also, on subsequent plays, it only plays part of the sound.
// And Web Audio API is better for playing sound effects anyway because it can play a sound overlapping with itself, without maintaining a pool of <audio> elements.
window.audioContext = window.audioContext || new AudioContext(); // Interoperate with other things using Web Audio API, assuming they use the same global & pattern.
const audio_buffer_promise =
fetch("audio/sound.wav")
.then(response => response.arrayBuffer())
.then(array_buffer => audioContext.decodeAudioData(array_buffer))
var play_sound = async function () {
audioContext.resume(); // in case it was not allowed to start until a user interaction
// Note that this should be before waiting for the audio buffer,
// so that it works the first time (it would no longer be "within a user gesture")
// This only works if play_sound is called during a user gesture (at least once), otherwise audioContext.resume(); needs to be called externally.
const audio_buffer = await audio_buffer_promise; // Promises can be awaited any number of times. This waits for the fetch the first time, and is instant the next time.
// Note that if the fetch failed, it will not retry. One could instead rely on HTTP caching and just fetch() each time, but that would be a little less efficient as it would need to decode the audio file each time, so the best option might be custom caching with request error handling.
const source = audioContext.createBufferSource();
source.buffer = audio_buffer;
source.connect(audioContext.destination);
source.start();
};
} catch (error) {
console.log("AudioContext not supported", error);
play_sound = function() {
// no-op
// console.log("SFX disabled because AudioContext setup failed.");
};
}
I did a search, in search of a way to help you, but I did not find an effective way to disable the commands, however, I found a way to customize them, it may help you, follow the apple tutorial link
I think what's left to do now is wait, see if ios 13 will bring some option that will do what you want.

Disable iOS Safari lock screen scrubber for media

Safari on iOS puts a scrubber on its lock screen for simple HTMLAudioElements. For example:
const a = new Audio();
a.src = 'https://example.com/audio.m4a'
a.play();
JSFiddle: https://jsfiddle.net/0seckLfd/
The lock screen will allow me to choose a position in the currently playing audio file.
How can I disable the ability for the user to scrub the file on the lock screen? The metadata showing is fine, and being able to pause/play is also acceptable, but I'm also fine with disabling it all if I need to.
DISABLE Player on lock screen completely
if you want to completely remove the lock screen player you could do something like
const a = new Audio();
document.querySelector('button').addEventListener('click', (e) => {
a.src = 'http://sprott.physics.wisc.edu/wop/sounds/Bicycle%20Race-Full.m4a'
a.play();
});
document.addEventListener('visibilitychange', () => {
if (document.hidden) a.src = undefined
})
https://jsfiddle.net/5s8c9eL0/3/
that is stoping the player when changing tab or locking screen
(code to be cleaned improved depending on your needs)
From my understanding you can't block/hide the scrubbing commands unless you can tag the audio as a live stream. That being said, you can use js to refuse scrubbing server-side. Reference the answer here. Although that answer speaks of video, it also works with audio.
The lock screen / control center scrubber can also be avoided by using Web Audio API.
This is an example of preloading a sound and playing it, with commentary and error handling:
try {
// <audio> element is simpler for sound effects,
// but in iOS/iPad it shows up in the Control Center, as if it's music you'd want to play/pause/etc.
// Also, on subsequent plays, it only plays part of the sound.
// And Web Audio API is better for playing sound effects anyway because it can play a sound overlapping with itself, without maintaining a pool of <audio> elements.
window.audioContext = window.audioContext || new AudioContext(); // Interoperate with other things using Web Audio API, assuming they use the same global & pattern.
const audio_buffer_promise =
fetch("audio/sound.wav")
.then(response => response.arrayBuffer())
.then(array_buffer => audioContext.decodeAudioData(array_buffer))
var play_sound = async function () {
audioContext.resume(); // in case it was not allowed to start until a user interaction
// Note that this should be before waiting for the audio buffer,
// so that it works the first time (it would no longer be "within a user gesture")
// This only works if play_sound is called during a user gesture (at least once), otherwise audioContext.resume(); needs to be called externally.
const audio_buffer = await audio_buffer_promise; // Promises can be awaited any number of times. This waits for the fetch the first time, and is instant the next time.
// Note that if the fetch failed, it will not retry. One could instead rely on HTTP caching and just fetch() each time, but that would be a little less efficient as it would need to decode the audio file each time, so the best option might be custom caching with request error handling.
const source = audioContext.createBufferSource();
source.buffer = audio_buffer;
source.connect(audioContext.destination);
source.start();
};
} catch (error) {
console.log("AudioContext not supported", error);
play_sound = function() {
// no-op
// console.log("SFX disabled because AudioContext setup failed.");
};
}
I did a search, in search of a way to help you, but I did not find an effective way to disable the commands, however, I found a way to customize them, it may help you, follow the apple tutorial link
I think what's left to do now is wait, see if ios 13 will bring some option that will do what you want.

Categories

Resources