Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I record audio from a speaker (and not a microphone)?

I am trying to record audio from a speaker only using TypeScript or JavaScript.

Expected: it is recording audio from a speaker only, not a microphone.

Actual: it is recording audio from a microphone

What is the problem in my code?

let audioChunks: any = [];
navigator.mediaDevices.getUserMedia({ audio: true })
  .then(stream => {
    audioChunks = [];
    let rec = new MediaRecorder(stream);

    rec.ondataavailable = e => {
      audioChunks.push(e.data);
      if (rec.state == "inactive") {
        let blob = new Blob(audioChunks, { type: 'audio/x-mpeg-3' });
        downloadFile(blob, "filename.wav");
      }
    }

    rec.start();

    setTimeout(() => {
      rec.stop();
    }, 10000);
  })
  .catch(e => console.log(e));
like image 588
Praveen saxena Avatar asked Dec 05 '25 15:12

Praveen saxena


1 Answers

Circa 2025 you can use getDisplayMedia(). See below.

Capturing speakers has been possible for years on Firefox. Not on Chromium based browsers on Linux, by conventional means, for 5 years.

Previously, at least on Linux on Chromium-based browsers, it was necessary to set an input device to an output device. See Chromium does not support capture of monitor devices by default #17 for some history and workarounds.

In brief, on Linux, with Pulse Audio, something like this

pactl load-module module-remap-source \
  master=@DEFAULT_MONITOR@ \
  source_name=speakers source_properties=device.description=Speakers \
&& pactl set-default-source speakers

the you can do something like this

navigator.mediaDevices.getUserMedia({audio: true})
.then(async stream => {
  const [track] = stream.getAudioTracks();
  const devices = await navigator.mediaDevices.enumerateDevices();
  const device = devices.find(({label}) => label === "speakers");
  if (track.getSettings().deviceId === device.deviceId) {
    return stream;
  } else {
    track.stop();
    console.log(devices, device);
    return navigator.mediaDevices.getUserMedia({audio: {deviceId: {exact: device.deviceId}}});
  }
})
.then(stream => {
  const recorder = new MediaRecorder(stream);
  // Do stuff with output of speakers

taking that a step further we can capture the audio output of specific devices,

pactl load-module module-combine-sink \
sink_name=Web_Speech_Sink slaves=$(pacmd list-sinks | grep -A1 "* index" | grep -oP "<\K[^ >]+") \
sink_properties=device.description="Web_Speech_Stream" \
format=s16le \
channels=1 \
rate=22050
pactl load-module module-remap-source \
master=Web_Speech_Sink.monitor \
source_name=Web_Speech_Monitor \
source_properties=device.description=Web_Speech_Output
pactl move-sink-input $(pacmd list-sink-inputs | tac | perl -E'undef$/;$_=<>;/speech-dispatcher-espeak-ng.*?index: (\d+)\n/s;say $1') Web_Speech_Sink
navigator.mediaDevices.getUserMedia({audio: true})
.then(async stream => {
  const [track] = stream.getAudioTracks();
  const devices = await navigator.mediaDevices.enumerateDevices();
  const device = devices.find(({label}) => label === 'Web_Speech_Output');
  if (track.getSettings().deviceId === device.deviceId) {
    return stream;
  } else {
    track.stop();
    console.log(devices, device);
    return navigator.mediaDevices.getUserMedia({audio: {deviceId: {exact: device.deviceId}}});
  }
})
.then(stream => {
  const recorder = new MediaRecorder(stream);
  recorder.ondataavailable = e => console.log(URL.createObjectURL(e.data));
  const synth = speechSynthesis;
  const u = new SpeechSynthesisUtterance('test');
  u.onstart = e => {
    recorder.start();
    console.log(e);
  }
  u.onend = e => {
    recorder.stop();
    recorder.stream.getTracks().forEach(track => track.stop());
    console.log(e);
  }
  synth.speak(u);
});

This repository was created by myself to create and demonstrate different ways to capture system audio in the brower captureSystemAudio.

2025

It's possible to finally use getDisplayMedia() to capture speakers, without using extensions or system audio settings for workarounds; see the gist I wrote here Finally possible to capture monitor devices in Chromium and Chrome on Linux. Something like this

// It is finally possible to capture speechSynthesis.speak() on Chromium and Chrome
// Enable Speech Dispatcher, PulseAudio loopback for screen capture, disable default WebRTC input volume adjustment from 100% to 8%
// chrome --enable-speech-dispatcher --enable-features=PulseaudioLoopbackForScreenShare --disable-features=WebRtcAllowInputVolumeAdjustment
// Still have to manually select share system audio in picker with systemAudio set to "include"
// https://issues.chromium.org/issues/40155218
let stream = await navigator.mediaDevices.getDisplayMedia({
  // We're not going to be using the video track
  video: {
    width: 0,
    height: 0,
    frameRate: 0,
    displaySurface: "monitor"
  },
  audio: {
    suppressLocalAudioPlayback: false,
    // Speech synthesis audio output is generally 1 channel
    channelCount: 2,
    noiseSuppression: false,
    autoGainControl: false,
    echoCancellation: false
  },
  systemAudio: "include",
  // Doesn't work for Tab capture
  // preferCurrentTab: true
});

function log(e, ...args) {
  if (e?.target) {
    console.log(e.target.constructor.name, e.type);
  } else {
    console.log(...args);
  }
};

let [videoTrack] = stream.getVideoTracks();

videoTrack.stop();

let [audioTrack] = stream.getAudioTracks();

log(null, audioTrack.constructor.name, audioTrack.kind, audioTrack.getSettings().deviceId);

let recorder = new MediaRecorder(stream);

recorder.onstart = log;

recorder.onstop = (e) => {
  recorder.stream.getTracks().forEach((track) => track.stop());
  log(e);
};

recorder.ondataavailable = (e) => {
  console.log(URL.createObjectURL(e.data));
  log(e);
};

let utterance = new SpeechSynthesisUtterance(`von Braun believed in testing. I cannot
emphasize that term enough – test, test,
test. Test to the point it breaks.
- Ed Buckbee, NASA Public Affairs Officer, Chasing the Moon`);

utterance.onstart = (e) => {
  recorder.start();
  log(e);
};

utterance.onend = (e) => {
  recorder.stop();
  log(e);
};

globalThis.speechSynthesis.speak(utterance);
like image 74
guest271314 Avatar answered Dec 08 '25 06:12

guest271314



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!