# Hooks

Source: https://docs.gryt.chat/docs/build/voice-hooks

The API you actually call

Every hook here is a singleton. Its body runs once inside `<VoiceSingletonHooks />` and every caller reads the same value, so calling `useMicrophone()` in five components doesn't open five microphones.

## useSFU

The call itself.

```typescript
const {
  connect, disconnect, isConnected, isConnecting,
  connectionState, connectionError,
  streams, streamSources, videoStreams,
  addVideoTrack, removeVideoTrack,
  addScreenVideoTrack, removeScreenVideoTrack,
  addScreenAudioTrack, removeScreenAudioTrack,
  currentServerConnected, currentChannelConnected,
} = useSFU();
```

`connect(channelId, esportsMode?, maxBitrate?)` asks the room coordinator for access, probes the SFU URLs it gets back, and brings the peer connection up. `disconnect(onDisconnect?)` tears it down.

`connectionState` is a `SFUConnectionState`: `disconnected`, `requesting_access`, `connecting`, `connected`, `reconnecting` or `failed`.

`connectionError` says why a connection ended when the engine has something to say. `"reconnect-failed"` means it retried and gave up. Null covers everything else, including an ordinary hang-up. Without it, `disconnected` alone can't tell a dropped call from somebody pressing leave, which left embedders guessing.

`streams` is keyed by id and carries `{ stream, isLocal, kind }`. `streamSources` gives you the Web Audio nodes per remote stream, a gain and an analyser, which is what a speaking indicator or a per-person volume slider reads. `videoStreams` is the plain map of camera and screen streams.

The video and screen track methods take a `MediaStreamTrack`, its `MediaStream` and an optional preferred codec, and handle the renegotiation.

There are also optional escape hatches for debugging and stats: `getPeerConnection`, `getScreenSenderTrackId`, `getCameraSenderTrackId`, `getScreenVideoSender` and `activeSfuUrl`.

## useMicrophone

```typescript
const {
  microphoneBuffer, isLoaded, devices, getDevices,
  micUnavailable, isTransmitting, getVisualizerData, getGateLevel,
  setPushToTalkActive, addHandle, removeHandle,
} = useMicrophone(shouldAccess);
```

`microphoneBuffer` is the whole audio graph: the raw and processed analysers, the gain nodes, the noise gate worklet, the RNNoise node, the AGC nodes and the compressor. A meter reads `analyser` or `finalAnalyser` from it.

`micUnavailable` is `"denied"`, `"no-device"`, `"failed"`, or null once a microphone is live. The three cases want different advice, which is why they aren't one boolean. Joining a voice channel still works in this state on purpose, because listening without a microphone is useful, so this is what lets the UI say so out loud instead of looking healthy while nobody can hear you.

`isTransmitting` is true while audio is actually leaving the client, and `getGateLevel()` returns the level the noise gate is deciding on. Both are null when the gate worklet is unavailable.

`setPushToTalkActive(active)` opens and closes the transmit gate. The embedder owns the trigger, a key on the desktop or a held button on a phone, and calls this. It does nothing in voice-activity mode.

### Handles

`addHandle(id)` and `removeHandle(id)` are how a component says it needs the microphone open. The microphone stays live while at least one handle is held and is released when the last one goes, which is what stops the OS showing your app listening while it sits idle. `useHandles` exposes the same registry directly.

## useCamera

```typescript
const {
  cameraStream, cameraEnabled, setCameraEnabled,
  cameraError, retryCamera, devices, getDevices,
} = useCamera();
```

Quality and fps come from `VoiceConfig.camera` rather than from arguments. `QUALITY_CONSTRAINTS` maps a `CaptureQuality` to width and height, and `CAMERA_FPS_OPTIONS` is the list of allowed frame rates.

## useScreenShare

```typescript
const {
  startScreenShare, stopScreenShare, screenShareActive,
  screenVideoStream, screenAudioStream,
  nativeAudioActive, nativeScreenCaptureAvailable,
  nativeEncodedCodec, subscribeEncodedFrames,
} = useScreenShare();
```

