Is there a way to get/set the USB Hardware ID's for Mic/Camera in Chromium browser from the command line.
I could not able to find out. The requirement is to set a particular Camera/Mic during runtime without manually updating in chrome://settings/content in the chromium browser.
It will be great if I can get some link or some sample code. Thanks !!!
You can use the --use-fake-ui-for-media-stream CLI flag to suppress the permissions dialog from showing up when you have a media stream. It will allow by default.
In terms of selecting a particular microphone or camera with a CLI flag, I don't believe it's possible. However, you can access and select the device to be used in JavaScript.
The navigator.mediaDevices.enumerateDevices object returns the following information:
The device kind - audioinput or videoinput
The device label - e.g. Built-in Microphone, Logitech bla, FaceTime HD Camera, etc.
The device Id - e.g. bd9759740e6c29df7703d0bfg62vbbc726fcb9422fdb948a35550835a840b328
The device group Id - If 2+ devices share the same physical device, they can have group Id
The Id is generated per session, so it's not something we can hardcode, but the label is unlikely to change. This means you could traverse the list and get the device Id that matches the string. I did this with async/await but you can use Promises or transpile to ES5 if need be:
(async() => {
const getFaceTimeCamId = async() => {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const faceTimeCam = devices.find(device =>
device.kind === 'videoinput' && device.label === 'FaceTime HD Camera');
return (faceTimeCam && faceTimeCam.deviceId) || undefined;
} catch(err) {
console.log(`${err.name}: ${err.message}`);
}
}
console.log(await getFaceTimeCamId());
})();
You can then set the constraints object for your stream to include the deviceId you want, for example, when using MediaDevices.getUserMedia.
const constraints = {
video: { deviceId: { exact: faceTimeCamId } }
};
I've not fully tested this as I don't have another camera or mic at hand, but hopefully this is of some help. I don't know your exact use case so probably made some assumptions.
Related
I'm using Puppeteer to automate some page actions in an already open, fully-visible browser (non-headless). Currently, I manually set the viewport like this:
const page = await browser.newPage();
await page.setViewport({width: W, height: H});
I have to manually set W and H based on both the actual screen resolution, and on the system-wide scaling factor. This makes the script very brittle and non-portable.
I would like to have the new page always open with the largest possible visible viewport, without having to manually specify what that is. I tried some of the other solutions suggested on SO and elsewhere, such as setting the viewport to null, but I have not yet stumbled upon a working solution for my specific use case. Any help would be appreciated. Thanks!
If you want to set the W and H persistently across a launched browser you need to set defaultViewport: null together with --window-size=${W},${H} launch arg. It sets the window size and viewport on browser-level, not on page-level (which changes with each new tab).
Like this, all the newly opened tabs will share the same window size and viewport.
const browser = await puppeteer.launch({
defaultViewport: null,
args: [`--window-size=${W},${H}`]
})
If you can retrieve the screen resolution from system specifications you would be able to correctly set the viewport size from it.
Though you will probably not be able to get this information directly from javascript.
If you can get this information from a PowerShell script (see edit), you could try the following to execute that script from javascript and retrieve this information in your program in order to set your viewport dimensions.
const {spawn} = require("child_process");
async function getSomeDataFromAPowerShellScript() {
const child = spawn("powershell.exe", ["./PATH/MyPowerShellScript.ps1"]); // spawn a powershell terminal as a child process of main program and run the provided script in it
return await new Promise(resolve => {
child.stdout.on("data", (data) => { // trigger when data is send into the child terminal
console.log(data);
resolve(data);
};
});
}
A call to getSomeDataFromAPowerShellScript() will return the first outputed data in the powershell terminal as a string.
If you want to retrieve more informations than just the first output in the powershell terminal you can use this instead:
async function getSomeDataFromAPowerShellScript() {
const child = spawn("powershell.exe", ["./PATH/MyPowerShellScript.ps1"]); // spawn a powershell terminal as a child process of main program and run the provided script in it
let result = [];
return await new Promise(resolve => {
child.stdout.on("data", (data) => { // trigger when data is send into the child terminal
console.log(data);
result.push(data);
};
child.on("exit", () => { // trigger when the child process exit after execution
resolve(result);
});
});
}
Edit:
You could use this powershell script from Ben N answer here How to get the current screen resolution on windows via command line? to get the current resolution of your primary screen:
PowerShell-script.ps1
Add-Type #"
using System;
using System.Runtime.InteropServices;
public class PInvoke {
[DllImport("user32.dll")] public static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("gdi32.dll")] public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
}
"#
$hdc = [PInvoke]::GetDC([IntPtr]::Zero)
[PInvoke]::GetDeviceCaps($hdc, 118) # width
[PInvoke]::GetDeviceCaps($hdc, 117) # height
original explanation
It outputs two lines: first the horizontal resolution, then the
vertical resolution.
To run it, save it to a file (e.g. screenres.ps1) and launch it with
PowerShell:
powershell -ExecutionPolicy Bypass .\screenres.ps1
Using this answer in combination of theDavidBarton answer should achieve what you're asking for.
In our project, we use AudioContext to wire up input from a microphone to an AudioWorkletProcessor and out to a MediaStream. Ultimately, this is sent to other peers in a WebRTC call.
If someone loads the page, the audio always sounds fine. But if they connect with a hard-wired microphone like a laptop mic or webcam, then connect a bluetooth device (such as airpods or headphones), then the audio becomes distorted & robotic sounding.
If we tear out all the other code and simplify it, we still have the issue.
bypassProcessor.js
// Basic processor that wires input to output without transforming the data
// https://github.com/GoogleChromeLabs/web-audio-samples/blob/main/audio-worklet/basic/hello-audio-worklet/bypass-processor.js
class BypassProcessor extends AudioWorkletProcessor {
process(inputs, outputs) {
const input = inputs[0];
const output = outputs[0];
for (let channel = 0; channel < output.length; ++channel) {
output[channel].set(input[channel]);
}
return true;
}
}
registerProcessor('bypass-processor', BypassProcessor);
main.js
const microphoneStream = await navigator.mediaDevices.getUserMedia({
audio: true, // have also tried { channelCount: 1 } and { channelCount: { exact: 1 } }
video: false
})
const audioCtx = new AudioContext()
const inputNode = audioCtx.createMediaStreamSource(microphoneStream)
await audioCtx.audioWorklet.addModule('worklet/bypassProcessor.js')
const processorNode = new AudioWorkletNode(audioCtx, 'bypass-processor')
inputNode.connect(processorNode).connect(audioCtx.destination)
Interestingly, I have found if you comment out the 2 audio worklet lines and instead create a simple gain node, then it works fine.
// await audioCtx.audioWorklet.addModule('worklet/bypassProcessor.js')
// const processorNode = new AudioWorkletNode(audioCtx, 'bypass-processor')
const gainNode = audioCtx.createGain()
Also if you simply create the AudioWorkletNode, but don't even connect it to the others, this also reproduces the issue.
I've created a small React app here that reproduces the problem: https://github.com/JacobMuchow/audio_distortion_repro/tree/master
I've tried some options such as detecting when this happens using 'ondevicechange' event, closing the old AudioContext & nodes and recreating everything, but this only works some of the time. If I wait for some time and then recreate it again, it works so I'm worried about some type of garbage collection issue with the processor when attempting this, but that might be beside the point.
I suspect this has something to do with sample rates... when the AudioContext is correctly recreated it switches from 48 kHz to 16 kHz and then it sounds find. But sometimes it is recreated with 48 kHz still and it continues to sound robotic.
Threads on the internet concerning this are incredibly sparse and I'm hoping someone has specific experience with this issue or this API and can point out what I need to do differently.
For Chrome, the problem is very likely https://crbug.com/1090441 that was recently fixed. I think Firefox doesn't have this problem but I didn't check.
During my testing, I have found out that reaching 50 audio output streams (as displayed in chrome://media-internals/ Audio tab) on a single tab causes the audio output to disappear. Does Chrome have a set maximum limit of audio output streams allowed per displayed tab? If so, is there some workaround for that? The Chrome version that I am using is Version 87.0.4280.141.
Whenever we're muting/unmuting the audio(second function below) and adjusting the mic volume(first function below), we create a new audio context. Does too many audio context instances caused the issue?
private setLocalStreamVolume(stream: MediaStream | undefined) {
const context = new AudioContext()
const destination = context.createMediaStreamDestination()
const gainNode = context.createGain()
if (stream) {
for(const track of stream.getTracks()){
const sourceStream = context.createMediaStreamSource(new MediaStream([track]));
sourceStream.connect(gainNode)
gainNode.connect(destination)
gainNode.gain.value = this._micVolume
}
}
return destination.stream
}
export function mixStreams(streams: Iterable<(MediaStream | undefined)>) {
const context = new AudioContext()
const mixedOutput = context.createMediaStreamDestination()
for(const stream of streams)
if(stream)
for(const track of stream.getTracks()){
const sourceStream = context.createMediaStreamSource(new MediaStream([track]));
sourceStream.connect(mixedOutput);
}
return mixedOutput.stream.getTracks()[0]
}
Does too many audio context interactions caused the issue?
Too many AudioContext instances certainly will. In fact, on some systems you can only use a single AudioContext.
I'm not sure what your specific use case is, but you probably only need one AudioContext. All your MediaStreamSourceNodes can live in the same context.
I want to play stream from gstreamer in a web browser.
I played around a with RTP, WebRTC and SDP files but, while VLC was able to connect to stream by simple SDP, browsers were not. I later understood that WebRTC requires secure connection which only complicates things and is not needed for my purposes. I stumbled upon Media Source Extension (MSE) of html5, which seems that it could help, but I'm not able to find some comprehensive tutorial or appropriate specs on how to get gstreamer to stream correct data and later how to play them using MSE. I'm also not sure about latency with using MSE.
So is there a way to play stream from gstreamer in a browser?
Thanks.
Using node webrtc project, I was able to combine output from gstreamer with webrtc call. For gstreamer, there is a project which enables it's use with node gstreamer superficial. So basically, you need to run gstremaer process from node process, which can then control output from gstremaer. On every gstreamer frame there is a callback called which takes the frame and can send it to webrtc calls.
Then an webrtc calls needs to be implemented. There is required some signaling protocol for calls. One side of the call will be the server and another will be the client's browser, instead of two browsers. Then a video track will be created where frames from gstreamer superficial will be pushed.
const { RTCVideoSource } = require("wrtc").nonstandard;
const gstreamer = require("gstreamer-superficial");
const source = new RTCVideoSource();
// This is WebRTC video track which should be used with addTransceiver see below
const track = source.createTrack();
const frame = {
width: 1920,
height: 1080,
data: null
};
const pipeline = new gstreamer.Pipeline("v4l2src ! videorate ! video/x-raw,format=YUY2,width=1920,height=1080,framerate=25/1 ! videoconvert ! video/x-raw,format=I420 ! appsink name=sink");
const appsink = pipeline.findChild("sink");
const pull = function() {
appsink.pull(function(buf, caps) {
if (buf) {
frame.data = new Uint8Array(buf);
try {
source.onFrame(frame);
} catch (e) {}
pull();
} else if (!caps) {
console.log("PULL DROPPED");
setTimeout(pull, 500);
}
});
};
pipeline.play();
pull();
// Example:
const useTrack = SomeRTCPeerConnection => SomeRTCPeerConnection.addTransceiver(track, { direction: "sendonly" });
I'm creating a simple program for reading text file on the Windows Phone. I decided to make it a Universal Windows Platform (UWP) App.
In the app, I have a very simple MessageDialog, with three options, Yes, No, Cancel. It works perfectly on the Desktop and in the Simulator. However, when testing with the actual device, the ShowAsync method fails with the message: "Value does not fall in the expected range".
This only happens if there are more than two commands registered in the dialog. Does the MessageDialog class really supports up to three commands - as the documentation suggests - or is this only applying for UWP Apps running on Desktop devices?
At the moment, there is a clear statement in the docs:
The dialog has a command bar that can support up to 3 commands in desktop apps, or 2 commands in mobile apps.
Sad but true: on mobiles, there are two commands only. Need more? Use ContentDialog instead.
It looks like the documentation is missing information about Mobile (and really the API should do a better job here).
For Mobile, if you hit the Back key you get a null return value, so you can do this (not recommended coding pattern, but best I can think of):
async Task Test()
{
const int YES = 1;
const int NO = 2;
const int CANCEL = 3;
var dialog = new MessageDialog("test");
dialog.Commands.Add(new UICommand { Label = "yes", Id = YES });
dialog.Commands.Add(new UICommand { Label = "no", Id = NO });
// Ugly hack; not really how it's supposed to be used.
// TODO: Revisit if MessageDialog API is updated in future release
var deviceFamily = AnalyticsInfo.VersionInfo.DeviceFamily;
if (deviceFamily.Contains("Desktop"))
{
dialog.Commands.Add(new UICommand { Label = "cancel", Id = CANCEL });
}
// Maybe Xbox 'B' button works, but I don't know so best to not do anything
else if (!deviceFamily.Contains("Mobile"))
{
throw new Exception("Don't know how to show dialog for device "
+ deviceFamily);
}
// Will return null if you press Back on Mobile
var result = await dialog.ShowAsync();
// C# 6 syntactic sugar to avoid some null checks
var id = (int)(result?.Id ?? CANCEL);
Debug.WriteLine("You chose {0}", id);
}