Tunnel & Transcode

NEW TRANSCODE STREAM continuously converts a live RTSP source — or any other continuous media source — into a browser-playable stream. Browsers cannot play raw RTSP directly; this is a real protocol constraint, not a design choice. Combined with DIRECTIVE TUNNEL, a camera or encoder with no public IP can be exposed and streamed to a browser end to end.

Implementation status. Everything on this page is implemented and working.

TRANSCODE STREAM — Live RTSP to Browser

Start a Live Transcode
NEW TRANSCODE STREAM FROM "/root/clip.mp4" SET ?stream
AFTER EMIT ?stream("rtsp_url")
(* Output: rtsp://transcode.ocalt.com/<token> *)
Stop It
TRANSCODE STREAM ?stream STOP

What a Transcode Returns

Starting a stream returns a handle carrying token, rtsp_url and playback_url. The RTSP URL plays in VLC, ffmpeg, or any RTSP client; the playback URL is an HLS playlist for a browser. Both point at the same running stream — publishing happens once. Recording to a file returns token and path instead.

Hold on to the handle: TRANSCODE STREAM ?handle STOP is how a stream ends, and until it is stopped it keeps running and keeps consuming resources.

Sources and Destinations

A transcode reads from one place and writes to another. Both ends are flexible, which is what lets the same verb cover live streaming, recording, and format conversion.

Sources — a URL, including a tunnel URL pointing at a machine’s own port; or a path in your namespace (/root/..., /mounted/...).

Destinations — an RTSP endpoint, returned as rtsp_url in the form rtsp://transcode.ocalt.com/<token>; or a path in your namespace, using TO.

Namespace File to a Live RTSP Endpoint
NEW TRANSCODE STREAM FROM "/root/clip.mp4" SET ?stream
AFTER EMIT ?stream("rtsp_url")
(* rtsp://transcode.ocalt.com/<token> — playable in VLC or any RTSP client *)
Recording — RTSP In, File Out
NEW TRANSCODE STREAM FROM "rtsp://transcode.ocalt.com/abc123" TO "/root/recordings/today.mp4" DURATION 30 MINUTES SET ?rec
AFTER EMIT ?rec("path")
(* Records for 30 minutes, then ends itself. TO may also point at /mounted/...
   DURATION defaults to 5 minutes if omitted; being non-blocking, it can be set longer. *)

Stopping a Stream

DURATION is the normal way to bound a transcode — it runs for that long and ends itself. STOP is optional and used only to end one early: before its duration elapses, or when it was started with no duration at all. A stream that has a duration and is left alone needs no STOP.

Stop a Stream Early
NEW TRANSCODE STREAM FROM "/root/clip.mp4" DURATION 1 HOUR SET ?s
AFTER EMIT ?s("rtsp_url")
(* ... later, to end it before the hour is up ... *)
AFTER TRANSCODE STREAM ?s("id") STOP
Give a transcode a DURATION or stop it yourself — otherwise a recording without a duration would run for the default five minutes and a live stream would end at the same ceiling. STOP takes the stream’s id, the same token returned when it started.

Output Format — HLS or MSE

Both formats are supported via FORMAT. HLS is segment-based — simpler, universally supported, a few seconds of inherent latency. MSE is a live WebSocket feed, built on the same relay as SYNC CHANNEL — more plumbing, lower latency.

HLS — Segmented Playlist
NEW TRANSCODE STREAM FROM "/root/clip.mp4" FORMAT "hls" SET ?stream
AFTER EMIT ?stream("playback_url")
(* A .m3u8 playlist URL — play with a <video> tag and hls.js, or natively on Safari *)
MSE — Live WebSocket Feed
NEW TRANSCODE STREAM FROM "/root/clip.mp4" FORMAT "mse" SET ?stream
AFTER EMIT ?stream("connect_url")
(* A raw WebSocket endpoint delivering live media chunks via MediaSource Extensions,
   lower latency than HLS at the cost of more client-side JavaScript *)

DIRECTIVE TUNNEL + TRANSCODE — Exposing a Camera with No Public IP

A home security camera or local encoder behind NAT has no reachable public address. DIRECTIVE TUNNEL exposes its RTSP port through the agent's existing outbound connection; TRANSCODE STREAM then converts the now-reachable feed into something a browser can actually play.

Full Pipeline — Camera to Browser
DIRECTIVE "laptop" SERVE "C:/downloads/test.mp4" SET ?src
AFTER DIRECTIVE "laptop" TUNNEL PORT ?src("port") SET ?tunnel
AFTER NEW TRANSCODE STREAM FROM ?tunnel("url") SET ?stream
AFTER EMIT ?stream("rtsp_url")
(* SERVE puts the file on a local port, TUNNEL makes that port reachable,
   TRANSCODE reads it server-side. Nothing is installed and nothing is uploaded. *)

Transcoding a File Source

TRANSCODE STREAM is not limited to live RTSP sources — a static file already sitting in a Site Mode namespace can be streamed the same way, useful when a file needs to be served as a continuous stream rather than downloaded whole.

Streaming a File from a Site's Namespace
NEW TRANSCODE STREAM FROM "/root/sites/mysite/myvideo.mp4" FORMAT "hls" SET ?stream
AFTER EMIT ?stream("playback_url")
Internal architecture. Transcoding happens entirely on Ocalt’s side. A continuous process per active stream reads from the source and publishes into a media server, which holds the result and serves it. Publishing once makes the same stream available as RTSP, HLS and WebRTC — one pipeline, several ways to consume it. Nothing runs on the source machine, and nothing needs to be installed there.
FORMAT applies to recordings, not to live streams. With TO, FORMAT chooses the codec the file is written in. Without it — a live RTSP stream — the source is carried as it is and FORMAT has no effect. Use QUALITY to change what a live stream carries.

QUALITY and TIME

A transcode does not have to send the source as it is. QUALITY fixes the output resolution; TIME starts it somewhere other than the beginning.

Fixing the Resolution
NEW TRANSCODE STREAM FROM "/root/media/lecture.mkv" QUALITY "720p" SET ?stream
AFTER EMIT ?stream("url")
QUALITY Output
"144p" / "240p"Minimal bandwidth — preview and thumbnail work
"360p" / "480p"Watchable over a constrained connection
"720p"The usual choice for streaming a tunnelled source
"1080p"Full detail, at full bandwidth
Aspect ratio is preserved — QUALITY sets the height and the width follows. A source smaller than the requested quality is not upscaled; it is sent as it is.
Starting Partway In
NEW TRANSCODE STREAM FROM "/root/media/lecture.mkv" TIME "00:12:30" QUALITY "480p" SET ?stream
AFTER EMIT ?stream("url")
(* The stream begins twelve and a half minutes in *)
Why seeking belongs here. A player seeking inside a stream can only ask for byte ranges the source is willing to serve, and a tunnelled or served file often cannot answer that reliably. TIME moves the decision into the runtime: the transcode starts at that point, so the player receives a stream that genuinely begins there and has nothing to seek for.
A Clip — TIME With DURATION
NEW TRANSCODE STREAM FROM "/root/media/lecture.mkv"
TIME "00:12:30" DURATION 90 SECONDS QUALITY "720p" SET ?clip
AFTER EMIT ?clip("url")