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.
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.
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
ReceiverAdvertiserand a TLSSignalingServer, prompts for consent, then builds aReceiverPipelineand answers the sender's SDP offer; the sender connects withSignalingClient, waits to be let in, and drives aSenderPipelinethrough offer/answer and trickle ICE. It is covered by in-process tests — a loopback TLS signaling exchange (crates/openplay-signaling/tests/loopback.rs) and twowebrtcbins 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 avideotestsrclook-alike ofSenderPipelinerather than the real PipeWire-fed one, because that needs a desktop portal and a human click. Requires theniceGStreamer 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
Capabilitiesand Miracast negotiatingWfdAudioCodecs— those are declarations the pipeline does not honour. - Pairing and authentication.
openplay-protocoldefinesPairingChallenge/PairingResponse/PairingConfirmand theAuth*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
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 | 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.
- 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
- Rust 1.88 or later. That is the real floor — CI enforces it in an
MSRVjob — even thoughrust-versionin the rootCargo.tomlstill says 1.80. 1.80 cannot resolve the currentCargo.lockat all - GStreamer 1.22 or later, including the following plugins:
gst-plugins-base,gst-plugins-good,gst-plugins-bad(which is wherewebrtcbinandh264parselive),gst-plugins-ugly(for thex264encsoftware fallback), and theniceplugin from libnice
- On Linux: PipeWire and the XDG Desktop Portal (
xdg-desktop-portaland a backend such asxdg-desktop-portal-gnomeorxdg-desktop-portal-wlr) - On Linux (Miracast Wi-Fi Direct):
wpa_supplicantwith D-Bus support
The
niceplugin is not optional for OpenPlay/WebRTC casts. It ships separately fromgst-plugins-badon every distribution. Without itwebrtcbinstill 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 niceshould 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-devThe -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-portalOn 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 gstreamerHomebrew 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.
git clone https://github.com/Developer1010x/openplay.git
cd openplay
cargo build --releaseThe compiled binaries will be at target/release/openplay-sender and target/release/openplay-receiver.
Start the sender on the machine whose screen you want to cast:
./openplay-senderStart the receiver on the machine that will display the stream:
./openplay-receiverThe 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.
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)
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 = trueConfiguration 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.
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/.
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.
OpenPlay is licensed under the GNU General Public License v3.0 or later. See LICENSE for the full text.