How do I combine two media streams? - javascript

navigator.mediaDevices.getDisplayMedia({ audio: true, video: true }).then(stream => {});
navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then(stream => {});
How to put these two streams into one?

Merging capture stream with webcam stream example using the video-stream-merger.js lib
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/video-stream-merger#4.0.1/dist/video-stream-merger.js"></script>
<title>Merge screen with webcam</title>
</head>
<body>
<video id="video"></video>
</body>
<script>
async function startCapture() {
let webcamStream = null;
const constraints = { audio: true, video: { width: 720, height: 480 } };
try {
webcamStream = await navigator.mediaDevices.getUserMedia(constraints);
} catch (err) {
/* handle the error */
console.error("Error: " + err);
}
let captureStream = null;
try {
const displayMediaOptions = null; //set it if you need
captureStream = await navigator.mediaDevices.getDisplayMedia(
displayMediaOptions
);
} catch (err) {
/* handle the error */
console.error("Error: " + err);
}
const merger = new VideoStreamMerger();
// Add the screen capture. Position it to fill the whole stream (the default)
merger.addStream(captureStream, {
x: 0, // position of the topleft corner
y: 0,
width: merger.width,
height: merger.height,
mute: true, // we don't want sound from the screen (if there is any)
});
// Add the webcam stream. Position it on the bottom left and resize it to 100x100.
merger.addStream(webcamStream, {
x: 0,
y: merger.height - 100,
width: 100,
height: 100,
mute: false,
});
// Start the merging. Calling this makes the result available to us
merger.start();
// We now have a merged MediaStream!
//merger.result
const video = document.querySelector("video");
video.srcObject = merger.result;
video.onloadedmetadata = function (e) {
video.play();
};
}
startCapture();
</script>
</html>

Related

getting error during convert audio blob to array buffer wav

I have to send audio stream from microphone with websocket as wav format,
during convert convert audio buffer array to wav I get this error:
DOMException: Failed to execute 'decodeAudioData' on 'BaseAudioContext': Unable to decode audio data
I tried javascript-processor-node for this but it was deprecated, I think audio worklet for sending simple converted format on websocket stream is too much for task like this!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body id="body">
<button id="button">test</button>
<script defer>
const btn = document.getElementById("button");
btn.addEventListener("click", () => {
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
const recorder = new MediaRecorder(stream);
recorder.start(10);
recorder.addEventListener("dataavailable", (event) => {
const streamBlob = new Blob([event.data]);
streamBlob
.arrayBuffer()
.then((arrayBuffer) => {
const audioCtx = new AudioContext({ sampleRate: 16000 });
audioCtx
?.decodeAudioData(arrayBuffer)
?.then((buffer) => {
console.log("🚀 ?.then wavBlob", buffer);
})
.catch((error) => {
console.log("🚀 . error1", error);
})
.finally(() => {});
})
.catch((error) => {
console.log("🚀 . error2", error);
});
});
}),
function (e) {
alert("Error capturing audio.");
};
});
</script>
</body>
</html>

How to upload a webcam stream of video and send bytes as soon as they arrive to the server using javascript and python3?

