playport README

The project's README from github.com/youcci/playport. As one markdown file: README.md.

PlayPort

Wireless CarPlay in your browser. A server-side CarPlay receiver that turns any screen on your network into a head unit.

License: GPL-3.0 Platform: macOS Status: experimental Tests

PlayPort live CarPlay viewer

PlayPort runs the car side of CarPlay on a Mac. Your iPhone connects wirelessly (Bluetooth bootstrap, then Wi-Fi), and every browser on the network can watch and control CarPlay — touch, rotary knob, media keys, Siri and phone calls. No dongle, no head unit, no app install: open a URL and drive.

Port notice. PlayPort's AirPlay, iAP2 and accessory-authentication code is ported from DiPlay by shihabal3amri, itself based on xcertplay by shilapi (both GPL-3.0). PlayPort replaces the Android head-unit app with a JVM server and a WebCodecs browser client. It is an independent project, not affiliated with or endorsed by the DiPlay or xcertplay authors.


Table of contents


How it works

          Bluetooth (first pairing)                     Wi-Fi / LAN
   ┌──────────────────────────────────┐   ┌─────────────────────────────────────────┐
   │  iAP2 identification             │   │  mDNS advertisement (_airplay._tcp)     │
   │  MFi authentication              │   │  AirPlay RTSP: pairing, /info, SETUP    │
   │  Wi-Fi credentials handoff       │   │  H.264 / HEVC video  +  RTP audio       │
   └──────────────────────────────────┘   │  HID input  +  iAP2 control tunnel      │
                                          └─────────────────────────────────────────┘
                        │                                     │
                        ▼                                     ▼
              ┌──────────────────────────────────────────────────────────┐
              │  PlayPort server (Kotlin/JVM + Ktor)                     │
              │  · :protocol — ported DiPlay stack (AirPlay, iAP2, MFi)  │
              │  · RTSP listener, mDNS, pairing store, BT bridge         │
              │  · decrypts media, never re-encodes it                   │
              └──────────────────────────────────────────────────────────┘
                        │  WebSocket: binary media + JSON control
                        ▼
              ┌──────────────────────────────────────────────────────────┐
              │  Browser client (TypeScript, no framework)               │
              │  · WebCodecs VideoDecoder → canvas  ·  AudioDecoder      │
              │  · pointer + keyboard → CarPlay HID                      │
              └──────────────────────────────────────────────────────────┘

The iPhone thinks it is talking to a car head unit: PlayPort advertises itself over Bonjour, authenticates with an MFi identity, receives the encrypted H.264/HEVC screen and RTP audio, and sends touch/knob/media HID reports back. The server passes the phone's encoded media straight to the browser (no server-side decoding or re-encoding), and the browser decodes it with WebCodecs.

Features

Screenshots

Display and quality Audio mixer (no active streams)
Display settings Audio mixer
Focus mode Mobile viewer
Focus mode Mobile viewer

Requirements

Component Requirement
Server macOS 13+ (developed and tested on macOS 27, Apple silicon)
Java JDK 21 (brew install openjdk@21)
Build tools Xcode Command Line Tools (xcode-select --install) for the Bluetooth bridge
Web client Node.js 20+ (only needed to build web/dist)
iPhone Any CarPlay-capable iPhone with Bluetooth and Wi-Fi
Network iPhone and Mac on the same Wi-Fi network; Bluetooth enabled on both

Quick start

git clone https://github.com/youcci/playport.git
cd playport

# 1. Add the accessory identity (see the section below) — the server refuses to start without it
mkdir -p identity/offline-mfi
cp /path/to/identity.pk8 /path/to/certificate.p7b identity/offline-mfi/

# 2. Build everything
./gradlew build                      # protocol + server, runs 96 tests
(cd web && npm install && npm run build)

# 3. Build the Bluetooth bridge (macOS RFCOMM helper)
macos/bt-bridge/build.sh

