Skip to content

Latest commit

 

History

34 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OpenPlay

CI License: GPL v3 Rust 1.88+

OpenPlay is an open-source screen casting system written in Rust. It lets you cast your screen from any Linux, macOS, or Windows machine to AirPlay receivers, Miracast receivers, or other machines running OpenPlay — all without proprietary software or cloud accounts.

What it does

OpenPlay ships two binaries:

  • openplay-sender — captures your screen and streams it to a receiver of your choice.
  • openplay-receiver — receives and displays incoming streams on a connected display.

The sender finds receivers on the local network over mDNS, so there are no IP addresses to type. Two OpenPlay instances can now find and connect to each other: the receiver advertises itself, listens for TLS signaling connections, and shows the person in front of it an Allow / Deny prompt before anything reaches the screen. That prompt is the whole access-control story today — see Security model.

Status

OpenPlay is under active development. This section is the honest summary. Every casting path is now connected to the binaries, so the distinction that matters below is wired versus verified: "wired" means the call path exists and tests exercise it, not that anyone has watched it work against the hardware or the second machine it is aimed at.

Works today

  • mDNS discovery of AirPlay and Miracast receivers, and mutual discovery between OpenPlay instances — the receiver advertises _openplay._tcp.local. and the sender browses it
  • Screen capture on Linux via XDG Desktop Portal and PipeWire
  • Hardware encoder probing with x264 fallback
  • Configuration loading, validation and first-run file creation
  • A self-signed TLS certificate, generated by the receiver on first launch, with its SHA-256 fingerprint published in the mDNS TXT record and pinned by the sender

Wired end to end, not confirmed between two real machines

  • OpenPlay (WebRTC) — both binaries now drive the whole path: the receiver runs ReceiverAdvertiser and a TLS SignalingServer, prompts for consent, then builds a ReceiverPipeline and answers the sender's SDP offer; the sender connects with SignalingClient, waits to be let in, and drives a SenderPipeline through offer/answer and trickle ICE. It is covered by in-process tests — a loopback TLS signaling exchange (crates/openplay-signaling/tests/loopback.rs) and two webrtcbins negotiating and carrying real decoded frames (crates/openplay-pipeline/tests/webrtc_loopback.rs). Nobody has reported running it between two separate machines, and the WebRTC test drives a videotestsrc look-alike of SenderPipeline rather than the real PipeWire-fed one, because that needs a desktop portal and a human click. Requires the nice GStreamer plugin — see Prerequisites.
  • Miracast sending, including Wi-Fi Direct P2P on Linux — discovery, RTSP M1–M7, the control channel that holds the session open, and the RTP/MPEG2-TS pipeline are all connected. But this has never been verified against a real sink, so do not read "wired" as "works". A defect that ended every cast within milliseconds of a successful handshake survived in this path for a long time precisely because nobody had run it.