How do you upload a webcam to the server as the bytes are created from the webcam?
This is my starting code.
let mediaRecorder = new MediaRecorder(mediaStreamObj);
let chunks = [];
Here is the solution. Tested and Works in both Firefox and Chrome.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MediaCapture and Streams API</title>
<meta name="viewport" content="width=device-width">
<script type="text/javascript" src="js/jquery.min.js"></script>
</head>
<body>
<header>
<h1>MediaCapture, MediaRecorder and Streams API</h1>
</header>
<main>
<p></p>
<p><button id="btnStart">START RECORDING</button><br/>
<button id="btnStop">STOP RECORDING</button></p>
<video controls></video>
<video id="vid2" controls></video>
<!-- could save to canvas and do image manipulation and saving too -->
</main>
<script>
let constraintObj = {
audio: true,
video: {
facingMode: "user",
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 480, ideal: 720, max: 1080 }
}
};
// width: 1280, height: 720 -- preference only
// facingMode: {exact: "user"}
// facingMode: "environment"
//handle older browsers that might implement getUserMedia in some way
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
navigator.mediaDevices.getUserMedia = function(constraintObj) {
let getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
}
return new Promise(function(resolve, reject) {
getUserMedia.call(navigator, constraintObj, resolve, reject);
});
}
}else{
navigator.mediaDevices.enumerateDevices()
.then(devices => {
devices.forEach(device=>{
console.log(device.kind.toUpperCase(), device.label);
//, device.deviceId
})
})
.catch(err=>{
console.log(err.name, err.message);
})
}
navigator.mediaDevices.getUserMedia(constraintObj)
.then(function(mediaStreamObj) {
//connect the media stream to the first video element
let video = document.querySelector('video');
if ("srcObject" in video) {
video.srcObject = mediaStreamObj;
} else {
//old version
video.src = window.URL.createObjectURL(mediaStreamObj);
}
video.onloadedmetadata = function(ev) {
//show in the video element what is being captured by the webcam
video.play();
};
//add listeners for saving video/audio
let start = document.getElementById('btnStart');
let stop = document.getElementById('btnStop');
let vidSave = document.getElementById('vid2');
let mediaRecorder = new MediaRecorder(mediaStreamObj);
let chunks = [];
start.addEventListener('click', (ev)=>{
mediaRecorder.start(1000); // milliseconds, so 1 second is 1000
console.log(mediaRecorder.state);
})
stop.addEventListener('click', (ev)=>{
mediaRecorder.stop();
console.log(mediaRecorder.state);
});
mediaRecorder.ondataavailable = function(ev) {
chunks.push(ev.data);
var res;
var pos;
var b = "base64," ;
var fr = new FileReader();
fr.onload = function(){
res = this.result;
pos = res.search(b);
pos = pos + b.length;
res = res.substring(pos);
$.ajax({
type: 'POST',
url: 'server.py',
dataType: "html",
data: { chunk: res },
success: function(data){
//alert(data + ' yes');
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert('Status: ' + textStatus + ' ' + ' Error: ' + errorThrown);
}
});
}
fr.readAsDataURL(ev.data);
}
mediaRecorder.onstop = (ev)=>{
let blob = new Blob(chunks, { 'type' : 'video/mp4;' });
chunks = [];
let videoURL = window.URL.createObjectURL(blob);
vidSave.src = videoURL;
}
})
.catch(function(err) {
console.log(err.name, err.message);
});
</script>
</body>
</html>
And the Python3 server.py file to accept the real time webcam video is the following.
import os
import sys
import cgi
import cgitb
import base64
include_path = '/var/webcam_project/www'
cgitb.enable(display=0, logdir=f"""{include_path}/tmp_errors""") # include_path is OUTDIR
sys.path.insert(0, include_path)
def enc_print(string='', encoding='utf8'):
sys.stdout.buffer.write(string.encode(encoding) + b'\n')
from html import escape
args = cgi.FieldStorage()
chunk = '' if not args.getvalue( "chunk" ) else escape( args.getvalue( "chunk" ) )
mp4 = 'webcam.mp4'
with open (mp4, 'ab') as f:
f.write( base64.b64decode(chunk) )
html = 'success'
enc_print("Content-Type:text/html;charset=utf-8;")
enc_print()
enc_print(html)
Vote up if you like these kinds of solutions of value.

Electron Remote process with user input width and height not working

