Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cloudflare/partykit/llms.txt

Use this file to discover all available pages before exploring further.

partytracks is a client/server library for real-time audio and video in web applications. It wraps Cloudflare Realtime SFU with an Observable-based API that handles WebRTC complexity — peer connection recovery, hardware changes, network switches, device selection, and track lifecycle — so your application code does not need to.

Installation

npm install partytracks

Class: PartyTracks

Import from partytracks/client. PartyTracks handles all WebRTC negotiation through push and pull methods backed by Observables.
import { PartyTracks } from "partytracks/client";

const partyTracks = new PartyTracks();

Constructor

new PartyTracks(config?: PartyTracksConfig)
config
PartyTracksConfig
Optional configuration. See PartyTracksConfig below.

Methods

push(sourceTrack$, options?)

Push a local track to the Realtime SFU. Returns an Observable of TrackMetadata that you share with other peers so they can pull the track. If sourceTrack$ emits a new track, it replaces the old one on the transceiver. If the peer connection is disrupted, the track is automatically re-pushed and new TrackMetadata is emitted.
push(
  sourceTrack$: Observable<MediaStreamTrack>,
  options?: {
    sendEncodings$?: Observable<RTCRtpEncodingParameters[]>;
  }
): Observable<TrackMetadata>
sourceTrack$
Observable<MediaStreamTrack>
required
An Observable emitting the local MediaStreamTrack to broadcast. Use getMic().broadcastTrack$ or getCamera().broadcastTrack$ here.
options.sendEncodings$
Observable<RTCRtpEncodingParameters[]>
Optional Observable of RTP encoding parameters (e.g. for simulcast). Initial values are applied on push; subsequent emissions update the encodings.
Returns: Observable<TrackMetadata> — send the emitted metadata to other peers so they can call pull.

pull(trackData$, options?)

Pull a remote track from the Realtime SFU. Returns an Observable of MediaStreamTrack. If trackData$ emits new metadata (e.g. because the remote peer re-pushed) or if the peer connection is disrupted, the track is automatically re-pulled.
pull(
  trackData$: Observable<TrackMetadata>,
  options?: {
    simulcast?: {
      preferredRid$: Observable<string | undefined>;
    };
  }
): Observable<MediaStreamTrack>
trackData$
Observable<TrackMetadata>
required
An Observable of TrackMetadata received from the remote peer.
options.simulcast.preferredRid$
Observable<string | undefined>
For simulcast tracks, an Observable that emits the preferred RID (layer identifier) to receive.
Returns: Observable<MediaStreamTrack> — attach to a MediaStream or pass to createAudioSink.

Properties

peerConnection$
Observable<RTCPeerConnection>
Emits the active RTCPeerConnection. When the connection is disrupted, a new one is created and emitted automatically.
session$
Observable<{ peerConnection: RTCPeerConnection; sessionId: string }>
Emits the active peer connection together with its associated session ID. Flows from peerConnection$; emits a new pair when the connection changes.
transceiver$
Observable<RTCRtpTransceiver>
Emits each RTCRtpTransceiver as it is added to the peer connection.
peerConnectionState$
Observable<RTCPeerConnectionState>
Emits the current RTCPeerConnection.connectionState value whenever it changes.
history
History<ApiHistoryEntry>
A rolling log of API calls made by PartyTracks. Useful for debugging. Capped at maxApiHistory entries (default 100).

PartyTracksConfig

Optional configuration object for the PartyTracks constructor.
interface PartyTracksConfig {
  apiExtraParams?: string;
  iceServers?: RTCIceServer[];
  prefix?: string;
  maxApiHistory?: number;
  headers?: Headers;
}
apiExtraParams
string
Additional query parameters appended to every API request, e.g. "userId=123&roomId=456".
iceServers
RTCIceServer[]
Custom ICE servers. If omitted, ICE servers are fetched from the /partytracks/generate-ice-servers endpoint on your proxy.
prefix
string
The pathname prefix your proxy uses, e.g. "/api/partytracks". Provide a full URL (e.g. "https://api.example.com/partytracks") for cross-domain connections.
maxApiHistory
number
default:"100"
Maximum number of ApiHistoryEntry records to retain in history.
headers
Headers
Custom headers appended to every API request (e.g. for authentication).
Configuration examples:
// Default (same domain, /partytracks/* proxy)
const partyTracks = new PartyTracks();

// Custom path prefix
const partyTracks = new PartyTracks({ prefix: "/api/partytracks" });

// Cross-domain
const partyTracks = new PartyTracks({
  prefix: "https://api.example.com/partytracks"
});

