Video Playback

Microsoft::Xna::Framework::Media — VideoPlayer, Video, FFmpeg-backed frame decoding

ⓘ

Implementation status: VideoPlayer is real where the optional FFmpeg backend is built — it genuinely decodes video through FFmpeg (real libavcodec, libavformat and libswresample, with PTS-driven pacing), not a placeholder that returns blank frames. Since this snapshot the FFmpeg backend is optional: the Video and VideoPlayer types exist and link in every build, and a build without the backend reports System::NotSupportedException at run time instead of failing to link.

⚠

Decoding is available only where the FFmpeg backend was built — today native Linux and, per CNA's notes, macOS. The backend is selected by the CMake option CNA_ENABLE_VIDEO (AUTO by default): on Linux and macOS it is used when the libavcodec, libavformat, libavutil and libswresample development packages are found, and otherwise the build falls back. On Windows (MSVC and MinGW), Emscripten, Android and iOS the backend is never built (-DCNA_ENABLE_VIDEO=ON there is a configure error). Nothing fails to link, though: without the backend, creating a file-backed Video for an existing file, or calling VideoPlayer::Play with one, throws System::NotSupportedException (a missing file still throws FileNotFoundException), and FFmpeg is no longer a hard requirement for the project. Metadata-only Video objects and the XNB video reader work in every build. Guard video with CNA_VIDEO_AVAILABLE (defined only when the backend is present) or catch the exception, as the examples below do.

Overview

VideoPlayer and Video live in the Microsoft::Xna::Framework::Media namespace, matching the XNA 4.0 API. CNA uses FFmpeg as its optional video decoding back-end (a separate CNA::VideoFfmpeg module): the decoder reads compressed frames from the container, converts them from YUV colour space to RGBA, and uploads the result as a Texture2D. Your Draw() method retrieves that texture each frame via VideoPlayer::GetTexture() and renders it with SpriteBatch (or any other drawing path) exactly like any other Texture2D.

This is a real decoder, and it is no longer the only implemented part of Media. MediaPlayer genuinely plays songs, while MediaLibrary resolves the user's music and pictures directories, scans them recursively, parses common audio tags and playlists, discovers album art, and persists pictures. Treat individual device/library behaviors as platform-dependent rather than as a Windows Phone or Zune service emulation.

Frame timing is driven by the stream's own presentation timestamps rather than by a fixed frame counter, so playback tracks the container's real timeline instead of drifting against it.

⚠

Verification is configuration-scoped. This snapshot has 30 Media test sources with 304 statically discoverable GoogleTest-family definitions (alpha.1: 29 and 286). CNA records that both the CNA_ENABLE_VIDEO=ON and OFF configurations are built and their video contracts tested on Linux, but the decoder itself exists only where FFmpeg was found, and the optional split has not been run on macOS by its author. Run the target configuration you intend to ship.

Supported container and codec combinations:

  • MP4 — H.264 / H.265
  • OGV — Ogg Theora
  • WEBM — VP8 / VP9
  • MKV — Matroska (any FFmpeg-supported codec)
  • AVI — legacy container
  • MOV — QuickTime container
⚠

Platform restriction — the FFmpeg backend is NOT built on:

  • Emscripten / WebAssembly — use the HTML5 <video> element for in-browser video playback instead.
  • Android — use the Android MediaPlayer API directly.
  • Every Windows target — both the MinGW-w64 cross-toolchain and native MSVC. Windows builds have no video decoding.
  • iOS — the host macOS FFmpeg is deliberately not used for an iOS cross-build.

That leaves Linux and macOS desktop builds as the only places where VideoPlayer can decode anything. On every other target it throws NotSupportedException when you try to play a file.

Video Class

A Video object represents a video asset loaded from disk. Load it through the standard ContentManager pipeline:

// LoadContent()  — Load<T> returns the Video by value
Video video = getContentProperty().Load<Video>("videos/intro");

