Skip to content

fix(audio): do not reopen a missing music track on every frame - #713

Closed
alandarev wants to merge 1 commit into
sven-n:mainfrom
alandarev:fix/music-missing-track
Closed

alandarev wants to merge 1 commit into
sven-n:mainfrom
alandarev:fix/music-missing-track

Conversation

@alandarev

Copy link
Copy Markdown
Contributor

Summary

Scenes request their music every frame and rely on PlayMusic() doing nothing
while that track plays. A track that cannot be opened never becomes the current
one, so every request opened the file again — the decoder probe ran on the main
thread and another failed to init stream line went to the log on every frame,
as #584 describes (and on macOS the synchronous stream init could hang the main
thread).

MiniAudioBackend now remembers the last track that failed and skips plain
requests for it. It is tried again when another track is requested in between,
when the request is enforced, or after all music or that track was stopped
(turning the music off and on stops all of it); the per-frame stops of other
maps' tracks leave it alone. The destructor's Shutdown() call is qualified so
Cppcheck's virtualCallInConstructor is satisfied for the touched file
(behaviour unchanged).

Related issues

Closes #584.

Testing

  • Two unit tests with a no-device engine (tests/core/test_audio_playback.cpp):
    a missing track is not reopened by plain requests or other tracks' stops, and is
    reopened after another request, an enforced request or a stop. The first fails
    without the fix.
  • ctest: 455/455; clang-format 21.1.8 and Cppcheck with the CI options: no findings.
  • In the game with music on and no Data/Music folder, logging in and visiting
    Lorencia, Devias and Noria: before, 307–350 failed to init stream lines per
    track; after, one per track and visit.

Written with AI assistance and tested by hand.

Checklist

Scenes request their music every frame (the login scene, the map themes)
and rely on PlayMusic() doing nothing while that track plays. A track that
cannot be opened never becomes the current one, so every request opened
the file again, ran the decoder probe on the main thread and wrote another
"failed to init stream" line to the log - every frame, for as long as the
scene was shown.

MiniAudioBackend now remembers the last track that failed and skips plain
requests for it. It is tried again when another track was requested in
between, when the request is enforced, or after all music or that track
was stopped (turning the music off and on stops all of it). The per-frame
stops of other maps' tracks leave it alone.

Seen with music on and no music files, login then Lorencia, Devias and
Noria: before, 307-350 errors per track; after, one per track and visit.

The destructor now calls MiniAudioBackend::Shutdown() qualified (Cppcheck
virtualCallInConstructor on the touched file; behaviour unchanged).
@alandarev
alandarev marked this pull request as ready for review October 13, 2026 06:24
@alandarev

Copy link
Copy Markdown
Contributor Author

Closing as superseded by 122cc57, merged into main via #610. Upstream now keeps failed music tracks cached for the audio session, including named stop/replay loops in Balgass Barracks/Refuge and Cursed Temple. This also covers the edge case identified in this PR, so keeping upstream’s stronger implementation instead of duplicating it.

@alandarev alandarev closed this Oct 13, 2026
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.

Missing Data/Music: PlayMusic() is retried on every frame and can livelock miniaudio's resource manager (main thread frozen on macOS)

1 participant