// With auth headers and custom ICE servers
const partyTracks = new PartyTracks({
  iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
  apiExtraParams: "userId=123&roomId=456",
  headers: new Headers({ Authorization: "Bearer your-token" }),
  maxApiHistory: 50
});

getMic(options?)

Returns a MediaDevice representing the user’s microphone. By default the mic source is kept alive (retainIdleTrack: true) for “talking while muted” detection.
import { getMic } from "partytracks/client";

const mic = getMic(options?: MediaDeviceOptions);

getCamera(options?)

Returns a MediaDevice representing the user’s camera.
import { getCamera } from "partytracks/client";

const camera = getCamera(options?: MediaDeviceOptions);

MediaDeviceOptions

Both getMic and getCamera accept the same options:
interface MediaDeviceOptions {
  broadcasting?: boolean;
  retainIdleTrack?: boolean;
  transformations?: ((track: MediaStreamTrack) => Observable<MediaStreamTrack>)[];
  activateSource?: boolean;
  constraints?: Omit<MediaTrackConstraints, "deviceId" | "groupId">;
  onDeviceFailure?: (device: MediaDeviceInfo) => void;
}
broadcasting
boolean
default:"false"
Whether the track should start broadcasting immediately.
retainIdleTrack
boolean
Keep the source track active even when there are no subscribers. Defaults to true for mic, false for camera.
transformations
((track: MediaStreamTrack) => Observable<MediaStreamTrack>)[]
Initial set of track transformations (e.g. noise suppression, blur).
activateSource
boolean
default:"true"
Whether isSourceEnabled should be true from the start.
constraints
Omit<MediaTrackConstraints, 'deviceId' | 'groupId'>
Constraints passed to navigator.mediaDevices.getUserMedia(). deviceId and groupId are excluded because partytracks tries all available devices automatically when the preferred device is unavailable.
onDeviceFailure
(device: MediaDeviceInfo) => void
Callback invoked when an individual device fails to produce a healthy track. Use this to surface warnings to the user or deprioritize the device.

MediaDevice interface

Both getMic and getCamera return a MediaDevice:
interface MediaDevice {
  permissionState$: Observable<SafePermissionState>;
  devices$: Observable<MediaDeviceInfo[]>;
  activeDevice$: Observable<MediaDeviceInfo>;
  setPreferredDevice: (device: MediaDeviceInfo) => void;
  addTransform: (transform: (track: MediaStreamTrack) => Observable<MediaStreamTrack>) => void;
  removeTransform: (transform: (track: MediaStreamTrack) => Observable<MediaStreamTrack>) => void;
  isBroadcasting$: Observable<boolean>;
  startBroadcasting: () => void;
  stopBroadcasting: () => void;
  toggleBroadcasting: () => void;
  localMonitorTrack$: Observable<MediaStreamTrack>;
  broadcastTrack$: Observable<MediaStreamTrack>;
  isSourceEnabled$: Observable<boolean>;
  enableSource: () => void;
  disableSource: () => void;
  toggleIsSourceEnabled: () => void;
  error$: Observable<Error>;
}
permissionState$
Observable<SafePermissionState>
Emits the browser permission state for this device ("granted", "denied", "prompt").
devices$
Observable<MediaDeviceInfo[]>
Emits the list of available devices of this kind. Use to populate a device-selection UI.
activeDevice$
Observable<MediaDeviceInfo>
Emits the currently active device, the preferred device (if set), or the default device.
setPreferredDevice
(device: MediaDeviceInfo) => void
Sets and persists (via localStorage) the user’s preferred device. If the preferred device is unavailable, all other devices are tried. If it becomes available again, it is automatically selected.
broadcastTrack$
Observable<MediaStreamTrack>
The track to pass to partyTracks.push(). Switches to a silent/empty fallback track when broadcasting is stopped.
localMonitorTrack$
Observable<MediaStreamTrack>
An always-on monitor track. Primarily useful for the mic to enable “talking while muted” detection. Avoid for cameras unless users clearly understand the camera light will stay on.
isBroadcasting$
Observable<boolean>
Whether content is actively being sent.
isSourceEnabled$
Observable<boolean>
Whether the content source is enabled. Flips to false on errors or when the source ends (e.g. screenshare stopped).
error$
Observable<Error>
Emits errors encountered acquiring the source — most commonly NotAllowedError or DevicesExhaustedError.

getScreenshare(options?)

Returns a Screenshare object whose audio and video properties each expose broadcast/transform APIs. Source enabled state is shared at the top level.
import { getScreenshare } from "partytracks/client";

