16 KiB
MXL Player
MXL Player is a desktop and command-line controlled player for local Media eXchange Layer audio and video flows. It can play either feed independently, play both simultaneously, or switch a configured pair into and out of native MXL synchronization while running.
The application uses SDL3 for its window, input, and audio output; Vulkan for
video and ImGui rendering; and
go-mxl for MXL access.
Important
This project is under active development. Builds and feed playback have been verified in current Ubuntu and Fedora environments and on a separate Ubuntu machine. Native builds and feed playback have also been verified on macOS with MoltenVK. A macOS application bundle has been tested in a clean virtual machine; publication of release artifacts, stable releases, and MXL Fabrics support are planned.
Features
- Video-only and audio-only playback.
- Independent audio and video feeds, including feeds from different domains.
- Native synchronized audio/video playback when both feeds are compatible.
- Runtime feed apply, stop, resume, remove, and replacement.
- Runtime synchronization enable/disable.
- Finite or unlimited reconnect attempts with exponential backoff.
- JSON playlists with manual selection, timed advance, looping, and failure policies.
- Playback, media, timing, retry, and dropped-frame statistics.
- Fullscreen mode and an empty idle window when no video is active.
- Embedded UI font; no system font installation is required.
Current requirements
- Go 1.26 or newer.
- A C/C++ build toolchain for cgo dependencies.
- A compatible shared build of
libmxland its development headers. - pkg-config metadata for
libmxl. - SDL3 shared library.
- Vulkan loader and a working Vulkan driver.
The project currently pins:
github.com/qvest-digital/go-mxl v1.1.0-rc.2
The headers used while building and the libmxl.so loaded at runtime must be
compatible. In particular, synchronized playback requires the native symbol:
mxlCreateFlowSynchronizationGroup
Check it with:
nm -D /path/to/libmxl.so | grep mxlCreateFlowSynchronizationGroup
Building on Linux
Verified source-build environments:
| Environment | Result | Required distribution packages |
|---|---|---|
Ubuntu (ubuntu:latest, Distrobox) |
Build and feed playback successful | golang, libsdl3-dev, spdlog-dev |
Fedora (fedora:latest, Distrobox) |
Build and feed playback successful | golang, SDL3-devel, spdlog-devel, g++ |
| Ubuntu, separate physical machine | Build and feed playback successful | SDL3, compiler toolchain, and a compatible libmxl installation |
These checks used an existing shared libmxl installation with
PKG_CONFIG_PATH pointing to a valid libmxl.pc. Package names can differ
between distribution releases.
First verify that pkg-config resolves the intended MXL installation:
pkg-config --modversion libmxl
pkg-config --cflags --libs libmxl
pkg-config --variable=pcfiledir libmxl
pkg-config --variable=libdir libmxl
If MXL is installed in a nonstandard prefix:
export MXL_PREFIX=/opt/mxl/1.2.0
export PKG_CONFIG_PATH="$MXL_PREFIX/lib/pkgconfig"
export LD_LIBRARY_PATH="$MXL_PREFIX/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
Then build and test:
go mod download
go test ./...
mkdir -p build
go build -o build/mxl-player ./cmd/mxl-player
Confirm which native library the executable will use:
ldd build/mxl-player | grep libmxl
Do not copy a libmxl.pc file containing paths from another computer. A
relocatable SDK file can use ${pcfiledir} to describe its own location.
macOS status
The SDL3 and Vulkan loaders contain macOS library-name support. The Vulkan dependency is temporarily replaced by a portability fork while the upstream change is reviewed.
A macOS source build and native feed playback have succeeded on real Apple hardware. Building currently requires:
- Xcode Command Line Tools;
- Go;
- SDL3;
- Vulkan loader and MoltenVK;
- a macOS build of libmxl with matching headers and pkg-config metadata.
When libmxl has been built from its source tree but has not been installed into a standard prefix, expose its build metadata, source headers, and library directory to cgo. For example:
export MXL_SOURCE=/Users/jitten/extra/mxl
export MXL_BUILD="$MXL_SOURCE/build/Darwin-Clang-Release"
export PKG_CONFIG_PATH="$MXL_BUILD${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
export CGO_CFLAGS="-I$MXL_SOURCE/lib/include"
export CGO_LDFLAGS="-L$MXL_BUILD/lib"
mkdir -p build
go build -o build/mxl-player ./cmd/mxl-player
The CGO_CFLAGS workaround is needed because the headers are still in the MXL
source tree. A properly installed MXL SDK should expose them through
libmxl.pc instead.
Nonstandard loader locations can be supplied explicitly:
export SDL3_LIBRARY="$(brew --prefix sdl3)/lib/libSDL3.dylib"
export VULKAN_LIBRARY=/absolute/path/to/libMoltenVK.dylib
export DYLD_LIBRARY_PATH="$MXL_BUILD/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"
./build/mxl-player
Direct loading of libMoltenVK.dylib is the configuration verified to work.
The Vulkan loader installed through Homebrew did not work in the tested setup,
so it is not currently recommended for running the player.
The resulting executable may currently need an additional libmxl runtime search path, for example:
install_name_tool -add_rpath /absolute/path/to/mxl/lib build/mxl-player
These workarounds are only needed for development builds. The packaged .app
uses bundled libraries and relative runtime paths instead.
Running the macOS application bundle
The ARM64 .app bundle has been tested on physical Apple Silicon hardware. The
application extracted from its release ZIP has also been launched successfully
in a clean macOS installation under UTM, without the development environment or
Homebrew dependencies.
The initial package is unsigned and is not notarized by Apple. Consequently, Gatekeeper may block its first launch. Right-click MXL Player.app, select Open, and confirm the prompt. It can also be allowed from System Settings → Privacy & Security after a blocked launch.
If macOS still retains a quarantine restriction, it can be removed manually:
xattr -dr com.apple.quarantine "/Applications/MXL Player.app"
Only use this command for an archive downloaded from the project's official GitHub release and verified against its published SHA-256 checksum.
Running
Start with no configured feed and enter domains/UUIDs in the GUI:
./build/mxl-player
Video only:
./build/mxl-player \
--domain /dev/shm/mxl \
--video VIDEO_UUID
Audio only:
./build/mxl-player \
--domain /dev/shm/mxl \
--audio AUDIO_UUID
Independent video and audio:
./build/mxl-player \
--video-domain /dev/shm/video \
--video VIDEO_UUID \
--audio-domain /dev/shm/audio \
--audio AUDIO_UUID
Start a compatible pair synchronized:
./build/mxl-player \
--domain /dev/shm/mxl \
--video VIDEO_UUID \
--audio AUDIO_UUID \
--sync
Synchronization is not a permanent startup mode. It can be enabled and disabled at runtime from the settings window.
Command-line options
| Option | Short | Meaning |
|---|---|---|
--help |
-h |
Show help and exit. |
--domain PATH |
-d |
Default domain for feeds without a specific domain. |
--video-domain PATH |
Domain for the video feed. | |
--audio-domain PATH |
Domain for the audio feed. | |
--video UUID |
-v |
Initial video flow UUID. |
--audio UUID |
-a |
Initial audio flow UUID. |
--sync |
-s |
Start a configured A/V pair synchronized. |
--max-attempts N |
Attempts per playback lifecycle; 0 means unlimited. |
|
--playlist FILE |
Load a JSON playlist. | |
--fullscreen |
-f |
Start fullscreen. |
--gpu-id N |
-g |
Select GPU ID; currently incomplete. |
--playback-id N |
-p |
Select the SDL playback-device ID. |
--list-playback |
List SDL playback devices and exit. | |
--list-gpu |
List Vulkan physical devices and exit. | |
--verbose |
Reserved; currently incomplete. |
Run ./build/mxl-player --help for the authoritative list.
GUI controls
The settings window accepts separate video/audio domains and UUIDs.
- Apply feeds applies the complete desired configuration.
- Stop stops a feed while retaining its UUID and domain.
- Resume starts a stopped configured feed with a fresh lifecycle.
- Remove stops a feed and clears its configuration.
- Stop all and Resume all operate on both configured feeds.
- Synchronize switches a compatible active pair between synchronized and independent playback.
Keyboard shortcuts:
| Key | Action |
|---|---|
F1 |
Show or hide settings. |
F2 |
Show or hide statistics. |
F |
Toggle fullscreen. |
Q or Esc |
Quit. |
Retry behavior
Each playback lifecycle starts at attempt 1. Failed connections/readers are retried with exponential delay from 500 ms up to 5 seconds.
# Retry indefinitely
./build/mxl-player -d /dev/shm/mxl -v VIDEO_UUID --max-attempts 0
# Stop after three failed attempts
./build/mxl-player -d /dev/shm/mxl -v VIDEO_UUID --max-attempts 3
With independent A/V, one failed feed retries without stopping the other. A synchronized pair reconnects as one lifecycle.
Playlists
Start a playlist with:
./build/mxl-player --playlist playlist.json
Example:
{
"loop": true,
"on_failure": "next",
"retry": {
"max_attempts": 3,
"initial_delay": "500ms",
"max_delay": "5s"
},
"entries": [
{
"name": "Synchronized pair",
"video": {
"domain": "/dev/shm/mxl",
"uuid": "VIDEO_UUID"
},
"audio": {
"domain": "/dev/shm/mxl",
"uuid": "AUDIO_UUID"
},
"sync": true,
"duration": "10s"
},
{
"name": "Video only",
"video": {
"domain": "/dev/shm/mxl",
"uuid": "ANOTHER_VIDEO_UUID"
},
"duration": "15s"
},
{
"name": "Audio until manually advanced",
"audio": {
"domain": "/dev/shm/mxl",
"uuid": "ANOTHER_AUDIO_UUID"
}
}
]
}
Playlist rules:
durationuses Go duration syntax such as500ms,10s, or2m.- Missing/empty duration means manual advance.
loop: truereturns to the first entry after the last.on_failure: "wait"remains on an exhausted entry.on_failure: "next"advances after retry exhaustion.- A synchronized entry must contain both video and audio.
- Playlist retry settings override the CLI defaults unless
--max-attemptswas explicitly supplied.
The GUI supports entry selection, previous/next navigation, and pausing or resuming a timed entry.
Development checks
go fmt ./...
go test ./...
go test -race ./internal/playback ./cmd/mxl-player
go vet ./...
Live media and GPU/audio integration still require real MXL producers and are not covered by the unit test suite.
Architecture
Playback workers own individual reader lifecycles and retries. Slots ensure that only one worker lifecycle for a feed or synchronized pair runs at a time. The session controller converts the complete desired feed state into an independent or synchronized execution plan. Playlist logic sends the same session commands as the GUI, so it does not bypass lifecycle rules.
flowchart TD
CLI[CLI startup configuration]
GUI[ImGui controls]
PC[PlaylistController]
PEC[PlaylistEventCoordinator]
CMD[SessionCommand channel]
SC[SessionController]
PLAN[SessionPlan]
VS[VideoSlot]
AS[AudioSlot]
SS[SyncSlot]
VW[VideoWorker]
AW[AudioWorker]
SW[SyncWorker]
VF[VideoFactory]
AF[AudioFactory]
SF[SyncFactory]
MXL[go-mxl / libmxl]
VB[VideoBridge]
VR[Vulkan renderer]
AO[SDL audio sink]
STATUS[StatusStore]
MEDIA[MediaStatsStore]
CLI --> CMD
GUI --> CMD
PC --> CMD
CMD --> SC
SC --> PLAN
PLAN -->|independent video| VS
PLAN -->|independent audio| AS
PLAN -->|synchronized pair| SS
VS --> VW --> VF --> MXL
AS --> AW --> AF --> MXL
SS --> SW --> SF --> MXL
VW --> VB --> VR
SW --> VB
AW --> AO
SW --> AO
VW --> STATUS
AW --> STATUS
SW --> STATUS
VW --> MEDIA
AW --> MEDIA
SW --> MEDIA
STATUS --> GUI
MEDIA --> GUI
STATUS --> PEC
SC --> PEC
PC --> PEC
PEC --> PC
Main entities
| Entity | Responsibility |
|---|---|
SessionConfig |
Complete desired state of both feeds, synchronization request, and retry policy. |
SessionCommand |
One user or playlist request that produces a new desired session state. |
SessionController |
Owns the current desired state and reconciles it with the required topology. |
SessionPlan |
Validated execution plan: idle, independent feeds, or one synchronized pair. |
VideoSlot / AudioSlot |
Own replacement and cancellation of one independent worker lifecycle. |
SyncSlot |
Owns replacement and cancellation of the synchronized pair lifecycle. |
| Workers | Open readers, consume media, apply retry policy, and publish lifecycle status. |
| MXL adapter factories | Translate go-mxl readers, metadata, and errors into playback contracts. |
VideoBridge |
Transfers the latest borrowed video frame safely to the render loop. |
SDLAudioSink |
Interleaves planar F32 samples and manages SDL audio queueing. |
StatusStore |
Holds the latest lifecycle status for each playback unit. |
MediaStatsStore |
Holds feed metadata and received-media counters for diagnostics. |
PlaylistController |
Owns selection, timing, looping, navigation, and failure policy. |
PlaylistEventCoordinator |
Observes session/status state and tells the playlist when an entry is ready or exhausted. |
Ownership rules
- Every incoming feed configuration represents the complete desired state for that slot.
- A slot cancels and joins its current worker before starting a replacement; two worker lifecycles for one slot must never overlap.
- Invalid commands do not disturb the current valid session.
- Independent audio and video fail and retry independently.
- A synchronized pair fails and retries as one lifecycle.
- Runtime synchronization changes topology; it is not a startup-only mode.
- Cancellation flows from the application context through controllers, slots, workers, readers, and sinks.
- Playback owns lifecycle policy. Adapters know MXL details, while renderer, audio output, GUI, and playlists do not.
Source layout
| Path | Contents |
|---|---|
cmd/mxl-player |
Composition root, CLI, main loop, GUI, and playlist file decoding. |
internal/playback |
Transport-independent state, workers, slots, controllers, retry, playlist, and statistics. |
internal/adapter/mxl |
Local MXL implementations of playback reader contracts. |
internal/source |
Low-level local MXL video/audio access and error translation. |
internal/output |
SDL audio sink and sample interleaving. |
internal/renderer |
Vulkan video rendering. |
internal/imgui |
ImGui SDL input and Vulkan rendering integration. |
internal/sdl |
Minimal dynamically loaded SDL3 bindings used by the player. |
internal/assets |
Embedded UI assets and their licenses. |
MXL Fabrics is planned as an ingress transport that writes remote data into a
local MXL flow; it will not be implemented as a separate playback backend. See
MXLFABRICS_PLAN.md.
Known limitations
- Linux builds and feed playback have been verified on Ubuntu and Fedora; broader runtime and hardware coverage is still limited.
- macOS/MoltenVK builds and feed playback work on tested Apple Silicon hardware. The packaged application launches in a clean UTM macOS VM, but it remains unsigned and unnotarized.
- Windows support is not currently claimed.
- GPU selection and verbose logging flags are incomplete.
- Release artifacts have not yet been published.
- MXL Fabrics ingress is planned but not implemented.
- Native MXL headers and runtime libraries must be ABI-compatible.
Third-party assets
The embedded JetBrainsMonoNL Nerd Font Mono is distributed under the SIL Open
Font License 1.1. Its copyright notice, license, and source information are in
internal/assets/fonts.
License
MXL Player is licensed under the GNU General Public License, version 3 or any
later version. See LICENSE.
The embedded font retains its separate SIL Open Font License 1.1.