I am having a problem with using remote windows in electron I am trying to process user input ans use that input to create a new window with a certain width and height yet when I press submit nothing happens
I am not sure why its not working in theory everything looks fine i am not getting any errors and testt.html is loading up just fine when i press submit it just resets
here is my code:
testt.html
<script src="./renderer.js"></script>
<form onsubmit="SetAction(this)">
<label for="fname">Width</label><br>
<input type="text" id="fname" name="fname"><br>
<label for="lname">Height</label><br>
<input type="text" id="lname" name="lname"><br>
<input type="submit">
</form>
renderer.js
function SetAction(form) {
const { BrowserWindow } = require('#electron/remote/main')
//const remote = require('electron').remote;
//const BrowserWindow = remote.BrowserWindow;
const w = form.fname.value;
const h = form.lname.value;
const win = new BrowserWindow({
height: w,
width: h,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: false,
webSecurity: false
}
});
window.require("#electron/remote/main").enable(win.webContents);
win.loadFile('index.html')
// var w = form.fname.value;
// alert(w)
}
cleint.js
const WindowPosition = require( 'electron-window-position' );
const path = require('path')
const prompt = require('electron-prompt');
const fs = require('fs')
function createWindow () {
// Create the browser window.
const position = new WindowPosition();
var pos = position.getActiveScreenCenter(0,0);
const mainWindow = new BrowserWindow({
x: pos.x,
y: pos.y,
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false,
webSecurity: false
}
})
require("#electron/remote/main").initialize(); require("#electron/remote/main").enable(mainWindow.webContents);
console.log(app.getPath('userData'))
mainWindow.loadFile('index.html')
mainWindow.setBackgroundColor("#000F1A")
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
There are many ways to implement this functionality. Choosing a particular way and fine-tuning it would be based on
personal preference and desired functionality.
In the example below, I have not used nodeIntegration: true and contextIsolation: false. Instead, I have these
settings reversed. IE: nodeIntegration: false and contextIsolation: true. This ensures that "both your preload
scripts and Electron's internal logic run in a separate context to the website you load in a webContents".
See Context Isolation for more information.
To keep things simple, I have the below preload.js script only managing the definition of "channel names" and
implementation of Inter-Process Communication. Apart from
the preload.js script, all other files shown below should give you a good idea on how to implement an answer.
preload.js (main process)
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'openWindow'
],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
Within your main.js script, create the main window and listed for a call on the channel name openWindow. When
received, pass on the form options (width and height) to create the newIndex.html window.
main.js (main process)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
// Prevent garbage collection.
let mainWindow;
let newWindow;
function createWindow(options) {
return new electronBrowserWindow({
x: 0,
y: 0,
width: options.width,
height: options.height,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
}
electronApp.on('ready', () => {
mainWindow = createWindow({width: 800, height: 600});
mainWindow.loadFile('index.html')
.then(() => { mainWindow.show(); });
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// ----------------
electronIpcMain.on('openWindow', (event, options) => {
newWindow = createWindow(options);
newWindow.loadFile('newIndex.html')
.then(() => { newWindow.show(); });
})
This is the main window that will be displayed on application start-up, along with form logic and an IPC
call (openWindow) from the render to the main process.
index.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Main Window</title>
</head>
<body>
<label for="width">Width: </label>
<input type="number" id="width" min="400" max="800" step="50" value="800">
<label for="height">Height: </label>
<input type="number" id="height" min="200" max="600" step="50" value="600">
<input type="button" id="button" value="Open Window">
</body>
<script>
document.getElementById('button').addEventListener('click', () => {
let options = {
// Convert values from strings to integers
width: parseInt(document.getElementById('width').value),
height: parseInt(document.getElementById('height').value)
};
window.ipcRender.send('openWindow', options);
});
</script>
</html>
And finally, the second window that will be created on form submission.
newIndex.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>New Window</title>
</head>
<body>
<h1>New Window</h1>
</body>
</html>

WebRTC + nodejs/socketio no audio

Some days working on this, and no luck, I'm just trying to get the basic example working of only audio, but no audio on index1.html, everything works, no issues, connection established, offer/answer exchanged, icecandidate also exchanged, this is not working on both chrome or firefox.
I don't know whats wrong, I'm just exactly copying https://webrtc.github.io/samples/src/content/peerconnection/audio/ just splitting it out into 2 files, I dont get it, also I see a lot of people saying that the issue is wit stun/ice servers, actually the examples work without them so I guess I dont need them.
The setup:
index.html, the one who will initiate the call and capture its audio.
index1.html, will receive the call and play the audio.
PD: the code isn't clean or anything, just testing stuff.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! Site Title</title>
</head>
<body>
<h1>Hello World!</h1>
<button id="call">CALL</button>
</body>
<script src="http://localhost:3000/socket.io/socket.io.js"></script>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script>
var socket = io("http://localhost:3000");
//const { RTCPeerConnection, RTCSessionDescription } = window;
let callButton = document.getElementById("call");
let peerConnection;
socket.on('sendICe_to_0', data => {
console.log("received ice candidate", data);
peerConnection.addIceCandidate(data.candidate);
});
callButton.onclick = e => {
peerConnection = new RTCPeerConnection(null);
peerConnection.onicecandidate = e => {
console.log("send ice to 1", e);
socket.emit('sendICe_to_1', e);
}
navigator.mediaDevices
.getUserMedia({
audio: true,
video: false
})
.then(gotStream)
.catch(e => {
alert(`getUserMedia() error: ${e.name}`);
});
};
async function gotStream(stream) {
console.log("capturing audio");
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
let offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
console.log("sending offer", offer);
socket.emit('offer_to_1', offer);
/*peerConnection.createOffer()
.then(data => {
console.log("creating offer", data);
peerConnection.setLocalDescription(data)
.then(data => {
console.log("sending offer", data);
socket.emit('offer_to_1', data);
});
});*/
}
socket.on('send_asnwer_0', data => {
console.log("received anser", data);
peerConnection.setRemoteDescription(data);
});
</script>
</html>
index1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! Site Title</title>
</head>
<body>
<h1>Hello World!</h1>
<audio id="audio" autoplay controls></audio>
</body>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="http://localhost:3000/socket.io/socket.io.js"></script>
<script>
// https://github.com/peers/peerjs/issues/470
var socket = io("http://localhost:3000");
let peerConnection;
socket.on('sendICe_to_1', data => {
console.log("Received ice candidate", data);
peerConnection.onicecandidate = e => {
console.log("Sending ice candidate", e)
socket.emit('sendICe_to_0', e);
};
peerConnection.addIceCandidate(data.candidate);
peerConnection.ontrack = e => {
console.log("Adding remote stream");
let audio = document.querySelector('audio#audio');
if (audio.srcObject !== e.streams[0]) {
audio.srcObject = e.streams[0];
console.log('Received/Configured remote stream');
}
};
});
async function offerTo1(data) {
peerConnection = new RTCPeerConnection(null);
console.log("Received offer from 0", data)
await peerConnection.setRemoteDescription(data)
let answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
console.log("sending answer", answer);
socket.emit('send_asnwer_0', answer);
}
socket.on('offer_to_1', data => {
offerTo1(data);
});
</script>
</html>
NodeJs server
const io = require('socket.io')();
io.origins('*:*');
io.listen(3000);
let connectedSockets = [];
/*io.on('connection', (socket) => {
connectedSockets.push(socket);
console.log("Client connected!");
socket.on('sendAudio', (data) => {
console.log(data);
connectedSockets[1].emit("receiveAudio", data)
})
});*/
io.on('connection', (socket) => {
connectedSockets.push(socket);
console.log("Client connected!");
socket.on("offer_to_1", (data) => {
console.log("offer_to_1")
connectedSockets[1].emit("offer_to_1", data);
});
socket.on('send_asnwer_0', data => {
console.log("send_asnwer_0")
connectedSockets[0].emit("send_asnwer_0", data);
});
socket.on("sendICe_to_1", (data) => {
console.log("sendICe_to_1")
connectedSockets[1].emit("sendICe_to_1", data);
});
socket.on("sendICe_to_0", (data) => {
console.log("sendICe_to_0")
connectedSockets[0].emit("sendICe_to_0", data);
});
});

getUserMedia() can't release the camera

There is somewhere very silly mistake in my code that I can't find. Basically what I'm doing is, I'm using two separate buttons to start and stop recording the stream that I get from WebRTC getUserMedia() (I'm using RecordRTC for recording). My stop function stops the recording but does not release the camera.
<script type="text/javascript">
$(document).ready(function () {
var recorder;
var video = document.getElementById("video");
var videoConstraints = {
video: {
mandatory: {
minWidth: 1280,
minHeight: 720,
maxWidth: 1920,
maxHeight: 1080,
minFrameRate: 29.97,
maxFrameRate: 60,
minAspectRatio: 1.77
}
},
audio: true
};
function captureCamera(callback) {
navigator.mediaDevices.getUserMedia(videoConstraints).then(function (camera) {
callback(camera);
}).catch(function (error) {
alert('Unable to capture your camera. Please check console logs.');
console.error(error);
});
}
function stopRecordingCallback() {
video.src = video.srcObject = null;
video.src = URL.createObjectURL(recorder.getBlob());
video.play();
//recorder.camera.stop(); //its the deprecated way
recorder.camera.getTracks().forEach(track => track.stop()); //modern way as per documentation
recorder.destroy();
recorder = null;
}
hasGetUserMedia() {
return (navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
}
$('#startRecord').on("click", function () {
if (hasGetUserMedia()) {
/*----------------recording process start----------------*/
this.disabled = true;
captureCamera(function (camera) {
setSrcObject(camera, video);
video.play();
var options = {
recorderType: MediaStreamRecorder,
mimeType: 'video/webm\;codecs=h264',
audioBitsPerSecond: 128000,
videoBitsPerSecond: 2097152, // 2 mbps
};
recorder = RecordRTC(camera, options);
recorder.startRecording();
// release camera on stopRecording
recorder.camera = camera;
document.getElementById('stopRecord').disabled = false;
});
/*----------------recording process end----------------*/
}
else {
alert('getUserMedia() is not supported by your browser');
}
});
$('#stopRecord').on("click", function () {
this.disabled = true;
document.getElementById('startRecord').disabled = false;
recorder.stopRecording(stopRecordingCallback);
});
});
</script>
So I can't find the reason why the camera isn't released when the $('#stopRecord').on("click", function (){}) is called. Any help?
You can stop your stream's tracks, like this:
navigator.getUserMedia({audio: false, video: true},
function(stream) {
// can also use getAudioTracks() or getVideoTracks()
var track = stream.getTracks()[0]; // if only one media track
// ...
track.stop();
},
function(error){
console.log('getUserMedia() error', error);
});
So, in your case, I believe you can do something like this:
var track = recorder.camera.getTracks()[0]; // if only one media track
// ...
track.stop();

Categories

Resources