CNA-BUG-138: MediaPlayer flags ActiveSongChanged on every Play(Song*), even for the same song, and never for Play(const SongCollection&[, index])

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

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.

Play(Song*) compares the queue's copy with the caller's pointer, so the event fires on every call, while playing a collection never raises it even when the active song changes.

Identifier
CNA-BUG-138
Category
Bug
Subsystem
Audio & media
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Low (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::ActiveSongChanged as raised by MediaPlayer::Play(Song*), Play(const SongCollection&) and Play(const SongCollection&, intcs)

Expected behaviour

MediaPlayer.hpp documents the event as “Raised when the active song changes”. XNA raises it from the native player whenever its active song changes (the Media_ActiveSongChanged managed call dispatched by FrameworkDispatcher, decompiled reference), which includes starting a collection; whether XNA raises it when the same song is replayed is decided in native code the managed IL does not show. FNA's Play(Song), which CNA's code transcribes, raises it only when previousSong != song under Song's overloaded operator, that is, when the file differs.

Actual behaviour at TARGET

  • Play(Song*) in MediaPlayer.cpp keeps Song* previousSong = queue_[0] and flags the event when previousSong != song. The queue holds copies made by LoadSong, so a raw pointer comparison with the caller's Song is always unequal (except in the use-after-free case of the entry on queue-owned songs): replaying the same song raises ActiveSongChanged every time. CNA's Song::Equals compares handles as FNA's operator does, but is not used here.
  • Play(const SongCollection&, index) clears and refills the queue and calls PlaySong without setting FrameworkDispatcher::ActiveSongChanged. Only MediaStateChanged can fire, and only if the state changes, so switching from one playlist to another while music plays raises neither event. NextSong and the end-of-queue path in Update do set the flag. FNA's overload has the same omission.

Source locations

Evidence

Checked by reading at 009d40f5; not executed. FNA was read at FNA-XNA/FNA b3551247 (MediaPlayer.cs, Song.cs); XNA's dispatch path from the decompiled FrameworkDispatcher and the native callback shim.

Focused reproduction

Illustrative; not compiled or run.

Song a("Content/a.ogg", "a");
MediaPlayer::Play(&a);
FrameworkDispatcher::Update();   // ActiveSongChanged (expected)
MediaPlayer::Play(&a);
FrameworkDispatcher::Update();   // ActiveSongChanged again: same file (FNA: no event)
MediaPlayer::Play(otherPlaylist);
FrameworkDispatcher::Update();   // no ActiveSongChanged although the song changed

Current tests

ActiveSongChangedAndMediaStateChangedFireThroughFrameworkDispatcherUpdate in MediaPlayerTests.cpp covers one Play(Song*) from an empty queue. No test replays a song or plays a collection and checks the event.

Regression test

Two dispatcher-driven cases: replaying the same Song raises no ActiveSongChanged (compare with Equals), and Play(collection) while another song is active raises it once.

Blast radius

Games that update a now-playing display or statistics from ActiveSongChanged. Playback itself is unaffected.

Workaround

Track the active song in the game (compare getActiveSongProperty() handles each frame) instead of relying on the event.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index