mollusk 15766834f1 Revert "viewer HTTP: Content-Type application/octet-stream, not video/mp2t"
This reverts commit 0a253bd919.

The Content-Type change was a misdiagnosis. The real cause of VLC's
"no demux modules matched" was a missing `vlc-plugin-dvb` package on
the test machine — Arch/CachyOS ship the MPEG-TS demuxer plugin
(`libts_plugin.so`) in a separate package from `vlc`. Without it, VLC
falls through to the PS demuxer and misidentifies the H.264 stream.
With the package installed, `video/mp2t` opens cleanly.

`video/mp2t` is the correct Content-Type for an MPEG-TS stream and is
what we should be sending. Documentation of the package requirement
and a runtime check follow in a separate commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 05:26:53 -04:00
2026-05-20 16:04:36 -04:00

pixelpass

P2P screen sharing CLI for Linux. Single binary, hole-punched over iroh — no port forwarding, no signup, no server-side accounts. Hardware-encoded H.264 + AAC audio, viewed in mpv or VLC.

Built for people who just want to show their screen to a friend without spinning up a Discord call or fighting with NAT.

Status

v0.1.0 — verified end-to-end on the public internet (LTE relay path, ~2s latency, real carrier-grade NAT) as of 2026-05-20.

Working:

  • Wayland capture via the screencast portal (KDE Plasma 6 confirmed; other Wayland compositors with the portal should work but are untested)
  • VAAPI H.264 encode in GStreamer (RDNA3 confirmed; other VAAPI-capable GPUs should work)
  • Audio capture of the default sink's monitor
  • iroh QUIC bi-stream tunnel, direct-UDP and relay paths both verified
  • Interactive Host/View menu with clipboard auto-copy and mpv/VLC picker
  • Headless mode for scripts (pixelpass <ticket>)

Not yet working:

  • X11 capture (stubbed, returns an error)
  • Per-app audio routing (--app <name> is a flag stub)
  • Multi-viewer (single viewer per host by design right now)
  • VLC client renders the H.264 stream as a green screen — mpv works
  • --repair (PipeWire orphan cleanup) is a stub

Quick start

pixelpass

On the host machine: pick "Host", share a monitor via the portal dialog, the ticket lands on your clipboard. Send it to your viewer however you like (chat, email, paste in a note).

On the viewer machine: run pixelpass, pick "View", paste the ticket, pick mpv or VLC. The player launches detached and the stream starts.

Headless

# host: prints a ticket on stdout, waits for a peer
pixelpass

# viewer: skips the menu
pixelpass <ticket>
# then run the printed mpv command in another terminal

Requirements

  • Linux (Wayland session for now; X11 stubbed)
  • A VAAPI-capable GPU and the right driver:
    • AMD: libva-mesa-driver
    • Intel: intel-media-driver (modern iGPUs) or intel-vaapi-driver (older)
    • NVIDIA: libva-nvidia-driver (untested)
  • vainfo from libva-utils should list at least one H.264 entrypoint
  • GStreamer with these plugin packages installed:
    • gstreamer, gst-plugins-base, gst-plugins-good, gst-plugins-bad, gst-plugins-ugly, gst-libav, gst-plugin-va, gst-plugin-pipewire
  • A player: mpv (recommended) or vlc
  • PipeWire (for screencast portal + audio capture)

On Arch / CachyOS:

sudo pacman -S gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad \
               gst-plugins-ugly gst-libav gst-plugin-va gst-plugin-pipewire \
               libva-utils mpv
# plus your GPU's VAAPI driver

If the viewer is running on battery, set the CPU governor to performance or balanced — power-saver can choke even hardware-decoded 1080p H.264.

Build

cargo build --release
./target/release/pixelpass --help

rustc 1.95+ / edition 2024.

How it works

  Host                                                          Viewer
  ────                                                          ──────
  Wayland portal (ashpd) ──> PipeWire fd
                              │
                              ▼
              gst-launch: pipewiresrc -> videorate -> vah264enc ->
                          h264parse -> mpegtsmux
                          (audio:    pulsesrc <sink>.monitor ->
                                     avenc_aac -> aacparse ─┘)
                              │ stdout
                              ▼
              tokio HTTP server (in-process, ~30 lines)
                              │
                              ▼
              iroh QUIC bi-stream (ALPN pixelpass/0) ◄══════════►
                                                                 │
                                                                 ▼
                                                  tokio TcpListener
                                                  on 127.0.0.1:rand
                                                                 │
                                                                 ▼
                                                  mpv / VLC HTTP client

The viewer's player connects to a localhost HTTP server, which is just one end of the iroh tunnel. The host's HTTP server sits on the other end and streams GStreamer's stdout (an MPEG-TS containing hardware-encoded H.264 + AAC) through with no demux or remux.

iroh handles NAT traversal: direct UDP if hole-punching succeeds, relay path otherwise. Both have been verified end-to-end.

Why these choices

  • iroh over Holesail / dumbpipe / Tailscale: single Rust dep, no Node runtime, no signup, no daemon — fits the "one self-contained binary" goal.
  • GStreamer for capture/encode, not ffmpeg: stride/format pitfalls when bridging raw video between processes; one in-process pipeline sidesteps them.
  • In-process Rust HTTP server, not ffmpeg-as-server: ffmpeg's -listen 1 is one-shot and probe-budget-sensitive; the Rust task is pure passthrough with no codec assumptions.
  • MPEG-TS over fragmented MP4: every player on Linux handles it out of the box. AV1-in-MPEG-TS was tried and is unworkable through libavformat — if AV1 ever comes back, it has to ride a different container.
  • VAAPI H.264 over x264: ~5% of one CPU core instead of ~50% on the host's hardware.

Known limitations and gotchas

  • VLC shows a green screen against a stream mpv handles correctly. Likely a VLC-specific PCR / PMT / alignment expectation; not yet diagnosed. Use mpv until this is fixed.
  • Audio echo if the host plays the stream through speakers and captures system audio — expected, the mic / monitor picks up the playback. Headphones bypass it.
  • Single viewer per host by design right now. Restarting the player against the same URL fails with "connection refused"; restart the viewer too.
  • VAAPI driver must be package-tracked, not an orphaned .so on disk. mpv's --hwdec=auto silently falls back to software decode otherwise, which then chokes on a low-power viewer.

License

MIT OR Apache-2.0, your pick.

S
Description
No description provided
Readme 368 KiB
Languages
Rust 97.6%
Shell 2.4%