fix(akouo): playback-engine correctness (real seek, decode-failure signaling) - #485
Merged
Conversation
…gnaling) - #386: real Engine::seek via a watch channel to the decode task, awaiting the decoder's actual post-seek position (was a fabricated SeekCompleted). - #387: the DSP ring-buffer emits EngineEvent::Error and stops instead of livelocking when a decoded frame exceeds ring capacity. - #398: real Android seek (mirrors #386). - #399: Android subscribe_events returns an id and gains an unsubscribe path so the listener list no longer grows unbounded. - #400: notify() snapshots listener handles under the lock, then invokes the foreign callbacks after releasing it, so a reentrant subscribe/unsubscribe no longer deadlocks the non-reentrant std mutex. - #401: PlaybackStarted is emitted before the decode/dsp tasks are spawned, so it can no longer be reordered after Error/TrackEnded. - #402: the decode->DSP channel carries DecodeOutcome{Frame,Eos,Failed}, so a decode failure is distinct from end-of-stream and never emits TrackEnded. - #403: all blocking decode/probe file I/O moves off the async executor into a dedicated blocking path (decode/blocking.rs). - #404: cpal output-stream errors surface as EngineEvent::Error via an error channel instead of being only logged. Closes #386 Closes #387 Closes #398 Closes #399 Closes #400 Closes #401 Closes #402 Closes #403 Closes #404 Gate-Passed: kanon 0.1.5 +stages:fmt,check,clippy,nextest,lint sha:af979dfe9cc18e05d2fd3cf11af3d3b3314cbd13
forkwright
force-pushed
the
fix/akouo-playback-engine
branch
from
July 1, 2026 22:42
bc2809b to
abb3bfe
Compare
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.
Closes #386, #387, #398, #399, #400, #401, #402, #403, #404.
Changes
seek()firesSeekCompletedand returns without moving playback — silent no-op stub #398 — real seek.Engine::seek(and the Androidseek) were stubs that fabricatedSeekCompletedand echoed the requested position. Both now forward a command to the decode task, call the decoder's real seek, and return the decoder's actual post-seek position; a generation counter drops stale pre-seek frames.DecodeOutcome{Frame,Eos,Failed}, so a decode error (or open failure) is distinct from end-of-stream and never emitsTrackEnded.decode/blocking.rsruns each decoder on a dedicated thread (works on the current-thread runtime, whereblock_in_placewould panic), andprobe_codecis wrapped inspawn_blocking. Regression-proven with yields-to-executor tests.event_listenersmutex while invokingon_event, deadlocking on reentrantsubscribe_events#400 — reentrant-notify deadlock.notify()snapshots listener handles under the lock and invokes the foreign callbacks after releasing it (the std mutex is not reentrant).Error+ stops instead of spinning.subscribe_eventshas no unsubscribe API — listener list grows unboundedly across Activity lifecycle #399 — unsubscribe. Androidsubscribe_eventsreturns an id and gainsunsubscribe_events(bounded listeners).PlaybackStartedis emitted before the decode/dsp tasks spawn.EngineEvent::Errorvia an error channel.Verification
kanon gate --fullgreen;cargo test --workspacepasses (23 binaries). akouo-android compiles + tests host-side (UniFFI scaffolding); Android NDK cross-compile is not exercised on this host.Notes
Adds an
OutputConfig.enabledflag (default true) so headless decode/analysis works without an audio device — a small design addition beyond the nine issues. The f64 DSP hot path allocation profile is unchanged.