CNA-BUG-140: MediaPlayer::Play reports no error when a song cannot be played and can leave the player Playing with no track
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
A mixer load, track or start failure makes PlaySong return silently after the previous track was destroyed, so the state may stay Playing indefinitely, and a vanished file or a missing audio device throws non-XNA exceptions after the queue was replaced.
- Identifier
CNA-BUG-140- Category
- Bug
- Subsystem
- Audio & media
- Status
- Open
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Severity
- Medium (a triage suggestion, not a project priority)
- Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- MediaPlayer::Play(Song*), Play(const SongCollection&), Play(const SongCollection&, intcs), MoveNext and MovePrevious: failure behaviour
Expected behaviour
XNA's MediaQueue.Play(Song) throws InvalidOperationException (SongPlaybackFailed) when the native player cannot play the song (decompiled reference). Whatever CNA chooses, a failed Play should leave a consistent player: either an exception and the previous state, or Stopped. MediaPlayer.hpp documents no failure mode.
Actual behaviour at TARGET
Three failure paths in MediaPlayer.cpp:
- Undecodable or unstartable song, silently. In a build with a mixer,
PlaySongfirst destroys the current music track and audio and clears the song-ended flag, then returns without an error ifLoadMixerAudioFileyields nothing (an unsupported format, a corrupt file), if no track can be created or bound, or ifPlayMixerTrackfails. The timer and state are not touched: from Stopped the player stays Stopped with the song queued as active; from Playing it stays Playing with no track,PlayPositionkeeps counting from the previous song, andUpdatenever advances because the ended flag can no longer be set; from Paused, a laterResumereports Playing with nothing playing. Neither mixer's error text reaches the caller. - No audio device: a raw std::runtime_error. The music load is the first mixer use in a game that plays music before any sound effect. On SDL3 audio
LoadMixerAudioFilereachesGetMixer, on ALSAEnsureMixer, and either throwsstd::runtime_errorout ofPlayafter the queue was replaced.SoundEffect's constructors convert the same failure toNoAudioHardwareException; MediaPlayer does not. - Vanished file.
LoadSongcopies the song withnew Song(handle, name), whose constructor (Song.cpp) throwsFileNotFoundExceptionif the file no longer exists. The copy happens afterqueue_.Clear(), so the exception leaves an empty queue (or a partly refilled one for a collection) while the previous track keeps playing in the Playing state. FNA'sLoadSonghas the same shape.
Source locations
modules/media/src/Xna/MediaPlayer.cpp— MediaPlayer::PlaySong failure returns; Play, LoadSong, Updatemodules/audio/src/Backend/Sdl3Mixer/MixerEngine.cpp— LoadMixerAudioFile / FinalizeAudioLoad: empty result, or GetMixer's exceptionmodules/audio/src/Backend/CnaMixer/MixerEngine.cpp— LoadMixerAudioFile: EnsureMixer, then an empty result with the error kept in the enginemodules/audio/src/Backend/Sdl3Mixer/AudioMixer.cpp— GetMixer throws std::runtime_error when the device or mixer cannot be createdmodules/media/src/Xna/Song.cpp— Song::Song(std::string, std::string): FileNotFoundException for a missing filemodules/audio/src/Xna/SoundEffect.cpp— EnsureMixerOrThrowXna: the NoAudioHardwareException conversion MediaPlayer lacks
Evidence
Checked by reading at 009d40f5; not executed. The XNA behaviour was read from the decompiled MediaQueue.Play; FNA's LoadSong from FNA-XNA/FNA b3551247. The ALSA mixer records a decode error string in its engine that nothing in the media module reads.
Focused reproduction
Illustrative; not compiled or run (SDL3 audio, vendored SDL3_mixer).
Song music("Content/music.ogg", "music");
Song voice("Content/voice.opus", "voice"); // Opus is not decoded by this build
MediaPlayer::Play(&music); // Playing
MediaPlayer::Play(&voice); // music stops, nothing plays, no exception
MediaPlayer::getStateProperty(); // still MediaState::Playing, indefinitely
Current tests
MediaPlayerTests.cpp plays only decodable fixtures with SDL_AUDIODRIVER=dummy; its own comment on VisualizationEnabledStateStaysConsistentWithGetVisualizationData notes that mixer creation therefore never fails in the suite. No test plays an undecodable or deleted file or runs without an audio device.
Regression test
With a song playing, Play an undecodable file (a zero-byte .ogg is enough) and require a defined outcome: an InvalidOperationException with the previous song still active, or Stopped, never Playing without a track. Add the same for a file deleted between constructing the Song and calling Play.
Blast radius
Games playing unsupported formats (see CNA-BUG-052), corrupt or deleted files, and games started on machines without an audio output device (SDL3 and ALSA builds). Music loaded from decodable files on a working device is unaffected. In builds without a mixer (SDL2 or NULL audio) PlaySong has no load step, so only the vanished-file path applies there.
Workaround
Use the four decodable formats, and check MediaPlayer::getStateProperty() together with a game-side timer rather than trusting Playing; catch std::exception around the first Play.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.