const screenshare = getScreenshare(options?: ScreenshareOptions);

const screenshareVideoTrackMetadata$ = partyTracks.push(
  screenshare.video.broadcastTrack$
);
const screenshareAudioTrackMetadata$ = partyTracks.push(
  screenshare.audio.broadcastTrack$
);

ScreenshareOptions

interface ScreenshareOptions {
  activateSource?: boolean;
  retainIdleTracks?: boolean;
  audio?: boolean | { constraints?: MediaTrackConstraints; options?: { broadcasting?: boolean } };
  video?: boolean | { constraints?: MediaTrackConstraints; options?: { broadcasting?: boolean } };
}
activateSource
boolean
default:"false"
Whether isSourceEnabled should be true initially.
retainIdleTracks
boolean
Keep source tracks alive even when there are no subscribers.
audio
boolean | { constraints?, options? }
Enable audio capture, optionally with constraints and broadcast defaults.
video
boolean | { constraints?, options? }
Enable video capture, optionally with constraints and broadcast defaults.

createAudioSink

Use createAudioSink to safely play pulled audio tracks. It handles edge cases that prevent audio from playing correctly when tracks are attached directly to an HTMLAudioElement.
import { createAudioSink } from "partytracks/client";

const audioElement = document.querySelector("audio");
const audioSink = createAudioSink({ audioElement });

const pulledAudioTrack$ = partyTracks.pull(audioTrackMetadata$);

// Attach the pulled track to the sink.
// Unsubscribing cleans up automatically — no need to detach manually.
const subscription = audioSink.attach(pulledAudioTrack$);
audioElement
HTMLAudioElement
required
The <audio> element to play audio through.
audioSink.attach(pulledTrack$) returns a Subscription. Call .unsubscribe() to stop playback and clean up.

routePartyTracksRequest (server)

Import from partytracks/server. Proxies all requests to the Cloudflare Realtime SFU API, injecting your app credentials. Mount it on a wildcard path in your Worker.
routePartyTracksRequest(options: {
  appId: string;
  token: string;
  request: Request;
  turnServerAppId?: string;
  turnServerAppToken?: string;
  turnServerCredentialTTL?: number;
}): Promise<Response>
appId
string
required
Your Cloudflare Realtime SFU application ID.
token
string
required
Your Cloudflare Realtime SFU application token.
request
Request
required
The incoming Request object to proxy.
turnServerAppId
string
Optional TURN server application ID. When provided alongside turnServerAppToken, the /partytracks/generate-ice-servers endpoint returns TURN credentials in addition to STUN servers.
turnServerAppToken
string
Optional TURN server application token.
turnServerCredentialTTL
number
default:"86400"
Lifetime in seconds for generated TURN credentials.

Server setup with Hono

import { Hono } from "hono";
import { routePartyTracksRequest } from "partytracks/server";

type Bindings = {
  SFU_APP_ID: string;
  SFU_APP_TOKEN: string;
  TURN_SERVER_APP_ID?: string;
  TURN_SERVER_APP_TOKEN?: string;
};

const app = new Hono<{ Bindings: Bindings }>();

app.all("/partytracks/*", (c) =>
  routePartyTracksRequest({
    appId: c.env.SFU_APP_ID,
    token: c.env.SFU_APP_TOKEN,
    turnServerAppId: c.env.TURN_SERVER_APP_ID,
    turnServerAppToken: c.env.TURN_SERVER_APP_TOKEN,
    request: c.req.raw
  })
);

export default app;

React Utilities

Import from partytracks/react. By convention, Observable variables carry a $ suffix.
import {
  useObservableAsValue,
  useObservable,
  useValueAsObservable
} from "partytracks/react";
useValueAsObservable(value)
Observable<T>
Creates a stable Observable that emits whenever value changes between renders.
useObservableAsValue(observable$, defaultValue)
T
Subscribes to observable$ and returns the latest emitted value. Returns defaultValue until the first emission.
useObservable(observable$, observer)
void
Subscribes to observable$ and calls observer.next, observer.error, and/or observer.complete as events arrive.
function SomeComponent({ value }) {
  const value$ = useValueAsObservable(value);
  const latestValue = useObservableAsValue(value$, "default value");

  useObservable(value$, {
    next: (v) => console.log(v),
    error: (e) => console.error(e),
    complete: () => console.log("complete!")
  });
}
Install webrtc-adapter and import it before any partytracks/client code to smooth out cross-browser WebRTC behavior differences: import "webrtc-adapter";

Build docs developers (and LLMs) love