Skip to main content
Version: 0.30.0

Custom sources in React Native Mobile

note

This guide is exclusively for Mobile (React Native) applications.

@fishjam-cloud/react-native-client provides two hooks for publishing custom sources. Your app must be wrapped in FishjamProvider.

Publish a stream​

Set the stream on a stable source ID; set null to unpublish. The cleanup function keeps the source in sync with the component lifecycle.

export function usePublishStream(stream: MediaStream | null) { const { setStream } = useCustomSource("my-custom-source"); useEffect(() => { if (!stream) return; setStream(stream); return () => { setStream(null); }; }, [stream, setStream]); }
When tracks are actually published

Tracks are sent to Fishjam only while the peer is connected to a room. A stream set before joining is queued and published as soon as the connection is established, and pending sources are re-published automatically after a reconnect. Calls to setStream are serialized internally, so replacing one stream with another in quick succession is safe.

important

Custom sources are shared by ID: every useCustomSource call with the same source ID returns the same state, so multiple components can control and read one source.

Show a self-view​

The stream returned by useCustomSource renders like any other stream:

export function SelfView() { const { stream } = useCustomSource("my-custom-source"); if (!stream) return null; return ( <RTCView mediaStream={stream} style={{ height: 200, width: 200 }} objectFit="cover" /> ); }

Remove the source​

const { setStream } = useCustomSource("my-custom-source"); // ... await setStream(null);

Removing a source unpublishes its tracks but does not stop them; the producer of the stream owns the tracks and should stop them itself.

Publish custom audio​

React Native has no equivalent of the web's captureStream() or Web Audio, so audio your app generates does not arrive as a MediaStream. The useCustomAudioSource hook covers this case: it creates a custom audio track, publishes it under its source ID, and cleans it up. Your code pushes raw PCM samples with pushAudioSamples whenever your source produces them, for example a synthesizer, a decoder, or react-native-audio-api:

const { startStreaming, stopStreaming, track } = useCustomAudioSource({ sampleRateHz: 48000, channelCount: 1, }); await startStreaming(); // Whenever your source produces audio: onAudioChunk((chunk) => { if (track) pushAudioSamples(track, chunk); }); // When done: await stopStreaming();

The published track behaves like a live microphone: pushes are re-paced into a continuous stream, and pauses in pushing play as silence. The device microphone is not involved; publishing does not trigger the recording permission, and microphone capture stays with useMicrophone.

Notes on pushing samples:

  • Push any chunk size; the native layer re-frames and paces it. Float32Array samples are expected in [-1, 1]; Int16Array is taken as-is. Stereo tracks take interleaved samples.
  • The track handle is plain and worklet-serializable, so samples can be pushed straight from a worklet without hopping through the JS thread.
  • Pushed-but-not-yet-sent audio is buffered up to maxBufferedDurationMs and drains in real time, so a long clip (for example a text-to-speech utterance) can be handed over in one call.

Options​

OptionDefaultDescription
sourceId"customAudioSource"Source ID the track is published under. Give each instance that streams at the same time its own ID.
sampleRateHz48000Sample rate of the PCM you push; a positive multiple of 100. Any rate is resampled downstream.
channelCount11 for mono, 2 for interleaved stereo.
maxBufferedDurationMs60000How much pushed-but-not-yet-sent audio to hold, in milliseconds, before the oldest is dropped.

Custom audio tracks require the New Architecture. On the old architecture startStreaming does not throw; it fails internally and the failure surfaces in the hook's error field.

Receiving on other peers​

Custom tracks arrive in the customVideoTracks and customAudioTracks arrays of each peer returned by usePeers. See Receiving custom tracks.