The TLTV viewer is an HLS.js-based web player with two modes and a standalone
portal. It can run as a standalone command (tltv viewer) or be embedded in any
daemon via --viewer or --debug-viewer.
Phosphor-matching dark theme with clean playback UI:
- HLS.js video player with Safari native fallback
- SVG icon controls: volume slider, fullscreen, picture-in-picture
- EPG guide grid with per-channel program blocks (4px/min scale, 30-minute slots)
- Channel selector dropdown for multi-channel daemons
- Audio and subtitle track selectors
- Relay badge from signed origins (green = origin, dim = relay, yellow = spoofed)
tltv://URI display with click-to-copy- Footer with TLTV mark and links
- Stall detection with auto-recovery
- Audio-only overlay (music note SVG + label)
- Mobile responsive at 640px
Diagnostic viewer with five live sections:
- Channel — curated metadata fields plus remaining keys
- Stream — live HLS stats (segments, bitrate, buffer, resolution), per-variant breakdown (resolution, bandwidth, codecs, active label), audio/subtitle tracks
- Guide — entries with now-playing marker,
updatedandseqfields - Node — origin verification from signed metadata, channel list
- Peers — peer exchange entries
tltv viewer without a target starts a portal with a freeform tune box:
# Portal mode — opens tune box in browser
tltv viewer
# Direct tune to a host
tltv viewer demo.timelooptv.org
# Direct tune to a channel URI
tltv viewer "tltv://TVabc@origin.tv"The portal resolves channels server-side via /api/resolve:
- Fetches
/.well-known/tltv, verifies metadata, resolves stream - Hostname tune discovers ALL channels from the host
- Icons fetched as base64 data-URIs at resolve time (no CORS issues)
- Guide data fetched per-discovered-channel
Note: The standalone portal is explicitly local/single-user. The process holds a single global upstream — one client tuning changes playback for all connected browsers. Do not expose on a public network for multi-user access.
All four daemons (server, bridge, relay, mirror) support viewer embedding:
# Production viewer on a server
tltv server test -k channel.key --viewer --name "My Channel"
# Debug viewer on a relay
tltv relay --node origin:443 --debug-viewer
# Production viewer on a bridge with display config
tltv bridge --stream source.m3u --viewer --viewer-title "My Network"The flags are mutually exclusive — setting both --viewer and --debug-viewer
is a startup error.
| Flag | Short | Env | Default | Description |
|---|---|---|---|---|
--viewer-title |
-e |
VIEWER_TITLE |
(empty) | Nav bar label text |
--no-viewer-footer |
-Z |
VIEWER_FOOTER=0 |
on | Hide footer links |
Values are JSON-encoded into the viewer config to prevent injection.
The portal persists tuned channels in the browser's localStorage as
tltv_saved_channels. Each successful tune adds the channel to the guide grid.
Optional server-side file persistence:
| Flag | Short | Env | Description |
|---|---|---|---|
--saved-channels |
-E |
SAVED_CHANNELS |
JSON file for channel list persistence |
When set, the portal server reads the file on startup, serves it via
GET /api/saved-channels, accepts POST /api/saved-channels, and writes back.
Channels are deduplicated by ID.
Security: Raw access tokens are never persisted in saved channel data.
Channel removal via the x button auto-tunes the next channel.
On a private daemon (--access token), the embedded viewer root (/) and
/api/info are gated by the same ?token=... query parameter used by protocol
endpoints. Open /?token=secret to access the viewer.
The viewer JS uses a shared withToken() helper to propagate the query token to
/api/info and stream requests. /api/info never echoes the raw token back to
the browser — the browser keeps the token in its own query string.
When the standalone viewer listens on a non-loopback address, /api/resolve uses
the SSRF-safe client to prevent probing internal networks. The --local/-L
global flag overrides this block for development.
| Flag | Short | Env | Default | Description |
|---|---|---|---|---|
--viewer |
-V |
VIEWER=1 |
off | Enable production viewer |
--debug-viewer |
DEBUG_VIEWER=1 |
off | Enable debug viewer | |
--viewer-title |
-e |
VIEWER_TITLE |
(empty) | Nav bar label |
--no-viewer-footer |
-Z |
VIEWER_FOOTER=0 |
on | Hide footer |
--saved-channels |
-E |
SAVED_CHANNELS |
File persistence for portal |
- Channel selector. When
/api/inforeturns multiple channels, the viewer shows a dropdown. Channel switch reloads via?channel=idquery parameter. - Guide grid. Each channel row shows its own program blocks from per-channel guide data. Icons appear next to channel names in guide labels.
- Click-to-copy. The
tltv://URI usesnavigator.clipboardwithdocument.execCommand('copy')fallback for non-HTTPS contexts. - Timer management. Portal mode uses
clearTimers()/startTimers()to prevent accumulation of pollers on repeated tunes. - Token leak prevention. Standalone mode never exposes upstream URLs (which may contain tokens) in browser-facing JSON responses.