Skip to content

tv-native: async mpv client API — fix tvOS main-thread deadlock (#30) + player observability (#31) - #34

Merged
Quixomatic merged 5 commits into
mainfrom
mpv-player-async
Sep 5, 2026
Merged

tv-native: async mpv client API — fix tvOS main-thread deadlock (#30) + player observability (#31)#34
Quixomatic merged 5 commits into
mainfrom
mpv-player-async

Conversation

@Quixomatic

Copy link
Copy Markdown
Owner

Summary

Completes the port from plezy's MpvPlayerCoreBase to fix the tvOS main-thread ⇄ mpv vo-thread deadlock (#30) and add the player observability that made it invisible (#31).

Our Apple mpv core called libmpv synchronously on the calling thread, and Expo view prop setters run on the main thread. On tvOS the avfoundation VO must dispatch_sync to the main queue to touch its AVSampleBufferDisplayLayer; when that coincided with a main-thread mpv call, the two deadlocked — a multi-minute freeze that tvOS killed with 0x8BADF00D. This was a port shortcut present since v0.7.19 (we kept plezy's structure but dropped its async client API), not a deliberate choice.

What changed

The hybrid single-engine model, tvOS HDR display-criteria, 5.1/AVAudioSession, DVR clock, and load coalescing are untouched.

Verification

Apple TV dev build (development-tvos, via Xcode): direct-play at offset, DVR seeks, program rollovers, the full 49-clip caps diagnostic, 4K HEVC/TrueHD at offset, and subtitles — all clean, no regressions, no freeze. iPad is shared code.

Caveat: the #30 deadlock only manifests on the HEVC-copy + E-AC3→mp3 HLS transcode path, which can't be reproduced on a direct-play LAN setup — real-world confirmation comes from the reporter once this reaches a store/TestFlight build. The fix is structural, so shipping to get field confirmation is the plan.

Design + on-device matrix: .plans/mpv-async-refactor.md.

Fixes #30
Fixes #31

… + mpv logging #31

Apple TV / iPad no longer freeze on a main-thread ⇄ mpv deadlock (GitHub #30). The Apple mpv core called
libmpv synchronously on the calling thread, and the Expo view prop setters run on the main thread; on tvOS
the avfoundation VO must dispatch_sync to the main queue to touch its display layer, so a main-thread mpv
call could deadlock it — a multi-minute freeze that tvOS killed with 0x8BADF00D. Both Apple cores now use
libmpv's async client API (mpv_command_async / mpv_set_property_async + a request-id → reply table), which
never blocks the caller, so the deadlock is structurally impossible. Completes the port from plezy's
MpvPlayerCoreBase (structure was ported but sync calls kept since v0.7.19).

Added libmpv logging (#31): both cores request mpv log messages (verbose debug / warnings on store builds)
and emit via os_log so a shipped app's player log is visible in Console.app; print() calls replaced.

Changed avfoundation-composite-osd=no (subtitles are server-side; reduces VO↔main coupling; matches plezy).

Native change — verify on device (Apple TV + iPad) before release. Matrix + #30 repro in
.plans/mpv-async-refactor.md.
…otgun-proof)

scripts/bump-version.ts + a `version:bump` root script set every Airwave version file in lockstep from one
command: all apps/*/package.json (discovered), appinfo.json, app.json, tauri.conf.json, Cargo.toml,
Cargo.lock, and the roku manifest. Every edit is targeted (JSON version key / manifest version lines / the
airwave package line via its name anchor), so a dependency crate (phf, reqwest, …) can never be bumped by
accident. Refuses to run when files are out of sync; --dry-run previews. Edits version files only — the
changelog + commit + push stay with /version-bump.
One narration line per scene, timed to each scene's runtime — the source for the TTS clips we'll wire into
tools/promo next.
…telemetry, #31)

The channel player's tick now watches liveness: once a program is playing (baseline anchored, not paused,
not buffering), if no mpv progress event arrives for 12s the native player has frozen (e.g. the tvOS mpv
deadlock, #30) and one PlaybackLog row is posted with outcome "stalled" + the freeze detail. Previously the
only post-load telemetry was a one-shot 6s watchdog, so a freeze after playback started recorded nothing.
The JS thread + networking keep running during a native main-thread wedge (the heartbeat kept flowing in the
#30 crash), so JS is the only layer that can see it. Re-arms on progress. outcome is a free string server-
side, so no migration.
The mechanical file edits now go through scripts/bump-version.ts (added v0.13.5): the skill uses
`pnpm version:bump <tier> --dry-run` for the sync check and `pnpm version:bump <tier>` to bump all 15 version
files, then writes the changelog + commits + pushes as before. Documents the Cargo.lock global-sed footgun as
the reason the script exists, and the never-tag-without-James rule.
@vercel

vercel Bot commented Sep 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
airwave-site Ready Ready Preview Sep 5, 2026 6:13pm UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant