Skip to main content
Daily models every media stream — camera, microphone, screen share, and custom tracks — through the DailyTrackState interface. Understanding track states is essential for building a reliable call object UI.

DailyTrackState

See the DailyTrackState reference for the full interface definition and field descriptions.

Track States

The state field describes the current condition of a track — not a step in a sequential progression:

Why the off sub-fields matter

Track Types

Each DailyParticipant carries a tracks object of type DailyParticipantTracks:

Custom tracks

Custom tracks let you send arbitrary MediaStreamTrack objects alongside the standard camera and mic. Start them with startCustomTrack() and stop them with stopCustomTrack():
Custom track keys appear as string index entries in DailyParticipantTracks.

Local vs. remote track behavior

How a track’s initial state is reached differs between local and remote:
  • Local tracks start directly as playable, off, or blocked — they never pass through loading. If a local track starts off and the user enables it with setLocalVideo(true) or setLocalAudio(true), it transitions straight to playable (or blocked if the user denied device permissions).
  • Remote tracks can start in any state except playable or interrupted — most commonly loading (subscribed but stream not yet established), off (sender muted before you joined), blocked (sender has a device issue), or sendable (sender is ready but you haven’t subscribed).
  • interrupted is never an initial state — it only occurs after a track was playable and then transiently stalled, most commonly during network fluctuations or iOS tab backgrounding. It can return to playable once the condition resolves.

Track Subscription

By default Daily subscribes to tracks from all participants automatically (subscribeToTracksAutomatically: true). In large calls, subscribing to every track is wasteful. Disable automatic subscription to control this explicitly.

DailyTrackSubscriptionState

DailyTrackSubscriptionOptions

Subscribing per participant

Global subscription toggle

Calling setSubscribeToTracksAutomatically(false) during a call will unsubscribe you from all current tracks and require you to manually re-subscribe. Use with caution to avoid unintended media loss. For most cases, it’s best to set this at call creation time and manage subscriptions explicitly from the start.

Attaching Tracks to Video Elements

The recommended approach for rendering tracks is to use track-started and track-stopped events:

track vs. persistentTrack

The key difference: track is undefined whenever state !== 'playable'. persistentTrack is present whenever a track exists, regardless of playable state. We recommend using persistentTrack over track when attaching to media elements. It acts as a proactive defense against black frames during call disruptions and helps avoid browser bugs related to auto-playing media tracks.
Use track only when you need a guarantee that the media is currently flowing.

Track Events

The type field in DailyEventObjectTrack is one of 'video', 'audio', 'screenVideo', 'screenAudio', or a custom track name string.

Track replacement

track-started and track-stopped fire not only on state transitions but also when the underlying MediaStreamTrack is replaced — for example when a participant switches cameras or a device change causes a new track to be issued. In this case the track state stays playable throughout, but you will receive a track-stopped for the old track immediately followed by a track-started for the new one. This means you should key your media elements on the track object itself (or its id), not on participant or track type alone: