WebRTC change microphone or webcam while in call - javascript

I am trying to figure out how to change the microphone or webcam while you are in a videochat with someone.
I have been now trying for a few days and nothing works.
I was following this example, but it seems it is much harder to achieve the change while someone is already connected.
The issues I have: If I change the mic the sound is lost/the mic doesnt react at all. I also cannot change it back to the default.
A similar thing happens if I change the webcam. The stream hangs, the last frame is seen.
I get no error message, in fact it tells me that the changes were successful.
Changing the webcam/mic WORKS before the call is established
Here is the relevant codeblock. Everywhere I am reading just create new constraints and give the desired deviceId to the audio/video stream.:
function ChangeDevice() {
if (localStream) {
localStream.getTracks().forEach(track => {
track.stop();
});
}
var audioSource = audioInputSelect.value;
var videoSource = videoSelect.value;
console.log(videoSource);
console.log(audioSource);
const newConstraints = {
audio: {deviceId: audioSource ? {exact: audioSource} : undefined},
video: {deviceId: videoSource ? {exact: videoSource} : undefined}
};
navigator.mediaDevices.getUserMedia(newConstraints).then(gotStream).then(gotDevices).catch(handleError);
}
function gotStream(stream) {
console.log('Adding local stream.');
localStream = stream;
localVideo.srcObject = stream;
sendMessage(['got user media', room]);
if (isInitiator) {
maybeStart();
}
return navigator.mediaDevices.enumerateDevices(); // I added this
}
I think these two are the relevant functions, ChangeDevice is called when I select a new device from a dropdown. The id's are correct.
Here is the whole code I use:
pastebin.com/6JrK4jJD

Luckily replaceTrack seems to work now on all browsers, so there is no need to renegotiate.
I had to edit my gotStream function like this:
function gotStream(stream) {
// If already started
// Need this if webcam or mic changes
if (isStarted) {
var videoTrack = stream.getVideoTracks()[0];
var audioTrack = stream.getAudioTracks()[0];
var sender = pc.getSenders().find(function(s) {
return s.track.kind == videoTrack.kind;
});
var sender2 = pc.getSenders().find(function(s) {
return s.track.kind == audioTrack.kind;
});
console.log('found sender:', sender);
sender.replaceTrack(videoTrack);
sender2.replaceTrack(audioTrack);
localStream = stream;
localVideo.srcObject = stream;
} else {
console.log('Adding local stream.');
localStream = stream;
localVideo.srcObject = stream;
sendMessage(['got user media', room]);
if (isInitiator) {
maybeStart();
}
}
return navigator.mediaDevices.enumerateDevices(); // I added this
}

Related

Playing wave audio from JS array data in browser [duplicate]

