Skip to content

Pull-input Streams

A normal stream is push-in: your encoder opens RTMP/E-RTMP, SRT, or WHIP to FrameWorks and uploads media. A pull-input stream is the inverse: FrameWorks stores an upstream URL, then a media edge opens that source when playback needs it.

The payoff is operational: you can onboard venue cameras, contribution feeds, and existing origins without asking every source to become a FrameWorks encoder. Pull input also composes with the rest of the platform — playback access control, DVR, clips, thumbnails, analytics, and multistreaming all attach to the stream once media is active.

Use pull-input streams when:

  • The source is an existing camera, encoder, or origin server that cannot push to FrameWorks.
  • A venue sends you an SRT/RIST contribution feed.
  • You need to restream an HLS or MPEG-TS source.
  • You run self-hosted edge nodes that can reach private cameras or LAN/VPC sources.
SchemeMist inputNotes
rtsp://RTSPBasic-auth in URI accepted; rtsps:// is not supported yet.
srt://SRTStream ID and passphrase use standard SRT URL params.
rist://RISTMPEG-TS over RIST.
dtsc://DTSCPull from another Mist node. Useful for bridging self-hosted media planes.
https://.../*.m3u8HLSMatched by suffix.
https://.../*.tsTSSingle-file TS pulls; for live TS-over-HTTP, prefer HLS or tsudp://.
https://.../*.mkvEBMLIncludes WebM (.webm). Useful for VOD-as-live and some WHIP recordings.
tsudp://host:portTS over UDPUnicast or multicast. Private and multicast destinations need an opted-in edge cluster.

RTMP pull is not supported. RTMP stays push-only. If you need to pull from an RTMP source, run a converter such as FFmpeg, MistServer, or srt-live-transmit and pull its SRT, RIST, RTSP, or HLS output instead.

In the dashboard, create a stream and choose Pull as the ingest mode. Provide the upstream source URI and keep the source enabled if it should start when viewers request playback.

Through GraphQL, use createStream with ingestMode: PULL and pullSource:

mutation CreatePullStream($input: CreateStreamInput!) {
createStream(input: $input) {
__typename
... on Stream {
id
playbackId
ingestMode
pullSource {
sourceUriRedacted
enabled
class
allowedClusterIds
}
}
}
}
{
"input": {
"name": "Lobby Camera",
"ingestMode": "PULL",
"pullSource": {
"sourceUri": "rtsp://camera.example.net/live",
"enabled": true,
"allowedClusters": {
"clusterIds": []
}
}
}
}

The source URI is stored encrypted and returned only in redacted form. For public sources, allowedClusters.clusterIds: [] means any media cluster may run the pull. Set cluster IDs to pin a public source to a specific cluster set.

Private literals require two pieces of configuration:

  • The target media cluster must opt in with allow_private_pull_sources: true.
  • The pull stream must explicitly pin itself to that cluster with allowedClusters.clusterIds (GraphQL) or allowed_cluster_ids (bootstrap).
clusters:
warehouse-edge:
name: Warehouse Edge
type: edge
owner_tenant: acme
roles: [media]
allow_private_pull_sources: true

Then create the pull stream with the placement pin:

{
"input": {
"name": "Warehouse Camera",
"ingestMode": "PULL",
"pullSource": {
"sourceUri": "rtsp://192.168.10.50/live",
"enabled": true,
"allowedClusters": {
"clusterIds": ["warehouse-edge"]
}
}
}
}

This protects platform-managed clusters from trying to reach sources they cannot access and keeps private-source placement constrained to edge nodes you operate.

Deployment shapeResult
Platform cluster, public source URIAllowed.
Platform cluster, private literal source URIRejected.
Self-hosted edge with opt-in + placement pinAllowed and routed only to the pinned eligible media clusters.
Hostname resolving to private IP from an edgeTreated as public at validation; DNS reachability is operator-owned.

Pull streams are on-demand:

  1. A viewer requests /play/{playback_id}.
  2. Foghorn routes the viewer to an eligible media edge, even if the stream is currently inactive.
  3. MistServer starts the pull+ stream and asks Foghorn for the source.
  4. Foghorn ignores untrusted fallback query params and resolves the stored source URI from Commodore.
  5. The first edge pulls upstream media; later viewers prefer in-cluster DTSC fanout once available.
  6. When the last viewer leaves, MistServer drops the upstream after the standard grace period.

Idle pull streams do not fetch from the upstream and do not accrue live processing or viewer costs. Once active, metering is the same as push streams: viewer minutes, egress, transcoding, recording, and storage apply normally.

Every STREAM_SOURCE evaluation records the resolution outcome. The dashboard shows the latest events on the stream setup panel, and GraphQL exposes the same feed:

query PullSourceEvents($id: ID!) {
stream(id: $id) {
... on Stream {
ingestMode
recentPullSourceEvents(limit: 10) {
eventKind
detail
createdAt
}
}
}
}

eventKind is one of resolved, not_found, disabled, blocked_uri, private_not_allowed, commodore_error, or foghorn_base_unresolved. These are source resolution events: they explain whether Foghorn handed MistServer a source. Upstream connect/disconnect/retry telemetry depends on MistServer emitting additional trigger types.

  • Upstream unreachable - The Mist input exits. The next viewer triggers a fresh pull attempt.
  • Two viewers arrive at once - More than one edge can briefly pull from the upstream before fanout takes over.
  • Source URI changed - Update the pull source from the dashboard or GraphQL. Operator-managed pull streams can also be updated by re-running bootstrap with the new URI. Active sessions reconnect against the new origin after the media plane sees the updated source.
  • Source sends silence or black - FrameWorks does not synthesize fallback media; viewers see what the upstream sends.

Once media is active, pull streams behave like push streams:

For a lower-level service map, see the architecture note in docs/architecture/pull-streams.md.