# 4. Pair the iPhone once (keep Settings > Bluetooth open on the phone)
macos/bt-bridge/bt-bridge --scan
macos/bt-bridge/bt-bridge --pair AA:BB:CC:DD:EE:FF      # confirm the code on the iPhone

# 5. Prepare macOS
#    · System Settings → General → AirDrop & Handoff → turn OFF "AirPlay Receiver"
#    · System Settings → Privacy & Security → Bluetooth → enable your terminal app
#    · Allow incoming connections for java if the firewall asks

# 6. Run the server (use the Wi-Fi name the iPhone is on)
./gradlew :server:run --args="--wireless --wifi-ssid \"YOUR_WIFI\" --wifi-passphrase \"YOUR_PASSWORD\""

Open the https:// URL the server prints (it contains an access token) and accept the self-signed certificate warning once. HTTPS is required because browsers only expose WebCodecs — the video/audio decoders — in secure contexts. On the iPhone, accept the CarPlay prompt; if it asks for a code, enter 3939. The dashboard appears in the browser as soon as the first video frame arrives.

Accessory identity (required)

The iPhone will only start CarPlay with an authenticated accessory. On every connection it requests the accessory certificate and sends a challenge; PlayPort signs it with the matching private key. AirPlay setup uses the same identity. Without these two files the server exits at startup, and there is no software workaround.

PlayPort does not ship this identity — it is third-party key material, it is not relicensed as source code, and it must not be redistributed.

Where the files go

identity/offline-mfi/identity.pk8       # PKCS#8 EC P-256 private key
identity/offline-mfi/certificate.p7b    # accessory certificate (Apple Accessories CA)

identity/ is ignored by Git. To keep it somewhere else, pass --identity-dir /path/to/identity. You can verify a pair at any time with the bundled test — it signs a challenge and checks it against the certificate:

./gradlew :server:test --tests '*MfiIdentityTest*'

Where to normally find it

  1. From a DiPlay release APK (easiest public source). DiPlay bundles an experimental identity recovered from Carlinkit firmware and documents that it is extractable by design:

    curl -L -o DiPlay.apk \
      https://github.com/shihabal3amri/DiPlay/releases/download/v0.2.7/DiPlay-0.2.7.apk
    unzip -j DiPlay.apk 'assets/offline-mfi/*' -d identity/offline-mfi
    

    See DiPlay's docs/THIRD_PARTY_NOTICES.md for provenance.

  2. From Carlinkit C2Air firmware. The pair was originally recovered from public Allwinner V821 firmware. The reverse-engineering community documents how to dump and unpack these dongles — for example ludwig-v/wireless-carplay-dongle-reverse-engineering and the projects it links. Search the unpacked root filesystem for an Apple-issued X.509 certificate and a matching PKCS#8 EC key.

  3. Your own MFi credentials. If you are an MFi licensee with an accessory identity, use your own certificate and key. A physical MFi authentication coprocessor is not supported by this project.

  4. A remote MFi server. If you run an authentication service, point PlayPort at it instead of a local identity: --mfi-server https://host --mfi-token <token>.

Caveats. The publicly available identity is shared, experimental and not issued to PlayPort. Apple can revoke it in any iOS update, and this is not an Apple-certified product. Use it for personal experimentation, do not sell it, and do not commit or redistribute the key.

Display, orientation and quality

The phone renders CarPlay at whatever resolution PlayPort advertises, so the Display panel in the browser is the quality control room:

Applying a change restarts CarPlay (the phone reconnects in a few seconds). Values persist in ~/.playport/config.json. The same knobs exist as flags for a fixed setup:

./gradlew :server:run --args="--width 1920 --height 720 --fps 60 --ui-scale 85 --hevc"

Audio and microphone

Audio is passed through per stream (media, guidance, speech, telephony) and decoded in the browser with a jitter buffer. The Audio panel has a master volume and per-stream sliders; the control dock has Call (answer/end) and Mute call, and the sidebar has Night (day/night mode). When the phone opens Siri or a call, the browser asks for microphone permission and sends audio back to the phone.

Controls