Partly built

  • AirPlay sending — discovery, the HTTP/plist session layer, TLV8, NTP, the mirror stream and HAP pairing are implemented. Pairing previously used a fabricated SRP group and could never succeed; it now uses the real RFC 5054 3072-bit group, but is unconfirmed against physical hardware (#27). FairPlay will not be implemented here, so Apple TV 2nd/3rd generation are refused up front with an explicit error — see the decision in docs/crypto.md.

Not implemented at all

  • Audio. OpenPlay casts video only. There is no audio capture, encoding or transport anywhere in the workspace. Note this despite the protocol layer advertising Opus in Capabilities and Miracast negotiating WfdAudioCodecs — those are declarations the pipeline does not honour.
  • Pairing and authentication. openplay-protocol defines PairingChallenge / PairingResponse / PairingConfirm and the Auth* messages; nothing sends or handles them. The consent prompt is what stands in their place — see Security model.

Planned

  • AirPlay and Miracast receiver support
  • macOS and Windows screen capture backends
  • Pairing, so a receiver can recognise a sender it has approved before

Security model

Read this before pointing a receiver at anything you care about.

The consent prompt is the only gate. When a sender asks to cast, the receiver does not accept it, does not build a pipeline and does not put a pixel on the screen until somebody sitting in front of that screen presses Allow. The prompt names the sender, says plainly that OpenPlay cannot verify who it is, and defaults to nothing happening. There is no timeout that accepts, and one session runs at a time — a second sender is refused as Busy rather than silently replacing the first.

Behind that prompt there is no pairing and no authentication. Specifically:

  • The mDNS TXT record carrying the receiver's certificate fingerprint is unauthenticated. Anyone on the LAN can advertise a receiver with their own fingerprint, and a sender that has never seen the real one will pin the attacker's certificate.
  • Certificate pinning therefore buys confidentiality against a passive eavesdropper, and detects a substituted certificate on a later connection. It is not proof of who the receiver is.
  • The sender's identity is a display name it chose for itself. The receiver believes it because it has nothing better, and shows it to the user for that reason and no other.

So: approve devices you recognise, on networks you trust. Treat the name in the prompt as a hint, not an identity.

No STUN or TURN server is contacted. Only host ICE candidates are gathered, which keeps casts on the LAN and avoids telling a third party that a cast is happening. There is no config key for this; a deployment that needs a STUN server has to call WebRtcPeer::set_stun_server before negotiating.

Protocol support

Protocol Direction Notes
AirPlay Sender only, untested against hardware Discovery, HTTP/plist session layer, TLV8, NTP, the mirror stream and HAP pairing are implemented. FairPlay will not be implemented, so receivers that require it (Apple TV 2nd/3rd gen) are rejected by design. Target: Apple TV, AirPlay 2 TVs, and compatible displays. See #27
Miracast / Wi-Fi Display Sender only, never verified against a real sink Cast to Miracast adapters and smart TVs; Wi-Fi Direct P2P supported on Linux. Wired end to end — RTSP M1–M7, a control channel held open for the cast, RTP/MPEG2-TS over UDP
OpenPlay (WebRTC) Sender and receiver, untested between two machines Native protocol between two OpenPlay instances: mDNS discovery, TLS WebSocket signaling, a consent prompt on the receiver, then H.264 over webrtcbin. Both binaries drive it and loopback tests cover it in-process; no cross-machine report exists yet

AirPlay receiver support and Miracast receiver support are planned for a future release.

Features

  • Auto-discovery of receivers on the local network via mDNS — no IP addresses to type
  • Hardware-accelerated H.264 encoding with automatic fallback to software
    • Linux: VA-API (Intel/AMD), NVENC (NVIDIA)
    • macOS: VideoToolbox
    • Windows: Media Foundation, NVENC
    • All platforms: x264 software fallback
  • Screen capture via XDG Desktop Portal and PipeWire on Linux
  • Configurable bitrate and framerate
  • Self-signed TLS certificate generated by the receiver on first launch; its fingerprint is advertised over mDNS and pinned by the sender, which is confidentiality but not authentication — see Security model
  • An explicit Allow / Deny prompt on the receiver before any cast is displayed
  • Lightweight egui GUI — no Electron, no browser runtime

Building from source

Prerequisites

  • Rust 1.88 or later. That is the real floor — CI enforces it in an MSRV job — even though rust-version in the root Cargo.toml still says 1.80. 1.80 cannot resolve the current Cargo.lock at all
  • GStreamer 1.22 or later, including the following plugins:
    • gst-plugins-base, gst-plugins-good, gst-plugins-bad (which is where webrtcbin and h264parse live), gst-plugins-ugly (for the x264enc software fallback), and the nice plugin from libnice
  • On Linux: PipeWire and the XDG Desktop Portal (xdg-desktop-portal and a backend such as xdg-desktop-portal-gnome or xdg-desktop-portal-wlr)
  • On Linux (Miracast Wi-Fi Direct): wpa_supplicant with D-Bus support

The nice plugin is not optional for OpenPlay/WebRTC casts. It ships separately from gst-plugins-bad on every distribution. Without it webrtcbin still builds and then refuses every pad request, so a cast fails with no useful error — confirmed the hard way on a machine that had everything else installed. gst-inspect-1.0 nice should print a plugin, not "No such element or plugin".

Ubuntu / Debian:

sudo apt install \
  libgstreamer1.0-dev \
  libgstreamer-plugins-base1.0-dev \
  libgstreamer-plugins-bad1.0-dev \
  gstreamer1.0-plugins-base \
  gstreamer1.0-plugins-good \
  gstreamer1.0-plugins-bad \
  gstreamer1.0-plugins-ugly \
  gstreamer1.0-nice \
  gstreamer1.0-libav \
  gstreamer1.0-pipewire \
  libpipewire-0.3-dev

The -dev packages compile the workspace; the plugin packages are what supply the elements at runtime, and neither substitutes for the other despite the similar names. gstreamer1.0-plugins-base gives appsink and videoconvert, -good the RTP payloaders, -bad webrtcbin and h264parse, -ugly x264enc, -libav the avdec_h264 software decoder, -nice the ICE plugin, and gstreamer1.0-pipewire the pipewiresrc element Linux capture feeds into (libpipewire-0.3-dev alone is not enough at runtime). See docs/install.md for the per-element detail.

Fedora:

sudo dnf install \
  gstreamer1-devel \
  gstreamer1-plugins-base-devel \
  gstreamer1-plugins-bad-free-devel \
  gstreamer1-plugins-base \
  gstreamer1-plugins-bad-free \
  gstreamer1-plugins-good \
  gstreamer1-plugins-ugly-free \
  libnice-gstreamer1 \
  pipewire-gstreamer \
  pipewire-devel \
  xdg-desktop-portal

On Fedora the ICE plugin is libnice-gstreamer1, not part of gstreamer1-plugins-bad-free. Note also that avdec_h264 — the receiver's only software decode fallback — ships in gstreamer1-libav from RPM Fusion, not from Fedora proper; see docs/install.md.

macOS (Homebrew):

brew install gstreamer

Homebrew folded gst-plugins-base, gst-plugins-good, gst-plugins-bad, gst-plugins-ugly and gst-libav into the single gstreamer formula, so naming them separately now just resolves to the same package. That formula depends on libnice; verify with gst-inspect-1.0 nice before assuming the ICE plugin is present.

Compile

git clone https://github.com/Developer1010x/openplay.git
cd openplay
cargo build --release

The compiled binaries will be at target/release/openplay-sender and target/release/openplay-receiver.

Running

Start the sender on the machine whose screen you want to cast:

./openplay-sender

Start the receiver on the machine that will display the stream:

./openplay-receiver

The receiver advertises itself over mDNS and waits. The sender scans for receivers automatically — select one from the list, where its protocol badge (AirPlay, Miracast or OpenPlay) determines how it is cast to, there is no separate protocol chooser — then click ▶ Start Casting.

For an OpenPlay cast the sender then waits, with no timeout, until somebody at the receiver presses Allow. Nothing is displayed and no media pipeline is built before that. Press Deny, and the sender reports that the person at the receiver declined.

Command-line options

openplay-sender
  --config <path>   Use a custom config file
  --name <name>     Override the display name shown in the window and sent to AirPlay receivers

openplay-receiver
  --config <path>   Use a custom config file
  --name <name>     Override the display name (shown in the window and
                    advertised over mDNS)
  --port <port>     Override the signaling port the receiver binds (default: 7290)

Configuration

On first launch, a config file is created at:

  • Linux: $XDG_CONFIG_HOME/openplay/config.toml (usually ~/.config/openplay/config.toml)
  • macOS: ~/Library/Application Support/org.openplay.OpenPlay/config.toml
  • Windows: %APPDATA%\openplay\OpenPlay\config\config.toml

Example config.toml:

display_name = "My Laptop"
port = 7290
max_bitrate_kbps = 6000
framerate = 30
force_sw_encode = false
airplay_enabled = true
miracast_enabled = true

Configuration values are validated against supported ranges on load: the display name must be non-empty, the port non-zero, max_bitrate_kbps between 100 and 100000, and framerate between 1 and 240. An out-of-range value (for example a max_bitrate_kbps of 0 from a typo) is reported as a clear configuration error rather than failing later inside the media pipeline.

Repository layout

A flat Cargo workspace of eleven crates. The two binaries are openplay-sender and openplay-receiver; everything else is a library used by one or both.

openplay/
  crates/
    openplay-sender/      Binary: sender GUI (egui), receiver list, casting logic
    openplay-receiver/    Binary: receiver GUI (egui), display window
    openplay-airplay/     AirPlay protocol: HAP pairing, FairPlay, NTP, mirror stream, TLV8
    openplay-miracast/    Miracast / Wi-Fi Display: RTSP, WFD params, Wi-Fi Direct (Linux)
    openplay-signaling/   WebSocket signaling client and server
    openplay-protocol/    OpenPlay signaling wire format and state machine
    openplay-discovery/   mDNS advertisement and browsing
    openplay-pipeline/    GStreamer pipeline construction and encoder probing
    openplay-capture/     Screen capture abstraction (XDG Portal / PipeWire on Linux)
    openplay-crypto/      Self-signed TLS certificate lifecycle
    openplay-common/      Configuration, logging, XDG paths, shared constants
  data/                   Desktop entry, AppStream metainfo, icon, D-Bus and polkit files
  flatpak/                Flatpak manifest
  docs/                   Install, configuration, architecture, protocols, crypto,
                          packaging, troubleshooting, contributing
  .github/workflows/      CI

Each crate also carries its own README.md. Full documentation is in docs/.

Contributing

Contributions are very welcome, and the project is early enough that there is a lot of well-scoped work available.

Start with CONTRIBUTING.md — it gets you building in about a minute — or go straight to the good first issues.

The most useful thing most people can do costs nothing to try: run it and report what happened. No test in this repository can substitute for an actual Apple TV, a Miracast dongle, or two machines on a real network, and a failure report is just as valuable as a success.

Where help goes furthest:

Area Difficulty
Running an OpenPlay cast between two real machines and reporting the result Easy
Testing against real AirPlay / Miracast hardware Easy
Documentation corrections Easy
Pairing, so the consent prompt is backed by an identity Medium
Audio support — there is none today Medium
Verifying macOS and Windows capture Medium
Receiving AirPlay on Linux Hard

Open an issue before a large change so the approach can be agreed first. Small fixes can go straight to a pull request. No CLA.

License

OpenPlay is licensed under the GNU General Public License v3.0 or later. See LICENSE for the full text.

About

Open-source screen casting in Rust. AirPlay mirroring with HAP pairing, Miracast WFD, Wi-Fi Direct and a native WebRTC path, with no proprietary software.

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages