Cast your Linux desktop (screen + audio) to a Samsung Smart TV over the local network with low latency — typically 1–3 s glass-to-glass.
It captures the screen via xdg-desktop-portal ScreenCast (PipeWire) and
desktop audio via a PulseAudio monitor, encodes to H.264 + AAC with
GStreamer, and serves the muxed stream as MPEG-TS over HTTP. The TV is
told to play it via UPnP AVTransport (DLNA).
Direct MPEG-TS over HTTP avoids HLS's segment-buffer floor (typically 6–20 s on Samsung TVs) without lowering bitrate or resolution.
- Linux desktop with PipeWire + PulseAudio (or pipewire-pulse)
xdg-desktop-portalwith a working ScreenCast backend (GNOME, KDE, wlroots, etc.)- GStreamer 1.x with at least one of:
nvh264enc(NVENC),vah264enc(VAAPI), oropenh264enc - GStreamer plugins:
pipewiresrc,pulsesrc,videoconvert,videoscale,h264parse,avenc_aac,aacparse,mpegtsmux,fdsink - Python 3 with
dbus-pythonandPyGObject(gi) - A Samsung Smart TV with DLNA / UPnP enabled, reachable on the LAN
sudo apt install \
python3-dbus python3-gi \
gstreamer1.0-tools \
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
gstreamer1.0-libav \
gstreamer1.0-pipewire gstreamer1.0-pulseaudio \
xdg-desktop-portal
# Pick the one matching your GPU:
# sudo apt install gstreamer1.0-vaapi # Intel / AMD
# (NVENC ships with the NVIDIA gstreamer plugins)Edit the constants near the top of tv-cast.py to match your network:
TV_IP = "192.168.1.2" # Samsung TV's LAN IP
TV_DLNA_PORT = 9197 # default Samsung AVTransport port
PC_IP = "192.168.1.8" # this machine's LAN IP (TV connects here)
HTTP_PORT = 8888 # any free port on this machineYou can find the TV's IP in its network settings, or by checking your router's DHCP lease table.
python3 tv-cast.py # medium (1080p), audio + video
python3 tv-cast.py --quality high # native resolution
python3 tv-cast.py --quality low # 720p, lower bitrate
python3 tv-cast.py --no-audio # video only
python3 tv-cast.py --audio-source game-audio.monitor # specific PulseAudio source
python3 tv-cast.py --encoder vaapi # force a specific encoderWhen it starts, the desktop portal will pop up a dialog asking which monitor to share. Pick one, and the cast begins. Press Ctrl+C to stop.
| Preset | Resolution | Bitrate |
|---|---|---|
low |
1280×720 | 3 Mbit/s |
medium |
1920×1080 | 6 Mbit/s |
high |
native (no scale) | 12 Mbit/s |
auto (default) picks the first available of nvenc, vaapi, openh264.
NVENC and VAAPI are hardware-accelerated and recommended; openh264 is a
software fallback.
- Screen capture —
xdg-desktop-portalScreenCast hands back a PipeWire node + file descriptor.pipewiresrcreads frames from there. - Audio capture —
pulsesrcreads from a PulseAudio monitor source (auto-detected as the default sink's monitor unless--audio-sourceis set). - Encode + mux — GStreamer encodes to H.264 (low-delay CBR, 1 s GOP,
B-frames disabled) and AAC, then muxes both into MPEG-TS with
mpegtsmux alignment=7(1316-byte aligned packets, network-MTU friendly). - HTTP serve — A tiny threaded
http.serverexposes/stream.ts. On eachGET, it spawns the GStreamer pipeline writing to stdout and pipes the bytes straight into the response body. The pipeline is per-connection, so reconnecting the TV produces a fresh sync within ~1 GOP. - Tell the TV — A UPnP AVTransport
SetAVTransportURI+PlaySOAP pair points the TV athttp://<PC_IP>:<HTTP_PORT>/stream.tswith the right DLNA flags so Samsung's player accepts it as a live stream.
TCP_NODELAY is enabled on the response socket to keep latency bounded by
GOP, not by Nagle.
- "Cannot reach TV" — confirm
TV_IPis correct and the TV's network setting allows discovery; some Samsung models hide DLNA behind a "External Device Manager" or "Anynet+" toggle. - Portal dialog never appears — make sure
xdg-desktop-portal-<gnome|kde|wlr>is installed and running for your session. - No audio — list sources with
pactl list short sourcesand pass one ending in.monitorvia--audio-source. Audio for a specific app is often easier from a per-app virtual sink. - TV plays for a few seconds then stops — usually a codec/bitrate the TV
rejects. Try
--quality mediumfirst; some older Samsung models top out near 8 Mbit/s 1080p. - No H.264 encoder found — install the GStreamer plugin matching your GPU (see the apt line above).
MIT — see LICENSE if present, otherwise consider this code MIT-licensed.