Input Action
Touch / mouse drag CarPlay touch (two contacts supported)
Arrow keys Rotary knob (up/down/left/right)
Enter Select
Esc / Backspace Back
Space Play / pause
S Siri
Viewer controls Home, Back, Select, media transport, Siri, Call, Mute call, Night, Audio, Display, Refresh video, Focus mode, Fullscreen

Use Guide for connection steps and keyboard shortcuts. Focus mode hides the header and sidebar while keeping the CarPlay controls within reach.

Configuration

Every option can be given as a CLI flag; UI changes are saved to ~/.playport/config.json (hand-editable — CLI flags always win). Run with --help for the full list.

Flag Description Default
--device-name <name> Name shown on the iPhone PlayPort
--model, --manufacturer Strings advertised to the phone playport
--airplay-port <n> AirPlay RTSP port 7000
--http-port <n> Browser UI port (HTTPS) 8080
--width, --height, --fps, --ui-scale, --hevc Display defaults 1280×720@60, 100%, H.264
--rhd Right-hand-drive layout off
--bind <address> Bind address auto-detect LAN
--identity-dir <path> Directory containing offline-mfi/ ./identity
--state-dir <path> Identity, pairings, TLS, config ~/.playport
--mfi-server, --mfi-token Remote MFi service instead of local keys —
--wireless Enable the Bluetooth/Wi-Fi bootstrap off
--wifi-ssid, --wifi-passphrase, --wifi-channel, --wifi-security Network handed to the phone auto-detect SSID
--bt-address <mac> iPhone Bluetooth address auto-discovered
--bt-bridge <path> Bluetooth helper binary macos/bt-bridge/bt-bridge
--mdns <backend> auto, dns-sd or jmdns auto (dns-sd on macOS)
--http Serve plain HTTP (WebCodecs then only works on localhost) off
--token <token> Fixed browser access token random per start

Troubleshooting

Symptom Cause and fix
bt-bridge aborts immediately (SIGABRT) macOS Bluetooth privacy. System Settings → Privacy & Security → Bluetooth → enable your terminal app.
iPhone never appears in Bluetooth settings Expected — macOS hides iPhones. Pair with macos/bt-bridge/bt-bridge --pair <address> (with Settings → Bluetooth open on the phone).
--pair hangs The device is already paired; just start the server, or use --force-pair to re-bond.
no Wi-Fi network detected macOS hides the SSID from processes without Location Services, or the Wi-Fi is on another interface. Pass --wifi-ssid and --wifi-passphrase, or grant Location Services to your terminal.
Phone connects but video is black Open the https:// URL (WebCodecs needs a secure context) and hard-refresh. Press Fix video to force a keyframe.
H.265/HEVC … cannot decode Your browser lacks HEVC. Switch the codec back to H.264 in the Display panel.
No CarPlay prompt on the iPhone Check the phone joined the same Wi-Fi. Remove stale entries in iPhone Settings → General → CarPlay, then let the supervisor retry (or restart the server).
Port 7000 already in use macOS AirPlay Receiver is on. System Settings → General → AirDrop & Handoff → turn it off.
Browser warns about the certificate Expected: the server generates a self-signed certificate in ~/.playport. Accept it once.
Stream resolution lower than requested The iPhone clamps to what its encoder supports; the header HUD shows what actually arrived.

Security and privacy

Project layout

protocol/   Kotlin/JVM library ported from DiPlay's shared module (AirPlay, iAP2, MFi, streams)
server/     Ktor server: RTSP + mDNS + Bluetooth bridge + WebSocket hub + display/mic/config APIs
web/        Vite + TypeScript browser client (WebCodecs video/audio, input, panels)
macos/      bt-bridge: Swift IOBluetooth RFCOMM helper for the wireless bootstrap
identity/   Accessory identity drop-in (gitignored — see above)
docs/       Logo and screenshots

Development

./gradlew build          # compiles both modules and runs the test suite (96 tests)
(cd web && npm run build)  # type-checks and bundles the browser client
(cd web && npm test)       # checks keyboard isolation and touch mapping
./gradlew :server:run --args="--help"

