@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/voiceIt 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
@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.
