Camera is off.
Game capture needs Chrome or Safari on a computer. This browser can’t capture a window.
Pick BlackHole here to carry the game’s sound. See Setup.
Holds the game’s sound back so it lines up with the picture. See Setup for a tap test.
Sessions
Every recording is kept here, with its marks. Save sends the video and a marks file to Files, where you pick iCloud Drive.
Relay
A web page can’t send RTMP. This page sends one WebRTC (WHIP) stream to your relay, and the relay fans it out over RTMP to whichever platforms you’ve set up below — Twitch, Instagram, YouTube and TikTok, all four at once if you want, each running on its own. Without a relay, Record only still works.
Saved on this device only.
What each platform needs
- Twitch
- Create an account at twitch.tv — no follower or age minimum. Stream key is under Creator Dashboard → Settings → Stream. It doesn’t change, so you only enter it once. Twitch also has an undocumented beta WHIP endpoint; this app doesn’t use it.
- YouTube
- Needs a Google account with a channel enabled for live streaming at youtube.com/live_dashboard, and you must be 16+. The first time, YouTube says it can take up to 24 hours to turn on. After that, the Stream URL and key are in YouTube Studio → Create → Go live → Stream, and they stay the same every time — like Twitch, once is enough.
- TikTok
- 1,000+ followers and age 18+, and TikTok wants a track record streaming from its mobile app first. Even then, applying for LIVE Studio / stream-key access (in the app’s Creator Tools) isn’t guaranteed to be approved. The server URL and key come from LIVE Producer on a computer and change every stream.
- A Creator or Business account — switch under Settings → Account type and tools. On instagram.com from a computer: Create → Live video shows a server URL and key. They change every stream. Personal accounts can’t.
So for TikTok and Instagram, run one command on the relay with the fresh URL and key before each stream; the steps are in the recipe below. Twitch and YouTube only need that once. You can run all four at the same time — each is its own push on the relay, so starting or stopping one never touches the others — just mind the relay’s CPU and upload bandwidth as you add destinations (see “Before you rely on it” below).
Relay recipe (MediaMTX, Caddy, ffmpeg)
Serve this page from the relay itself with Caddy, so the page and the WHIP endpoint share one origin and one certificate. Replace stream.example.com with your domain everywhere. Enter https://stream.example.com/live/whip as the WHIP URL above, and the value of WHIP_TOKEN as the Bearer token.
Open TCP 22, 80 and 443, and UDP and TCP 8189. Never open 8889 or 8554. Each platform is its own systemd service, so changing one key restarts only that push and leaves the ingest and the recordings running. MediaMTX can’t push RTMP itself, so ffmpeg does it.
/opt/mediamtx/mediamtx.yml
logLevel: info
api: no
rtmp: no
hls: no
srt: no
rtspAddress: 127.0.0.1:8554
webrtc: yes
webrtcAddress: 127.0.0.1:8889
webrtcEncryption: no
webrtcAllowOrigins: ['https://stream.example.com']
webrtcLocalUDPAddress: :8189
webrtcLocalTCPAddress: :8189
webrtcAdditionalHosts: ['stream.example.com']
webrtcICEServers2:
- url: stun:stun.cloudflare.com:3478
authInternalUsers:
- user: any
pass:
ips: ['127.0.0.1', '::1']
permissions:
- action: publish
path: live
- action: read
path: live
paths:
live:
record: yes
recordPath: /var/lib/stream/recordings/%path/%Y-%m-%d_%H-%M-%S-%f
recordFormat: fmp4
recordPartDuration: 1s
recordSegmentDuration: 1h
recordDeleteAfter: 7d
Caddyfile (set WHIP_TOKEN in Caddy’s service environment)
stream.example.com {
@whip path /live/*
handle @whip {
@authed header Authorization "Bearer {$WHIP_TOKEN}"
handle @authed {
reverse_proxy 127.0.0.1:8889
}
respond 401
}
handle {
root * /var/www/stream
file_server
}
}
/opt/stream/push.sh
#!/bin/bash
exec ffmpeg -hide_banner -loglevel warning -rtsp_transport tcp \
-i rtsp://127.0.0.1:8554/live -r 30 \
-c:v libx264 -preset veryfast -tune zerolatency -b:v 3000k -maxrate 3000k -bufsize 6000k \
-g 60 -keyint_min 60 -sc_threshold 0 -pix_fmt yuv420p \
-c:a aac -b:a 128k -ar 48000 -f flv "${URL%/}/${KEY}"
/etc/systemd/system/dest@.service
[Unit] Description=Push to %i After=mediamtx.service StartLimitIntervalSec=0 [Service] User=stream EnvironmentFile=/etc/stream/%i.env ExecStart=/opt/stream/push.sh Restart=always RestartSec=2
/usr/local/bin/dest (create /etc/stream first, then chmod +x)
#!/bin/bash
case "$1" in
set) read -rp "Server URL: " u; read -rsp "Stream key: " k; echo
printf 'URL="%s"\nKEY="%s"\n' "$u" "$k" > /etc/stream/$2.env
chown root:stream /etc/stream/$2.env; chmod 640 /etc/stream/$2.env
systemctl enable --now dest@$2; systemctl restart dest@$2 ;;
off) systemctl disable --now dest@$2; rm -f /etc/stream/$2.env ;;
esac
Each stream
- Start the stream in this app.
- On a computer, copy the fresh server URL and key from the platform’s producer page.
- Run ssh -t relay sudo dest set instagram (swap in tiktok, twitch or youtube) and paste both when asked. Repeat for every platform you want live right now; they run independently. Do this once for Twitch and YouTube and leave them running — their keys don’t change.
- Press Go live on each platform once its preview appears.
- Afterwards run sudo dest off instagram (or tiktok) for the one-time keys. Leave Twitch and YouTube running if you’ll reuse the same key next time.
Before you rely on it
- Run MediaMTX once in the foreground first. It rejects config keys it doesn’t know, and recent releases renamed some of them.
- Re-encoding two destinations at 720p30 should fit on 2 vCPUs, but that is untested, and all four at once will want more — size up and watch load average the first time you try it. A 1 vCPU plan only suits -c:v copy. Check the provider’s live price.
- Each destination is a separate re-encoded upload from the relay, so four at once needs roughly four times one destination’s bitrate in upload bandwidth — check your provider’s uplink, not just the download speed.
- At home you need UDP and TCP 8189 plus 80 and 443 forwarded, no CGNAT, and dynamic DNS.
- If audio is missing, read the MediaMTX log. Version 1.15.5 reported Opus errors with mono audio (issue 5293).
Game mode on a Mac
- macOS Sequoia or later, iPhone Mirroring on, iPhone locked and nearby.
- Open this page in Chrome or Safari on the Mac. Choose Game + camera and pick the iPhone Mirroring window.
- Camera: the Mac’s, or the iPhone through Continuity Camera.
- Game sound plays out of the Mac and browsers can’t capture it. Install BlackHole 2ch, make a Multi-Output device (speakers + BlackHole) in Audio MIDI Setup, set it as the Mac’s output, then choose BlackHole as Game audio input. Your microphone stays separate.
- Set BlackHole, the Multi-Output device and your microphone to 48,000 Hz in Audio MIDI Setup. In the Multi-Output device, make the physical output the clock source and tick Drift Correction on BlackHole.
- To line up sound and picture, record 10 seconds of tapping on the game screen, step through it in a frame-stepping player, and enter the gap in Game audio delay. Repeat after 30 minutes to check for drift. The delay can only hold the sound back, not bring it forward.
- Keep this tab visible, or on a second display, while you play.
Limits
- iPhone Safari can’t capture the phone’s own screen or another app’s sound. Streaming a game played on the phone itself needs a native app, or the Mac route above.
- On the phone the screen must stay on with this page in front. Locking it or switching apps stops the camera and the recording.
- Recording is written to browser storage every couple of seconds. A crash loses the last few seconds, and the session reappears under Sessions marked Interrupted. Chrome’s MP4 mode delivers its first media only after about 10 seconds, so a crash before then loses the whole recording; on Chrome this page records WebM instead. Save to Files soon after any stream, because browsers can clear storage when space is short.
- Keyboard: M adds a mark. Control+Shift+S ends the recording, even if the End button misbehaves. Pressing End (or that shortcut) a second time while it says Finishing forces it to finish at once. Shortcuts only work while this tab has keyboard focus.
- If the browser runs out of room during a recording, the page warns you, keeps recording from memory, and End still works. Keep the tab open, press End, and Save straight away. Closing the tab before saving loses whatever was held only in memory.