The protocol module is a faithful port of DiPlay's shared sources with Android dependencies replaced by small JVM shims (platform/Log, network/TcpLiveness, transport/IphoneUsbException), so future upstream fixes stay easy to merge. MfiIdentityTest validates the local identity against its certificate; WireTest covers the browser wire framing.

Status and limitations

PlayPort is experimental. It has been validated on macOS with an iPhone (iOS 27) over Wi-Fi: wireless bootstrap, MFi authentication, live video with touch input, display switching and persistence. Tested with H.264; HEVC passthrough is implemented but depends on your browser.

Credits and license

PlayPort is free software under the GNU General Public License v3.0 — see LICENSE. As a derivative of DiPlay and xcertplay (both GPL-3.0), it must remain under the same license: you may use, study, modify and share it, but distributed copies and modifications must keep this license and make their source available.

CarPlay, iPhone, AirPlay and MFi are trademarks of Apple Inc. PlayPort is an independent project; no Apple affiliation or endorsement is implied.

playport (youcci/playport)

Searching Database…

What is this?PlayPort is a server-side CarPlay receiver that runs on a Mac and turns any browser on your network into a wireless CarPlay screen — touch, knob, media keys, Siri and calls included, with no dongle or app install required.
Is this a native app I install on my phone, or does it run as a server?It is a JVM server you run on a Mac that plays the car side of CarPlay; the iPhone connects over Bluetooth then Wi-Fi, and any browser renders the video/audio with WebCodecs instead of a native head-unit app.
How do I get PlayPort running?Start the server (after setting up an accessory identity), then open the URL it prints in a browser and accept the self-signed certificate warning once; on the iPhone accept the CarPlay prompt (enter 3939 if it asks for a code).
What do I need installed before running it?A Mac on macOS 13+ (tested on Apple silicon), JDK 21, Xcode Command Line Tools for the Bluetooth bridge, and Node.js 20+ only if you need to rebuild the web client; the iPhone just needs Bluetooth and Wi-Fi on the same network.
What's the biggest catch before I can actually use this?PlayPort needs a real MFi accessory certificate and private key to authenticate with the iPhone, and it does not ship that third-party key material — you have to source it yourself, and without it the server refuses to start at all.
Can multiple devices on the network use it as the CarPlay screen at once?Yes — any browser on the LAN (laptop, tablet, wall panel, another Mac) can act as a viewer, and several can connect simultaneously to the same session, though the server itself only runs on macOS.
Does any of my CarPlay session data leave the machine?No — the MFi private key never leaves the Mac, nothing is uploaded anywhere, and the browser UI is locked down with a per-start access token plus a self-signed HTTPS certificate.
What's the story with the GPL-3.0 license and the ported code?PlayPort is GPL-3.0 and its AirPlay, iAP2 and accessory-authentication code is ported from the GPL-3.0 projects DiPlay and xcertplay, though PlayPort itself is an independent project not affiliated with their authors.
What can I actually control from the browser once connected?Touch with up to two contacts, a keyboard rotary knob, Home/Back/Select buttons, media transport, Siri, answering/ending/muting calls, day/night mode, and live display/audio settings.
Does it support H.265/HEVC video?Yes, if your browser can decode HEVC (checked via VideoDecoder.isConfigSupported) you can switch to it for sharper video at the same bitrate; otherwise it falls back to H.264, and the phone has the final say on the codec.
Why doesn't PlayPort just ship the MFi identity so setup is simpler?The certificate/key pair is Apple-issued third-party key material that PlayPort isn't licensed to redistribute, so you have to get your own, for example from a DiPlay release, a dumped Carlinkit dongle firmware, or real MFi credentials.
What happens if the iPhone's Bluetooth/Wi-Fi link drops?A connection supervisor detects the drop and re-invites the phone automatically to restore the Bluetooth/Wi-Fi handoff without manual reconnecting.
Creator
youcci
Size
0.9 MB
Stars
283
Updated
CommitOct 01
Age
16d
Commits · 90 Days