The ContentManager resolves the file extension automatically (.mp4, .ogv, .webm, .mkv, .avi, .mov as loose files); the path should be relative to the content root and omit the extension. A compiled .xnb or .cnb video asset loads too: the built-in VideoReader is registered in every build and carries the metadata, and the media file itself is referenced, not embedded. Video has no default constructor (only CNAEXT constructors taking a file name and a graphics device, with optional metadata), so keep it in a std::optional<Video> or a std::unique_ptr when it is a class member. Once loaded, the following read-only properties are available:

Property Type Description
getWidthProperty() int Frame width in pixels
getHeightProperty() int Frame height in pixels
getDurationProperty() System::TimeSpan Total playback duration of the video
getFramesPerSecondProperty() float Native frame rate reported by the container
getVideoSoundtrackTypeProperty() VideoSoundtrackType Indicates whether the video has a music track, dialog, or no audio (Music, Dialog, MusicAndDialog)

VideoPlayer Class

VideoPlayer controls playback of a Video asset. Create one instance per active video stream; it is not designed for concurrent playback of multiple videos from the same instance.

Methods

Method Description
Play(Video*) Starts playback of the given video from the beginning. If a video is already playing it is replaced. Without the FFmpeg backend this throws System::NotSupportedException before changing the player's video or state.
Pause() Pauses playback at the current frame. The decoded texture remains valid.
Resume() Resumes playback from the paused position.
Stop() Stops playback and rewinds to the beginning.
GetTexture() Returns a Texture2D* containing the current decoded frame. Call this every frame inside Draw(). Returns nullptr when the player is stopped and no frame has been decoded. Draw it with SpriteBatch::Draw(*frame, ...).

Properties

Property Type Description
getStateProperty() MediaState Current playback state: MediaState::Playing, MediaState::Paused, or MediaState::Stopped
getIsLoopedProperty() / setIsLoopedProperty(bool) bool Whether the video restarts automatically when it reaches the end. Default: false.
getIsMutedProperty() / setIsMutedProperty(bool) bool Mutes the audio track without affecting the decoded video frames.
getVolumeProperty() / setVolumeProperty(float) float Audio volume in the range 0.0 (silent) to 1.0 (full volume).
getPlayPositionProperty() System::TimeSpan Current playback position within the video. Read-only.
getVideoProperty() Video* The video currently loaded into the player. A CNAEXT method, SetAudioTrackEXT(int), selects among multiple audio tracks.

Rendering Pattern

VideoPlayer decodes frames on demand rather than pre-decoding the entire video. On each call to GetTexture(), CNA checks whether the elapsed playback time requires a new frame to be decoded, converts the YUV output to RGBA, and uploads it to the GPU as a Texture2D. This means the texture pointer returned by GetTexture() is stable for the lifetime of the player, but its contents are updated in-place each frame.

The recommended pattern inside Draw():

  1. Call player.GetTexture() (a VideoPlayer member) to obtain the current frame as a Texture2D*.
  2. If the pointer is non-null, draw it with SpriteBatch::Draw() scaled to the desired screen rectangle.

Platform Availability

Platform CNA_ENABLE_VIDEO=AUTO result Video at run time Recommended alternative
Linux (desktop) FFmpeg backend if all four dev packages are found, else fallback Decodes with the backend; NotSupportedException in a fallback build —
macOS (desktop) Same (Homebrew pkg-config) Decodes with the backend (the optional split was not run on macOS by its author) —
Windows (MinGW-w64 and native MSVC) Fallback (ON is a configure error) Throws NotSupportedException for file-backed video None — no video decoding on any Windows build
Emscripten / WebAssembly Fallback (ON is a configure error) Throws HTML5 <video> element
Android, iOS Fallback (ON is a configure error) Throws Android MediaPlayer API; the platform video player on iOS

FFmpeg Setup

FFmpeg is an optional dependency, located through pkg-config at configure time and controlled by CNA_ENABLE_VIDEO. It must be installed on the host system before building CNA if you want video decoding:

ValueConfigure behaviourRuntime video behaviour
AUTO (default)Enables the backend only when all four required FFmpeg pkg-config modules are found on a supported target; otherwise falls back silently.Decodes when found; otherwise like OFF.
ONRequires a supported target and all four modules; configuration fails if either is missing.Decodes through FFmpeg.
OFFDoes not probe or link FFmpeg.File-backed video throws NotSupportedException.

On Debian and Ubuntu, the four development packages the build probes for are:

sudo apt install libavcodec-dev libavformat-dev libavutil-dev libswresample-dev

The decoder is built on libavcodec (codec implementations), libavformat (container demuxers), libavutil and libswresample (audio resampling). A game that never plays video can say so explicitly and drop the dependency:

cmake -S . -B build -DCNA_ENABLE_VIDEO=OFF

That build has no FFmpeg link edge at all; using Game or audio-only MediaPlayer does not bring FFmpeg into the final executable, and MediaLibrary reports an unknown (zero) duration for tracks because audio-duration probing also needs the backend. When the backend is present, CNA defines CNA_VIDEO_AVAILABLE (and the older CNA_FFMPEG_AVAILABLE) on its targets.

⚠

There is no Windows FFmpeg setup to describe. The backend is not wired up on any Windows target, so installing FFmpeg development packages there will not enable video playback, and -DCNA_ENABLE_VIDEO=ON is rejected at configure time.

Code Examples

Loading and playing a video

// Game member variables
std::optional<Video> m_introVideo;          // Video has no default constructor
VideoPlayer          m_videoPlayer;

// LoadContent()
void MyGame::LoadContent() {
    spriteBatch_ = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());
    try {
        m_introVideo.emplace(getContentProperty().Load<Video>("videos/intro"));
        m_videoPlayer.setIsLoopedProperty(false);
        m_videoPlayer.setVolumeProperty(1.0f);
        m_videoPlayer.Play(&*m_introVideo);
    } catch (const System::NotSupportedException&) {
        // This build has no FFmpeg video backend (or the platform cannot decode): skip the intro.
        m_introVideo.reset();
    }
}

Getting the current frame and drawing with SpriteBatch

// Draw()
void MyGame::Draw(const GameTime& gameTime) {
    getGraphicsDeviceProperty().Clear(Color::Black);

    if (Texture2D* frame = m_videoPlayer.GetTexture()) {
        const Viewport vp = getGraphicsDeviceProperty().getViewportProperty();
        spriteBatch_->Begin();
        spriteBatch_->Draw(
            *frame,
            Rectangle(0, 0, vp.getWidthProperty(), vp.getHeightProperty()),
            Color::White);
        spriteBatch_->End();
    }
}

Checking playback state and looping

// Update()
void MyGame::Update(GameTime& gameTime) {
    if (!m_introVideo || m_videoPlayer.getStateProperty() == MediaState::Stopped) {
        // Intro finished (or unavailable) — transition to the main menu
        LoadMainMenu();
        return;
    }

    // Display elapsed / total time
    System::TimeSpan pos   = m_videoPlayer.getPlayPositionProperty();
    System::TimeSpan total = m_introVideo->getDurationProperty();
    UpdateProgressBar(pos, total);
}

// Enable looping before playback begins
m_videoPlayer.setIsLoopedProperty(true);
m_videoPlayer.Play(&*m_backgroundVideo);

Pausing and resuming on input

// Update()
void MyGame::Update(GameTime& gameTime) {
    auto kb = Keyboard::GetState();

    if (kb.IsKeyDown(Keys::Space) && m_prevKeyboard.IsKeyUp(Keys::Space)) {
        if (m_videoPlayer.getStateProperty() == MediaState::Playing) {
            m_videoPlayer.Pause();
        } else if (m_videoPlayer.getStateProperty() == MediaState::Paused) {
            m_videoPlayer.Resume();
        }
    }

    if (kb.IsKeyDown(Keys::Escape) && m_prevKeyboard.IsKeyUp(Keys::Escape)) {
        m_videoPlayer.Stop();
    }

    m_prevKeyboard = kb;
}

For a complete walkthrough, including a compile-time guard with CNA_VIDEO_AVAILABLE, see Tutorial 121: Video Playback.