Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

tv-cast

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.

Requirements

  • Linux desktop with PipeWire + PulseAudio (or pipewire-pulse)
  • xdg-desktop-portal with a working ScreenCast backend (GNOME, KDE, wlroots, etc.)
  • GStreamer 1.x with at least one of: nvh264enc (NVENC), vah264enc (VAAPI), or openh264enc
  • GStreamer plugins: pipewiresrc, pulsesrc, videoconvert, videoscale, h264parse, avenc_aac, aacparse, mpegtsmux, fdsink
  • Python 3 with dbus-python and PyGObject (gi)
  • A Samsung Smart TV with DLNA / UPnP enabled, reachable on the LAN

Installing dependencies (Debian/Ubuntu)

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)

Configure

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 machine

You can find the TV's IP in its network settings, or by checking your router's DHCP lease table.

Usage

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 encoder

When 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.

Quality presets

Preset Resolution Bitrate
low 1280×720 3 Mbit/s
medium 1920×1080 6 Mbit/s
high native (no scale) 12 Mbit/s

Encoders

auto (default) picks the first available of nvenc, vaapi, openh264. NVENC and VAAPI are hardware-accelerated and recommended; openh264 is a software fallback.

How it works

  1. Screen capturexdg-desktop-portal ScreenCast hands back a PipeWire node + file descriptor. pipewiresrc reads frames from there.
  2. Audio capturepulsesrc reads from a PulseAudio monitor source (auto-detected as the default sink's monitor unless --audio-source is set).
  3. 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).
  4. HTTP serve — A tiny threaded http.server exposes /stream.ts. On each GET, 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.
  5. Tell the TV — A UPnP AVTransport SetAVTransportURI + Play SOAP pair points the TV at http://<PC_IP>:<HTTP_PORT>/stream.ts with 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.

Troubleshooting

  • "Cannot reach TV" — confirm TV_IP is 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 sources and pass one ending in .monitor via --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 medium first; 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).

License

MIT — see LICENSE if present, otherwise consider this code MIT-licensed.

About

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.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages