Skip to content
The CNA BibleCNA 0.1.0-alpha.1 Edition

Chapter 46 The Media System

Media is no longer the shell-heavy namespace described by CNA’s older README count. At the pinned revision it contains file-backed songs and libraries, playlist parsing, queued music playback, visualization data, picture libraries, and FFmpeg video on the platforms where FFmpeg is enabled. This chapter states those behaviors by subsystem; it does not replace the stale README count with another undated test total.

Audio and Media form an intentional static-library cycle. FrameworkDispatcher in Audio advances MediaPlayer, while Media sends song playback to the audio mixer. CMake repeats the archives to resolve that link cycle. Removing either edge would require reassigning the shared dispatcher and playback responsibilities.

46.1 Song contract

Song can be constructed from a path or discovered by a MediaLibrary scan. A directly constructed song has null, non-owning Album, Artist, and Genre links; a scan supplies those links from the library hierarchy. IsRated and the 0–10 Rating are derived from ID3v2 POPM or Vorbis RATING tags. A source rating of zero means “unrated.” IsProtected is always false because protected files are not indexed.

Duration and PlayCount are mutable. Playback replaces the duration with the decoder-reported value, and MediaPlayer::Stop() resets play counts across the current queue. Handle is a CNAEXT path/handle accessor. Equals compares handles; GetHashCode hashes the resolved handle. The latter deliberately avoids FNA’s identity-hash inconsistency for two equal songs.

1 std::unique_ptr<Song> intro(
2 Song::FromUri("Intro Theme", "Content/Audio/intro.ogg"));
3 MediaPlayer::Play(intro.get());
4 // MediaPlayer clones the Song; the caller still owns intro.

The fragment assumes the Media header, <memory>, and the applicable namespaces.

Historical note. FNA is not the complete API oracle FNA’s Song omits Album, Artist, Genre, and ToString() from the XNA 4.0 surface. Repeated FNA-only reviews missed the difference; comparison with the pinned xna4-spec data found it. Chapter 76 explains why API parity with FNA and with XNA are separate claims.

Limitation. Advertised loose-file extensions The ContentManager song reader advertises .aac and .wma, but CNA’s vendored SDL3_mixer has no decoder for either format. The MediaLibrary scanner omits them. A loose-file resolution match therefore establishes that a Song can name the file, not that the mixer can decode it. MP3, Ogg, WAV, FLAC, and Opus follow the installed decoder routes.

46.2 MediaLibrary and metadata

MediaLibrary scans the host music and picture directories returned by SDL_GetUserFolder; a CNAEXT override makes tests independent of the CI account’s files. It builds SongCollection, Album, Artist, and Genre relationships, plus the parallel picture hierarchy. SavePicture() creates a “Saved Pictures” node on first use.

The in-tree metadata parser handles:

  • Ogg Vorbis comments and Ogg Opus OpusTags;

  • ID3v2.3/2.4 title, artist, album, genre, track, rating, and picture frames;

  • Latin-1, UTF-16 with byte-order mark, UTF-16BE, and UTF-8 ID3 text;

  • native FLAC VORBIS_COMMENT and picture blocks;

  • .m3u and .m3u8 playlists as UTF-8.

Files without readable tags fall back to filename, parent directory, and grandparent directory for title, album, and artist. Missing playlist entries are skipped. Album art comes from cover.jpg/folder.jpg or embedded ID3/FLAC pictures; the front-cover picture type wins when present.

The conversion from ID3’s 0–255 rating and the various informal Vorbis rating scales to XNA’s 0–10 property is CNA policy, not an XNA rule. Likewise, scans are synchronous snapshots; there is no MediaLibrary::Refresh().

Historical note. Testing values across layer boundaries The tag parser once read TRCK correctly while MediaLibrary discarded the value when constructing Song. A fixture with several distinct track numbers exposed the drop; a parser-only test could not. The current integration fixture checks the values after library construction.

46.3 Playback and queue ownership

