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

@gryt/voice

v0.4.4

Published

Gryt's voice engine: signalling, ICE, tracks and audio, with web and React Native adapters.

Downloads

3,289

Readme

npm install @gryt/voice

It was pulled out of the Gryt desktop client so the desktop app, the web app and the mobile app can share one implementation instead of keeping three in step.

It's on npm, and it's what the Gryt clients run: the desktop and web client moved onto it in GRYT-341, and the mobile app builds on it too. No version numbers here on purpose, since they go stale faster than this file gets read.

What it does and doesn't decide

The engine is told where to connect and what to capture, and reports what happened. It doesn't know which server is on screen, which servers exist, or whether one was removed. That sounds like a small distinction and it's the one that decides what belongs here.

Anything the engine can't work out for itself arrives through one of five seams:

VoiceConfig is what the person has chosen: microphone, camera and screen settings, input mode, whether noise suppression is on. It arrives through VoiceConfigProvider and changes while a call is running, which is why it is a React context rather than something set once at startup.

VoiceHost answers two questions about the platform, and they're separate on purpose. hasNativeCapture() asks whether native capture exists. allowsInsecureTransport() asks whether a plain ws:// connection to a private address is allowed. The client used to ask isElectron() for both, which gives the right answer on the desktop by coincidence: React Native has native capture and no mixed-content rule, so folding them together would quietly break LAN servers on a phone.

SfuTransport carries offers, answers and candidates. It's generic WebRTC, so an embedder can move those messages over anything it likes.

RoomCoordinator handles asking to join a channel and telling the server what is being published. That part is Gryt's rules rather than WebRTC's, since the server decides who may enter and how many fit.

VoicePlatform covers capture, playback and peer construction. The web implementation is the code that moved out of the client; the native one uses react-native-webrtc and react-native-audio-api.

What it won't do for you

No sounds, no toasts, no notifications. The engine reports state and the app decides what that means. A refused room request comes back as a RoomAccess with a reason and a retry delay, and whether that deserves a toast isn't the engine's call.

That isn't minimalism for its own sake. Every one of those decisions differs between a desktop app, a browser tab and a phone, and an SDK that makes them for you is one you end up fighting.

Noise suppression is deliberately not ported

The web adapter keeps RNNoise. The native one doesn't, and shouldn't.

Native WebRTC ships echo cancellation, noise suppression and automatic gain control, and phones have hardware echo cancellation on top. The RNNoise worklet exists because noise suppression in browsers is weak, and that reason doesn't survive the move to a phone. Adding it back would spend battery and CPU duplicating something the platform already does.

Push-to-talk is split in half

The engine owns the gate, which is the part that opens and closes the transmit gain. The app owns the trigger.

A key isn't the only way to ask to talk. The desktop listens for a keypress and an Electron global shortcut; a phone holds a button on a screen. So the app calls setPushToTalkActive and the engine decides what that means, including what happens when someone unmutes mid-press.

Deafen works on a phone. Volume doesn't

Both are one field in VoiceConfig.audio, and only one of them survives the move to native, so an embedder shouldn't offer them as a pair.

On the web, remote audio goes through a gain node per stream, and deafen is that gain set to zero. There's no AudioContext on a phone, so there's no graph and no gain node — react-native-webrtc plays a received track itself. Deafen falls back to enabled = false on each remote audio track, which is receiver-side: libwebrtc drops the decoded audio rather than asking the sender to stop, so nobody else's call changes.

outputVolume has no such fallback. A number between zero and one needs the graph, and approximating it with on and off would be a slider that snaps. It stays a web control, and an app that draws one on a phone is drawing something nothing reads.

Not bundled, on purpose

The build is unbundled output, one file in for one file out. Metro picks between .native.ts and .web.ts per file, and a bundle has no files left to pick between, so bundling would rule out the platform split this package exists for.

Relative imports get their .js extensions added after compilation rather than written into the source, which keeps the source readable and the output loadable outside a bundler.

Issues

Please report bugs and request features in the main Gryt repository.

Sponsors

What sponsoring pays for, the tiers, and everyone who has sponsored: gryt.chat/sponsors. To sponsor: GitHub Sponsors.

The list itself lives in the Gryt README, in one place rather than ten, so it cannot fall out of step across repositories.

License

AGPL-3.0 — Part of Gryt

@gryt/ui is the exception in this org, and deliberately so: it's generic components with nothing of Gryt in them, and copyleft there would rule out most of the people who might use it.

This isn't that. Signalling, ICE handling, track management and the connection state machine are the product rather than scaffolding around it. Copyleft here means somebody running a modified Gryt voice engine as a service publishes their changes, which is the same reason the apps are AGPL and applies more strongly here than it does to a button.

It's still yours to embed, self-host and modify. The licence only bites for running a modified version as a closed service.