`startScreenShare(withAudio, sourceId?)` takes the source id from whatever picker the embedder shows.

The native fields only matter where the host provides native screen capture. `nativeScreenCaptureAvailable` says whether it's there, `nativeEncodedCodec` is `"h264"`, `"hevc"` or null, and `subscribeEncodedFrames` hands you pre-encoded NAL frames from the hardware encoder. `nativeAudioActive` is true when the OS is capturing audio directly, which is the case where no phase cancellation is needed.

`STANDARD_FPS_OPTIONS` and `EXPERIMENTAL_FPS_OPTIONS` are the two frame rate lists, and `estimateBitrate(quality, fps)` returns a bitrate for a combination, or null.

## useSpeakers

```typescript
const { devices, getOutputDevices, applyOutputDevice, remoteBusNode } = useSpeakers();
```

`applyOutputDevice(deviceId)` calls `setSinkId` on the shared audio context. `remoteBusNode` is the gain node everyone else's audio is mixed into.

## Reading the configuration back

`useVoiceConfig` returns the `VoiceConfig` the provider was given, `useVoiceTarget`
returns the current `VoiceTarget` or null, and `useVoiceCallbacks` returns the
callbacks the engine reports through. All three read from `VoiceConfigProvider`
and are there for code sitting between the provider and the engine that needs the
same values without threading them down by hand.

## Native capture, on the desktop

Two hooks wrap the native binaries the desktop app ships. Both are no-ops
elsewhere: `available` comes back `false` and nothing else does anything.

`useNativeAudioCapture` returns `{ available, active, stream, start, stop }`.
`start(audioContext, sourceId?)` captures one application's audio when given a
window source id, and otherwise everything except Gryt's own process tree, which
is the part a browser can't do. `stream` is a `MediaStream` you can hand
straight to WebRTC.

`useNativeScreenCapture` returns `{ available, active, videoStream, encodedCodec,
subscribeEncodedFrames, start, stop }`. `start(monitorIndex, fps, maxWidth?,
maxHeight?, bitrate?, codec?)` drives the hardware encoder, `encodedCodec` reports
`"h264"`, `"hevc"` or null, and `subscribeEncodedFrames` hands you pre-encoded NAL
frames so they can be forwarded without a decode and re-encode. It returns its own
unsubscribe function.

<Callout type="warn" title="Two exported types share these names">
`NativeAudioCapture` and `NativeScreenCapture` each name two different interfaces
in this package: the shapes above, and the host-side APIs in `host/`. The host
ones win at the package root, so `import type { NativeAudioCapture } from "@gryt/voice"`
gives you the host interface rather than the hook's return. Until that's sorted
out, let the hook's return type be inferred instead of annotating it.
</Callout>

## The lower level

`useSFUStreams` is the stream bookkeeping `useSFU` uses internally: it takes the
stream maps and their setters and keeps them in step as tracks arrive and leave.
`usePipelineControls` is the same idea for audio, taking the microphone buffer,
the audio context and every setting the chain reads, and pushing changes into the
running graph.

Neither is needed to make a call. They're exported for an embedder rebuilding
`useSFU` or the microphone pipeline rather than using them.

## Smaller pieces

`useSharedAudioContext` gives you the one `AudioContext` everything runs on.

`useDeviceEnumeration` lists audio input devices and re-lists them on `devicechange`, prompting for permission if the labels come back empty.

`usePushToTalkGate` is the gate itself, for embedders wiring their own trigger.

`useVoiceLatency` returns a `LatencyBreakdown`, and `useVideoStats` returns inbound and outbound video stats from `getStats`.

`getCurrentVolume`, `getVolumeDb`, `volumeToLevel` and `isSpeaking` are plain functions over an analyser, for meters and speaking indicators.

`detectFraming` and `CENTRED` are the face-framing helpers, which run on the local machine and produce two numbers rather than sending video anywhere.

`selectBestSfuUrl`, `getCachedSfuUrl` and `warmSfuSelection` are the SFU probing, exposed so an embedder can warm the selection before somebody presses join.

`getIsBrowserSupported` tells you whether device enumeration is available at all.
