Files
go-mxl-player/README.md
T
Dmitry Sergeev 3768397bfd mac os test
2026-09-05 01:10:36 +03:00

503 lines
16 KiB
Markdown

# MXL Player
MXL Player is a desktop and command-line controlled player for local
[Media eXchange Layer](https://github.com/dmf-mxl/mxl) 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`](https://github.com/qvest-digital/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. macOS 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:
```text
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:
```text
mxlCreateFlowSynchronizationGroup
```
Check it with:
```sh
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:
```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
```
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:
```sh
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:
```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
```
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:
```sh
install_name_tool -add_rpath /absolute/path/to/mxl/lib build/mxl-player
```
These workarounds should not be necessary once the libraries are installed in
a normal dynamic-loader path. A self-contained `.app` bundle and downloadable
GitHub release are planned but do not exist yet.
## 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.
## Development checks
```sh
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.
```mermaid
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`](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 hardware, but the
application is not yet packaged or release-tested on a clean machine.
- 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`](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.