Files
go-mxl-player/README.md
T
Dmitry Sergeev 4fa5f9212a README.md
2026-09-03 20:34:39 +03:00

13 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. Linux is the currently tested platform. macOS support through Vulkan/MoltenVK is being tested. Packaging, 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 libmxl and 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

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 currently requires:

  • Xcode Command Line Tools;
  • Go;
  • SDL3;
  • Vulkan loader and MoltenVK;
  • a macOS build of libmxl with matching headers and pkg-config metadata.

Nonstandard loader locations can be supplied explicitly:

export SDL3_LIBRARY=/absolute/path/to/libSDL3.dylib
export VULKAN_LIBRARY=/absolute/path/to/libvulkan.dylib

These variables should not be necessary once the libraries are installed in a normal dynamic-loader path. A self-contained .app bundle is planned but does not exist yet.

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:

  • duration uses Go duration syntax such as 500ms, 10s, or 2m.
  • Missing/empty duration means manual advance.
  • loop: true returns 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-attempts was 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 is the only platform currently exercised regularly.
  • macOS/MoltenVK support is not yet release-tested.
  • Windows support is not currently claimed.
  • GPU selection and verbose logging flags are incomplete.
  • There are no prebuilt packages or application bundles yet.
  • 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.