# MXL Player MXL Player is a desktop player for local [Media eXchange Layer](https://github.com/dmf-mxl/mxl) audio and video flows, with GUI controls and command-line startup configuration. 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; - [`go-mxl`](https://github.com/qvest-digital/go-mxl) for MXL access. ## Features - Synchronized playback for compatible same-domain feeds; cross-domain feeds currently use independent readers. - 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. ## Planned - MXL Fabrics support; - MXL URI/addressability support; - Coordinated cross-domain reading using TAI timestamps; - GUI-created playlist; - GPU selection; - LUFS meter; - patching audio channels to selected device. ## Installation Prebuilt packages are available from [GitHub Releases](../../releases). - **macOS ARM64:** `.app` bundle with SDL3, libmxl, and MoltenVK included. - **Linux AMD64:** binary built on Ubuntu. A compatible libmxl installation, SDL3, Vulkan loader, and Vulkan-capable driver are required. Download the archive for your platform and verify its SHA-256 checksum before running it. ## Tested platforms - Ubuntu and Fedora: build and feed playback tested. - macOS ARM64: build and feed playback tested. ## Building from source: 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. - libmxl 1.2.0 or a compatible build providing `mxlCreateFlowSynchronizationGroup`. The project currently pins: ```text github.com/qvest-digital/go-mxl v1.1.0-rc.2 ``` ## Building on Linux First verify that pkg-config resolves the intended MXL installation: ```sh 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: ```sh 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: ```sh 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: ```sh ldd build/mxl-player | grep libmxl ``` ## macOS 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. Building currently requires: - Xcode Command Line Tools; - Go; - SDL3; - 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: ```sh export MXL_SOURCE=/path/to/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 (for example, in case of installing with homebrew): ```sh 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 ``` The resulting executable may currently need an additional libmxl runtime search path, for example: ```sh install_name_tool -add_rpath "$MXL_BUILD/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 package is ad-hoc signed but is not signed with an Apple Developer ID or 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: ```sh xattr -dr com.apple.quarantine "/Applications/MXL Player.app" ``` Only remove quarantine from an archive downloaded from the official project release and verified against its SHA-256 checksum. ## Running Start with no configured feed and enter domains/UUIDs in the GUI: ```sh ./build/mxl-player ``` Video only: ```sh ./build/mxl-player \ --domain /dev/shm/mxl \ --video VIDEO_UUID ``` Audio only: ```sh ./build/mxl-player \ --domain /dev/shm/mxl \ --audio AUDIO_UUID ``` Independent video and audio: ```sh ./build/mxl-player \ --video-domain /dev/shm/video \ --video VIDEO_UUID \ --audio-domain /dev/shm/audio \ --audio AUDIO_UUID ``` Start a compatible pair synchronized: ```sh ./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. ```sh # 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: ```sh ./build/mxl-player --playlist playlist.json ``` Example: ```json { "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. ## 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`](internal/assets/fonts/). ## License MXL Player is licensed under the GNU General Public License, version 3 or any later version. See [`LICENSE`](LICENSE). The embedded font retains its separate SIL Open Font License 1.1.