I am pretty sure I did everything correct but when I try to play or download the file nothing plays. I am using web audio api to record audio from the microphone to a WAV format. I am using this library to create the .wav file. It seems like nothing is being encoded.
navigator.mediaDevices.getUserMedia({
audio: true,video:false
})
.then((stream) => {
var data
context = new AudioContext()
var source = context.createMediaStreamSource(stream)
var scriptNode = context.createScriptProcessor(8192, 1, 1)
source.connect(scriptNode)
scriptNode.connect(context.destination)
encoder = new WavAudioEncoder(16000,1)
scriptNode.onaudioprocess = function(e){
data = e.inputBuffer.getChannelData('0')
console.log(data)
encoder.encode(data)
}
$('#stop').click(()=>{
source.disconnect()
scriptNode.disconnect()
blob = encoder.finish()
console.log(blob)
url = window.URL.createObjectURL(blob)
// audio source
$('#player').attr('src',url)
// audio control
$("#pw")[0].load()
})
})
I figured it out! To help anyone who needs to do the same thing. It uses Web Audio API and this javascript library
navigator.mediaDevices.getUserMedia({
audio: true,video:false
})
.then((stream) => {
context = new AudioContext()
var source = context.createMediaStreamSource(stream)
var rec = new Recorder(source)
rec.record()
$('#stop').click(()=>{
rec.stop()
blob = rec.exportWAV(somefunction) // exportWAV() returns your file
})
use recordRTC for recording video and audio, I used in my project, it's working well, here is the code to record audio using recordrtc.org
startRecording(event) { // call this to start recording the Audio( or video or Both)
this.recording = true;
let mediaConstraints = {
audio: true
};
// Older browsers might not implement mediaDevices at all, so we set an empty object first
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// Some browsers partially implement mediaDevices. We can't just assign an object
// with getUserMedia as it would overwrite existing properties.
// Here, we will just add the getUserMedia property if it's missing.
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function(constraints) {
// First get ahold of the legacy getUserMedia, if present
var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// Some browsers just don't implement it - return a rejected promise with an error
// to keep a consistent interface
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
}
// Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
return new Promise(function(resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
}
navigator.mediaDevices.getUserMedia(mediaConstraints)
.then(successCallback.bind(this), errorCallback.bind(this));
}
successCallback(stream: MediaStream) {
var options = {
type: 'audio'
};
this.stream = stream;
this.recordRTC = RecordRTC(stream, options);
this.recordRTC.startRecording();
}
errorCallback(stream: MediaStream) {
console.log(stream);
}
stopRecording() { // call this to stop recording
this.recording = false;
this.converting = true;
let recordRTC = this.recordRTC;
if(!recordRTC) return;
recordRTC.stopRecording(this.processAudio.bind(this));
this.stream.getAudioTracks().forEach(track => track.stop());
}
processAudio(audioVideoWebMURL) {
let recordRTC = this.recordRTC;
var recordedBlob = recordRTC.getBlob(); // you can save the recorded media data in various formats, refer the link below.
console.log(recordedBlob)
this.recordRTC.save('audiorecording.wav');
let base64Data = '';
this.recordRTC.getDataURL((dataURL) => {
base64Data = dataURL.split('base64,')[1];
console.log(RecordRTC.getFromDisk('audio', function(dataURL,type) {
type == 'audio'
}));
console.log(dataURL);
})
}
Note that you cannot record the audio/video from the live site in Google Chrome unless your site is https enabled

JavaScript video in browser: how to load mp4 video by modifying this sample code?

I found sample code for loading the user's webcam (which works), but I'd like to modify it to run 'myvideo.mp4' in the project folder. Here is the existing code:
async function setupVideo2() {
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
throw new Error(
'Browser API navigator.mediaDevices.getUserMedia not available');
}
const video2 = document.getElementById('video2');
video2.width = videoWidth2;
video2.height = videoHeight2;
const stream = await navigator.mediaDevices.getUserMedia({
'audio': false,
'video': {
facingMode: 'user',
width: videoWidth2,
height: videoHeight2,
},
});
video2.srcObject = stream;
return new Promise((resolve) => {
video2.onloadedmetadata = () => {
resolve(video2);
};
});
}
I have this code already called elsewhere and it works and returns the user's webcam. So I figure modifying this I should be able to use arbitrary video sources, e.g. an MP4 file I have in my project folder.
"...Can I just do video2.srcObject = './video_example.mp4';? I get TypeError: Failed to set the 'srcObject' property on 'HTMLMediaElement': The provided value is not of type 'MediaStream'."
No you must use .src to set a file path, not using srcObjecct.
function setupVideo2()
{
const video2 = document.getElementById('video2');
video2.width = videoWidth2;
video2.height = videoHeight2;
video2.src = "https://www.w3schools.com/tags/movie.mp4"; //# testable video link
}

Record (and stop) an already running Webcam/Media Stream

I’ve created a minimal WebRTC test site that is able to request the user’s webcam/audio stream, to record it, and to playback the recording after it has been stopped.
Demo: https://output.jsbin.com/tabosipefo/
Edit1: https://jsbin.com/tabosipefo/edit?html,console,output
Since this happens all within one Promise navigator.mediaDevices.getUserMedia(), I was wondering, if it is actually possible to detect and on-going stream and to (a) record it, and (b) to stop and save it.
1 WebRTC does not work in jsbin when in edit view for some reason...
If you use no framework and want to use vanilla JS, your best step is to tack the stream object to the global window.
Preview stream
const showWebcamStream = () => {
navigator.mediaDevices
.getUserMedia({ audio: true, video: true })
.then(stream => {
window.localStream = stream; // ⭠ tack it to the window object
// grab the <video> object
const video = document.querySelector("#video-preview");
video.srcObject = stream;
// Display stream
video.onloadedmetadata = () => video.play();
})
.catch(err => console.log(err.name, err.message));
};
Now the video will be displayed within the video element (id: #videp-preview).
Stop Stream(s)
const hideWebcamStream = () => localStream.getTracks().forEach(track => track.stop());
You should put the mediaRecorder in the window object in order to stop it later.
Record Stream
const startWebcamRecorder = () => {
// check if localStream is in window and if it is active
if ("localStream" in window && localStream.active) {
// save the mediaRecorder also to Window in order independently stop it
window.mediaRecorder = new MediaRecorder(localStream);
window.dataChunks = [];
mediaRecorder.start();
console.log(mediaRecorder.state);
mediaRecorder.ondataavailable = e => dataChunks.push(e.data);
}
};
Stop Recording and Preview the recording
You need another video element to playback your recording #video-playback
const stopWebcamRecorder = () => {
if ("mediaRecorder" in window && mediaRecorder.state === "recording") {
mediaRecorder.stop();
console.log(mediaRecorder.state);
mediaRecorder.onstop = () => {
let blob = new Blob(dataChunks, { type: "video/mp4;" });
dataChunks = [];
let videoURL = window.URL.createObjectURL(blob);
const videoPlayback = document.getElementById("video-playback");
videoPlayback.src = videoURL;
};
}
};

javascript close audio stream and remove mic icon from tab [duplicate]

I am using recording.js. The functionality is working fine but after I stop recording the red icon still appears in chrome's tab(near title). Please suggest what to do.
Sorry if it is damn easy.. :P
This is my code:
window.URL = window.URL || window.webkitURL;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
var recorder;
var savedSrc = '';
var audio = document.querySelector('audio');
var onFail = function(e)
{
console.log('Rejected!', e);
};
var onSuccess = function(s)
{
var context = new AudioContext();
var mediaStreamSource = context.createMediaStreamSource(s);
recorder = new Recorder(mediaStreamSource);
recorder.record();
$('#recordText').html('Recording...');
// audio loopback
// mediaStreamSource.connect(context.destination);
};
function startRecording()
{
if (navigator.getUserMedia)
{
navigator.getUserMedia(
{
video : false,
audio : true,
toString : function()
{
return "audio";
}
}, onSuccess, onFail);
}
else
{
console.log('navigator.getUserMedia not present');
}
};
function stopRecording()
{
$('#recordText').html('Record');
recorder.stop();
recorder.exportWAV(function(s)
{
audio.src = window.URL.createObjectURL(s);
});
}
To remove red icon after using Recorder.js:
var audioStream;
var onSuccess = function(s) {
...
audioStream = s;
}
function stopRecording() {
...
audioStream.getTracks()[0].stop();
}
getTracks() returns only one element since you use only audio in your config.
I hope it will help someone.
You can end the stream directly using the stream object returned in the success handler to getUserMedia.
Example
localMediaStream.stop()
It means that your browser is holding the active instance of mic stream.
The solution given below can be a food for your thoughts.
Solution:
While assigning an audio stream ensure that you assigned reference to it to windows variable and control it (stop) from wherever you need it.
See if my code can make sense to you, its working in my case. Ensure that you reassign the stream once you stopped otherwise you ll get an exception as stop completely destroy the existing instance. (please adapt the code accordingly)
// Excerpt from my reactjs code
async function requestRecorder() {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
window.localStream=stream;
return new MediaRecorder(stream);
}
const stopRecording = () =>
{
//release mic resource and hence red icon is removed
window.localStream.getTracks()[0].stop();
};
It's a browser feature, not a site feature. It will be there until you close the tab, indicates that "This tab has access to or using microphone or webcam".
At the time of writing the answer there were no way to remove that icon. You may now can remove it after the recording has stopped.
Check #akaravashkin 's answer, I haven't tested it.

Stream live audio to Node.js server

I'm working on a project and I require to send an audio stream to a Node.js server. I'm able to capture microphone sound with this function:
function micCapture(){
'use strict';
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
var constraints = {
audio: true,
video: false
};
var video = document.querySelector('video');
function successCallback(stream) {
window.stream = stream; // stream available to console
if (window.URL) {
video.src = window.webkitURL.createObjectURL(stream);
} else {
video.src = stream;
}
//Send audio stream
//server.send(stream);
}
function errorCallback(error) {
console.log('navigator.getUserMedia error: ', error);
}
navigator.getUserMedia(constraints, successCallback, errorCallback);
}
As you can see, I'm able to capture audio and play it on the website.
Now I want to send that audio stream to a Node.js server, and send it back to other clients. Like a voicechat, but I don't want to use WebRTC as I need the stream in the server. How can I achieve this? Can I use socket.io-stream to do this? In the examples I saw, they recorded the audio, and sent a file, but I need "live" audio.
I have recently done live audio upload using socket.io from browser to server. I am going to answer here in case someone else needs it.
var stream;
var socket = io();
var bufferSize = 1024 * 16;
var audioContext = new AudioContext();
// createScriptProcessor is deprecated. Let me know if anyone find alternative
var processor = audioContext.createScriptProcessor(bufferSize, 1, 1);
processor.connect(audioContext.destination);
navigator.mediaDevices.getUserMedia({ video: false, audio: true }).then(handleMicStream).catch(err => {
console.log('error from getUserMedia', err);
});
handleMicStream will run when user accepts the permission to use microphone.
function handleMicStream(streamObj) {
// keep the context in a global variable
stream = streamObj;
input = audioContext.createMediaStreamSource(stream);
input.connect(processor);
processor.onaudioprocess = e => {
microphoneProcess(e); // receives data from microphone
};
}
function microphoneProcess(e) {
const left = e.inputBuffer.getChannelData(0); // get only one audio channel
const left16 = convertFloat32ToInt16(left); // skip if you don't need this
socket.emit('micBinaryStream', left16); // send to server via web socket
}
// Converts data to BINARY16
function convertFloat32ToInt16(buffer) {
let l = buffer.length;
const buf = new Int16Array(l / 3);
while (l--) {
if (l % 3 === 0) {
buf[l / 3] = buffer[l] * 0xFFFF;
}
}
return buf.buffer;
}
Have your socket.io server listen to micBinaryStream and you should get the data. I needed the data as a BINARY16 format for google api if you do not need this you can skip the function call to convertFloat32ToInt16().
Important
When you need to stop listening you MUST disconnect the the processor and end the stream. Run the function closeAll() below.
function closeAll() {
const tracks = stream ? stream.getTracks() : null;
const track = tracks ? tracks[0] : null;
if (track) {
track.stop();
}
if (processor) {
if (input) {
try {
input.disconnect(processor);
} catch (error) {
console.warn('Attempt to disconnect input failed.');
}
}
processor.disconnect(audioContext.destination);
}
if (audioContext) {
audioContext.close().then(() => {
input = null;
processor = null;
audioContext = null;
});
}
}
it's an old time question, i see. I'm doing the same thing (except my server doesn't run node.js and is written in C#) and stumbled upon this.
Don't know if someone is still interested but i've elaborated a bit. The current alternative to the deprecated createScriptProcessor is the AudioWorklet interface.
From: https://webaudio.github.io/web-audio-api/#audioworklet
1.32.1. Concepts
The AudioWorklet object allows developers to supply scripts (such as JavaScript or >WebAssembly code) to process audio on the rendering thread, supporting custom >AudioNodes. This processing mechanism ensures synchronous execution of the script >code with other built-in AudioNodes in the audio graph.
You cannot implement interfaces in Javascript as far as i know but you can extend a class derived from it.
And the one we need is: https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor
So i did write a processor that just mirrors the output with the input values and displays them.
class CustomAudioProcessor extends AudioWorkletProcessor {
process (inputs, outputs, parameters) {
const input = inputs[0];
const output = output[0];
for (let channel = 0; channel < input.length; ++channel) {
for (let i = 0; i < input[channel].length; ++i) {
// Just copying all the data from input to output
output[channel][i] = input[channel][i];
// The next one will make the app crash but yeah, the values are there
// console.log(output[channel][i]);
}
}
}
}
The processor must then be placed into the audio pipeline, after the microphone and before the speakers.
function record() {
constraints = { audio: true };
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
audioCtx = new AudioContext();
var source = audioCtx.createMediaStreamSource(stream);
audioCtx.audioWorklet.addModule("custom-audio-processor.js").then(() => {
customAudioProcessor = new AudioWorkletNode(audioCtx, "custom-audio-processor");
source.connect(customAudioProcessor);
customAudioProcessor.connect(audioCtx.destination);
})
audioCtx.destination.play();
Works! Good luck! :)

Categories