Skip to main content
Version: Next

Custom sources

Custom sources allow you to publish media that doesn't come straight from the camera, microphone or screen share, for example a game render loop or an ML-processed camera feed. Any content you can produce as video frames or audio samples can be published.

important

If you only wish to send plain camera, microphone or screen share output through Fishjam, then you most likely should refer to Streaming media, Managing devices and Screen sharing instead of this section.

Every custom source follows the same contract: you register a MediaStream under a stable source ID with the useCustomSource hook (Web ยท React Native), and Fishjam publishes its video track as a customVideo track and its audio track as a customAudio track. Other peers receive them like any other track.

Choose a guideโ€‹

GuidePlatformUse it when
Web๐ŸŒ WebYou have a MediaStream from a canvas, a media element, Web Audio, or a library like Smelter
React Native๐Ÿ“ฑ MobileYou have a React Native MediaStream to publish, or want to publish app-generated audio
Vision Camera๐Ÿ“ฑ MobileYou want to publish a VisionCamera feed, optionally running frame processors on it
WebGPU effects๐ŸŒ Web ยท ๐Ÿ“ฑ MobileYou want to draw your own shaders, overlays or effects into the published video
Low-level frame API๐Ÿ“ฑ MobileYou produce video frames yourself, e.g. from a native pipeline or your own renderer
Platform support

On the web you can publish both video and audio from any MediaStream. In React Native, custom video comes from the video guides in this section, and custom audio from the useCustomAudioSource hook, which publishes PCM samples your app pushes.

Receiving custom tracksโ€‹

Custom tracks published by other peers show up in the usePeers hook (Web ยท React Native), bucketed into the customVideoTracks and customAudioTracks arrays of each peer.

import { usePeers } from "@fishjam-cloud/react-client"; export function RemoteCustomVideos() { const { remotePeers } = usePeers(); return ( <ul> {remotePeers.map((peer) => ( <li key={peer.id}> {peer.customVideoTracks.map((track) => ( <VideoRenderer key={track.trackId} stream={track.stream} /> ))} </li> ))} </ul> ); }

Remote customAudioTracks are played by attaching each track's stream to an <audio> element, the same way as microphone tracks.

Guides in this sectionโ€‹

Further readingโ€‹