npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

shared-microphone

v0.1.3

Published

Open the microphone once and share its audio frames with every consumer that needs them

Readme

shared-microphone

Opens the microphone once and shares its audio frames with everything that needs them.

When two or more things need the same audio — wake word detection and transcription, say — having each of them call getUserMedia means the device is reopened every time you switch between them. Reopening takes hundreds of milliseconds on some hardware, and whatever is said during that window is lost entirely. Wake word matching falls below its threshold when the start of an utterance is clipped, so this shows up as the wake word simply not firing rather than as a delay.

  • No runtime dependencies
  • One device, however many consumers. The same request never opens a second one
  • One listener throwing does not stop delivery to the others
  • Works around the device quirks listed below, each found on real hardware

Install

npm install shared-microphone

Usage

import { micBus } from "shared-microphone";

// Start receiving. You can subscribe before the microphone is open.
const stopListening = micBus.subscribe((samples, sampleRate) => {
  // samples is single-channel Float32 in the -1..1 range
  recognizer.write(samples, sampleRate);
});

// Open the microphone. Pass null for the default device.
await micBus.open(selectedDeviceId);

// Stop receiving. The microphone stays open.
stopListening();

// Release the microphone — handing it to a call, or when the tab goes to the background.
micBus.close();

subscribe and open are independent. Starting or stopping a recording never touches the device, so moving between wake word detection and transcription causes no reopen.

Several consumers

micBus.subscribe(wakeWordDetector.write);
micBus.subscribe(transcriber.write);
await micBus.open();
// Both receive the same frames from a single device.

Surfacing what went wrong

import { createMicBus } from "shared-microphone";

const bus = createMicBus({
  onWarning: (warning) => {
    if (warning.type === "device-fallback") {
      toast.warning("That microphone is unavailable. Using the default one.");
    }
  },
});

| type | Meaning | | ---- | ---- | | device-fallback | The requested device could not be opened, so the default one is in use | | listener-failed | A listener threw. Delivery to the others continued | | sink-not-silenced | The output sink could not be silenced. It still works |

Options

Pass them to createMicBus(options). The exported micBus is createMicBus().

| Option | Default | Meaning | | ---- | ---- | ---- | | frameSize | 4096 | Samples per frame. Roughly every 85 ms at 48 kHz | | getUserMedia | navigator.mediaDevices.getUserMedia | Replaces the acquisition call | | audioContext | global | Creates the AudioContext, falling back to webkitAudioContext | | onWarning | — | Receives the events in the table above |

Device quirks handled here

Each of these was found on real hardware and is built into the implementation.

  • The output sink is silenced. While Bluetooth is connected the AudioContext routes its output to the headset, and some Android devices cannot establish full-duplex audio alongside the microphone input. When that happens the render thread stalls and no frames arrive at all. setSinkId({ type: "none" }) avoids opening hardware output. iOS Safari has no setSinkId, so it is called only where it exists
  • A context that starts suspended is resumed. On Android Chrome the context starts suspended when a getUserMedia await comes first, and without a resume no frames arrive
  • Only "something else is using it" failures are retried. AbortError, InvalidStateError, NotReadableError and TrackStartError get one retry after 250 ms. Missing permission and missing hardware give the same answer however many times you ask, so those are passed straight through
  • A failure during wiring releases the microphone it acquired. Otherwise an open device nobody receives from is stranded, and the next open cannot acquire one
  • Concurrent opens collapse into one. Letting them through opens a second device

Known limitations

  • Built on ScriptProcessorNode. It is deprecated, but it is the only path confirmed to work everywhere including iOS Safari, so it ships as-is for now. An AudioWorklet path is under consideration
  • No audio processing. Resampling and format conversion belong to the consumer

License

MIT