Skip to content

fix(audio): don't report a stopped playback as a decode error - #6723

Merged
longcw merged 3 commits into
mainfrom
longc/background-audio-stop-decode-error
Aug 6, 2026
Merged

fix(audio): don't report a stopped playback as a decode error#6723
longcw merged 3 commits into
mainfrom
longc/background-audio-stop-decode-error

Conversation

@longcw

@longcw longcw commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Problem

A stop on a BackgroundAudioPlayer clip writes error decoding audio to the log. PyAV raises av.error.InvalidDataError or EOFError from av.open. The stop is intentional and the audio file is correct. A clip that loops hits this often, because _loop_audio_frames opens a new container for each iteration.

When the input closes, the decode thread can still read the container header. Two paths close it. audio_frames_from_file cancels the reader task, and the finally block of the reader calls decoder.end_input(). PyAV then reads a part of the file as a complete container. aclose() closes the StreamBuffer while the thread reads it. _decode_loop reports both windows through one except Exception block.

Fix

A cancelled read no longer signals an end of input. aclose() marks the decoder closed before it closes the input. _decode_loop writes no error after that flag is set. The WAV path already had this guard.

Real decode errors stay visible. Garbage input, a corrupt file, and a missing file all report as before.

This change keeps #3863 open. The other reports there come from a mime and encoding mismatch. That is a real decode error, and the log keeps it.

Stopping a BackgroundAudioPlayer clip logs "error decoding audio" with
an av.error.InvalidDataError or EOFError raised from av.open. The stop
is intentional and the audio is fine.

The decode worker thread can still be probing the container when the
input is torn down, and there are two windows for that.
audio_frames_from_file cancels its reader task first, and the reader's
finally calls decoder.end_input(), so a half-read file reaches PyAV as a
complete container. aclose() then closes the StreamBuffer out from under
the thread, which is a second, smaller window. _decode_loop reports both
through a blanket except Exception.

A cancelled read now signals an abort instead of an end of input,
aclose() marks the decoder closed before tearing the input down, and
_decode_loop skips the log once that flag is set. The WAV inline path
already had the same guard.

Genuine decode failures are unaffected: garbage input, a corrupt file
read to completion, and a missing file all still report as before.
@longcw
longcw requested a review from a team as a code owner August 6, 2026 01:58

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

longcw added 2 commits August 6, 2026 10:28
…hread

The 120-iteration stop/start sweep asserted that no decode error is logged
across a timing window. It passed on macOS and on Linux in a container, and
failed only on the CI runners, where it also caught probe failures that the
close guard is not responsible for.

Assert the contract the fix establishes instead: a cancelled read must not
signal an end of input. Holding the reader mid-file with a slow push makes
that deterministic, and it still fails against the unpatched reader.
@longcw
longcw merged commit aaf3cd9 into main Aug 6, 2026
23 checks passed
@longcw
longcw deleted the longc/background-audio-stop-decode-error branch August 6, 2026 04:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants