tv-native: async mpv client API — fix tvOS main-thread deadlock (#30) + player observability (#31) - #34
Merged
Merged
Conversation
… + 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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Completes the port from plezy's
MpvPlayerCoreBaseto fix the tvOS main-thread ⇄ mpvvo-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
avfoundationVO mustdispatch_syncto the main queue to touch itsAVSampleBufferDisplayLayer; when that coincided with a main-thread mpv call, the two deadlocked — a multi-minute freeze that tvOS killed with0x8BADF00D. 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
MpvCore.swift+MpvAudioCore.swift→ libmpv async client API (mpv_command_async/mpv_set_property_async+ a request-id → reply table drained byMPV_EVENT_*_REPLY). Submits return immediately and never block the caller, so the deadlock is structurally impossible. mpv preserves submission order, so the load-then-reset sequence is unchanged. (tvOS: main thread and mpvvothread deadlock — freeze, then 0x8BADF00D watchdog kill (two crash logs, identical stacks) #30)os_log(mpv_request_log_messages+MPV_EVENT_LOG_MESSAGE, verbose in debug / warnings on store builds), visible in Console.app off a retail device. Remainingprint()s replaced. (Player observability: a mid-program freeze is invisible — telemetry is a 6s post-load watchdog, and libmpv logging is never enabled #31)avfoundation-composite-osd=no— subtitles are selected/burned server-side (Plex), so mpv's per-frame OSD compositing isn't our render path; disabling it reduces VO ↔ main coupling (matches plezy + streamyfin).use-tv-player.ts— once a program is playing, if no progress event arrives for 12s the native player has frozen; posts onePlaybackLogwith outcomestalled. The JS thread + networking keep running during a native wedge (the heartbeat kept flowing in the tvOS: main thread and mpvvothread deadlock — freeze, then 0x8BADF00D watchdog kill (two crash logs, identical stacks) #30 crash), so this is the only layer that can see it. (Player observability: a mid-program freeze is invisible — telemetry is a 6s post-load watchdog, and libmpv logging is never enabled #31)pnpm version:bump(scripts/bump-version.ts) — lockstep, targeted version bumps across all 15 version files (no more hand-sed / Cargo.lock footgun) — and the/version-bumpskill now points at it. Plus the promovo-script.md.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