Video Playback
Implementation status: VideoPlayer is real — it genuinely decodes video through FFmpeg (real libavcodec, libavformat and libswresample, with PTS-driven pacing), not a placeholder that returns blank frames.
Video is effectively native Linux/macOS only today. On Windows, Emscripten and Android the three video translation units are excluded from the build entirely. The headers still exist, so code calling Video or VideoPlayer compiles and then fails at link time with undefined symbols — it is not a runtime stub you can probe for. Guard video features behind your own build-time switch on those targets. Note the converse too: on Linux and macOS FFmpeg is a hard requirement for the whole project, not an optional codec extra — CMake configuration fails outright without the libavcodec, libavformat, libavutil and libswresample development packages.
Overview
VideoPlayer and Video live in the Microsoft::Xna::Framework::Media namespace, matching the XNA 4.0 API. CNA uses FFmpeg as its video decoding back-end: 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. Alpha.1 contains 29 Media test sources with 286 statically discoverable GoogleTest-family definitions, but the video implementation itself is compiled only on its supported native targets and needs FFmpeg. 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 — FFmpeg is NOT available on:
- Emscripten / WebAssembly — use the HTML5
<video>element for in-browser video playback instead. - Android — use the Android
MediaPlayerAPI directly. - Every Windows target — both the MinGW-w64 cross-toolchain and native MSVC. Windows builds have no video decoding at all.
That leaves desktop Linux as the only platform where VideoPlayer actually decodes anything.
Video Class
A Video object represents a video asset loaded from disk. Load it through the standard ContentManager pipeline:
// LoadContent()
auto video = content.Load<Video>("videos/intro");
The ContentManager resolves the file extension automatically; the path should be relative to the content root and omit the extension. Once loaded, the following read-only properties are available:
| Property | Type | Description |
|---|---|---|
Width |
int |
Frame width in pixels |
Height |
int |
Frame height in pixels |
Duration |
TimeSpan |
Total playback duration of the video |
FramesPerSecond |
float |
Native frame rate reported by the container |
VideoSoundtrackType |
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. |
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. |
Properties
| Property | Type | Description |
|---|---|---|
State |
MediaState |
Current playback state: MediaState::Playing, MediaState::Paused, or MediaState::Stopped |
IsLooped |
bool |
Whether the video restarts automatically when it reaches the end. Default: false. |
IsMuted |
bool |
Mutes the audio track without affecting the decoded video frames. |
Volume |
float |
Audio volume in the range 0.0 (silent) to 1.0 (full volume). |
PlayPosition |
TimeSpan |
Current playback position within the video. Read-only. |
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():
- Call
player.GetTexture()to obtain the current frame as aTexture2D*. - If the pointer is non-null, draw it with
SpriteBatch::Draw()scaled to the desired screen rectangle.
Platform Availability
| Platform | FFmpeg available | Recommended alternative |
|---|---|---|
| Linux (desktop) | Yes | — the only platform with working video |
| Windows (MinGW-w64 and native MSVC) | No | None — no video decoding on any Windows build |
| Emscripten / WebAssembly | No | HTML5 <video> element |
| Android | No | Android MediaPlayer API |
FFmpeg Setup
FFmpeg is an optional, desktop-Linux-only dependency, located through pkg-config at configure time. It must be installed on the host system before building CNA with video support; if it is absent, CNA builds fine and video is simply unavailable.
On Debian and Ubuntu:
sudo apt install libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev
The decoder is built on libavcodec (codec implementations), libavformat (container demuxers) and libswresample (audio resampling), alongside the usual FFmpeg support libraries.
There is no Windows FFmpeg setup to describe. Video is not wired up on any Windows target, so installing FFmpeg development packages there will not enable VideoPlayer.
Code Examples
Loading and playing a video
// Game member variables
Video m_introVideo;
VideoPlayer m_videoPlayer;
// LoadContent()
void MyGame::LoadContent() {
m_introVideo = content.Load<Video>("videos/intro");
m_videoPlayer.IsLooped = false;
m_videoPlayer.Volume = 1.0f;
m_videoPlayer.Play(m_introVideo);
}
Getting the current frame and drawing with SpriteBatch
// Draw()
void MyGame::Draw(GameTime gameTime) {
graphicsDevice->Clear(Color::Black);
Texture2D* frame = m_videoPlayer.GetTexture();
if (frame != nullptr) {
spriteBatch->Begin();
spriteBatch->Draw(
frame,
Rectangle(0, 0,
graphicsDevice->GetViewport().Width,
graphicsDevice->GetViewport().Height),
Color::White);
spriteBatch->End();
}
}
Checking playback state and looping
// Update()
void MyGame::Update(GameTime gameTime) {
if (m_videoPlayer.State == MediaState::Stopped) {
// Intro finished — transition to the main menu
LoadMainMenu();
return;
}
// Display elapsed / total time
TimeSpan pos = m_videoPlayer.PlayPosition;
TimeSpan total = m_introVideo.Duration;
UpdateProgressBar(pos, total);
}
// Enable looping before playback begins
m_videoPlayer.IsLooped = 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.State == MediaState::Playing) {
m_videoPlayer.Pause();
} else if (m_videoPlayer.State == MediaState::Paused) {
m_videoPlayer.Resume();
}
}
if (kb.IsKeyDown(Keys::Escape) && m_prevKeyboard.IsKeyUp(Keys::Escape)) {
m_videoPlayer.Stop();
}
m_prevKeyboard = kb;
}