diff --git a/.gitignore b/.gitignore index 66f2601..c51671c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.DS_Store build imgui.ini diff --git a/README.md b/README.md index 7570e2c..de6fd27 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,9 @@ video and ImGui rendering; and > 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. +> 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 @@ -163,9 +164,29 @@ path, for example: 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. +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: + +```sh +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 @@ -480,11 +501,12 @@ local MXL flow; it will not be implemented as a separate playback backend. See - 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. +- 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. -- There are no prebuilt packages or application bundles yet. +- 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. diff --git a/README_public.md b/README_public.md new file mode 100644 index 0000000..5dc59d9 --- /dev/null +++ b/README_public.md @@ -0,0 +1,348 @@ +# 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. diff --git a/REFACTORING_PLAN.md b/old_notes/REFACTORING_PLAN.md similarity index 100% rename from REFACTORING_PLAN.md rename to old_notes/REFACTORING_PLAN.md diff --git a/fixes.md b/old_notes/fixes.md similarity index 100% rename from fixes.md rename to old_notes/fixes.md diff --git a/gui.md b/old_notes/gui.md similarity index 100% rename from gui.md rename to old_notes/gui.md diff --git a/test-notes.txt b/old_notes/test-notes.txt similarity index 100% rename from test-notes.txt rename to old_notes/test-notes.txt diff --git a/release/.DS_Store b/release/.DS_Store deleted file mode 100644 index 0ad0aee..0000000 Binary files a/release/.DS_Store and /dev/null differ