dispose method
Tears down the player.
The teardown order is load-bearing. The sequence is:
- Flip
_disposedso any subsequent setter or public-API call fails fast via_checkNotDisposed(). Before any await, continue the pending hooks and end the live PCM sources (a reader blocked on one wakes up), then release the OS media session. - Await bring-up, then drop the
OrphanHandleTrackerentry so a hot-restart that fires before the destroy completes doesn't try to clean up a handle we're already cleaning up, and dispose the DSP pipelines (each turns off what it armed). - Send the
quitcommand to mpv. mpv processes it asynchronously and firesMPV_EVENT_SHUTDOWNinside its own event queue. requestStop(stop flag +mpv_wakeup) so the event loop unwinds deterministically even ifquitwas dropped orMPV_EVENT_SHUTDOWNis delayed by a stalled hook. Then dispose the filter taps, whose streams the app listens to directly.- Await
_eventIsolate.stop(), which returnstrueonly once the isolate has ACTUALLY exited, so it is provably offmpv_wait_event. Then wait (bounded) for anyregisterHookstill runningmpv_hook_add, and close the client, which settles every in-flight call. mpvTerminateDestroy(_handle)ONLY when the exit and the hook registrations are confirmed, in a throwaway isolate and awaited (it waits for the core to shut down and the output to close), then release the heldcontent://descriptors. Destroying the handle while the isolate is still insidempv_wait_eventwould race the event-loop thread and crash libmpv when the handle is freed mid-syscall; on an unconfirmed exit we skip destroy and let the OS reclaim the handle.- Close all reactive properties + controllers. Order within
this group does not matter for correctness (they tolerate
close()while a listener is attached); grouping by ownership is for auditability. The closes are not awaited: an active listener getsdoneright after dispose completes, a paused one when it resumes.
Idempotent AND await-safe: every call, including concurrent ones,
returns the same teardown future, so a second await dispose()
completes only when the teardown has actually finished (a bare
_disposed early-return would let the second caller proceed while
native resources are still being released).
Implementation
Future<void> dispose() => _disposeFuture ??= _disposeImpl();