MediaPlayer supplies song and collection playback, pause, resume, stop, shuffle, repeat, queue navigation, events, and visualization. Playlist is read-only, as on XNA. MediaQueue::Add(Song*) adopts its pointer, whereas SongCollection is non-owning. To keep the queue from deleting library-owned songs, MediaPlayer::Play clones each input song and enqueues the clone.

In linear mode, next/previous clamp at the ends unless repeat is enabled. With repeat, the end wraps to the beginning. Shuffle chooses uniformly from the full queue, so a track may repeat immediately. Sound-enabled builds use SDL3_mixer’s stopped callback to advance. The no-sound fallback compares elapsed time with a known, nonzero duration; a zero-duration song does not auto-advance.

Volume and mute are independent state. Setting volume while muted retains the new value for the next unmute; the mixer alone receives zero gain while muted. MediaStateChanged and ActiveSongChanged are deferred until FrameworkDispatcher::Update(). Game calls the dispatcher during its normal update loop, but a caller using the static media API outside Game must pump it explicitly.

46.4 Visualization

MediaPlayer::GetVisualizationData() consumes samples captured by SDL3_mixer’s post-mix callback. The callback writes to a single-producer/single-consumer ring buffer without allocation, locking, or exceptions. Sample slots are std::atomic<float> with relaxed ordering so audio-thread writes and game-thread reads are defined C++ behavior. A reader may still observe samples spanning adjacent callback batches; the resulting one-frame visual imprecision is accepted.

A 512-sample Hann-windowed radix-2 FFT produces XNA’s 256 bins. CNA scales a full-amplitude sine to approximately 1.0 in its bin; XNA did not document a normalization. Disabled or not-yet-fed visualization returns zero-filled arrays. Tests cover failed installation and removal of the mixer tap so the enabled flag does not claim a callback that is absent or cannot be removed.

46.5 Video contract

Where CNA_FFMPEG_AVAILABLE is enabled, VideoPlayer uses FFmpeg for decoding, a CNA renderer texture for frames, and SDL3 audio streaming. Play() rejects a declared width, height, or frame rate that disagrees with the decoded stream. VideoSoundtrackType is metadata only in both CNA and FNA; it does not duck or mute tracks.

The public methods check disposed state and throw ObjectDisposedException after Dispose(). Dispose() itself is idempotent. This differs from FNA because the C++ destructor also calls Dispose(); throwing during that destructor could terminate the process.

SetAudioTrackEXT and SetVideoTrackEXT select streams independently. A live audio switch reopens only the audio output; a live video switch recreates the frame texture if dimensions change. The audio buffer is cleared even when no audio device or audio track is active, preventing unbounded accumulation and stale samples on a later play. Tests use a multi-track fixture to observe sample-rate change and verify that the unrelated video texture is retained.

1 VideoPlayer player;
2 player.Play(introVideo);
3 player.SetAudioTrackEXT(1); // switches audio without recreating video output

46.6 Platform limitations

The pinned CMake configuration disables FFmpeg on Windows (MinGW and MSVC), Android, and Emscripten. Video translation units are excluded on those targets, so using Video/VideoPlayer is a link-time failure, not a runtime NotSupportedException. Linux and macOS are the intended FFmpeg-enabled routes when the required development packages are available.

There is also a native-Windows guard mismatch. CMake’s unavailable set includes WIN32, but two ContentManager.cpp preprocessor guards check Emscripten, Android, and MinGW; one repeats __MINGW32__ and neither checks _WIN32. An MSVC build can therefore retain Video dispatch references after the implementations were excluded. This is an open pinned-revision build defect.

46.7 Evidence status

Source and focused tests establish parsing, hierarchy construction, queue ownership, event timing, visualization state, disposal, and multi-track reconfiguration. FFmpeg fixture tests exercise decoding on the configured host. Those results do not establish video availability on the CMake-excluded targets, nor do extension lists establish decoder support. The platform gate and Windows mismatch are source-proven; no Windows video execution is claimed.

Type at least three characters. Results are ranked by how often and where the words occur.