Skip to content

dotcl/playa

Repository files navigation

playa

English / 日本語

A split-tiling video / music player written with dotcl (Common Lisp on .NET), Avalonia and LibVLCSharp.

Split the window like Emacs, put a different video in each pane, and play them all at once — each pane keeps its own playlist, its own volume and its own position.

Four videos playing at once in a 2x2 split

Playlist panel, showing the queue and per-track lengths Key bindings in the settings window

Screenshots: Blender Foundation open moviesBig Buck Bunny, Sintel, Tears of Steel and Caminandes 3: Llamigos, (CC) Blender Foundation, CC-BY 3.0.

Almost everything — the UI and the application logic — is written in Common Lisp (under src/, split by feature). The C# side handles boot plus the platform shims that need native callbacks. The same code runs as a GUI on Windows / macOS / Linux.

This project doubles as a technical demo of how far you can push a practical GUI app in dotcl. Read it as a worked example of "build an Avalonia Window from Common Lisp, drive a native media engine, and keep the platform-specific parts at arm's length."

What makes it different

  • Emacs-style tiling of players. The window is a split tree (src/tree.lisp); s / S split the selected pane left-right / top-bottom, x closes it, Tab moves between panes, and Shift+HJKL rearranges panes in place. Every leaf is a full player with its own playlist.
  • The GUI is written in Common Lisp — even the Avalonia.Application subclass is emitted from Lisp via dotnet:define-class (Playa.App). Assembling Window / Grid / VideoView, wiring events, posting to the Dispatcher — all on the Lisp side.
  • C# is only boot plus a platform shim. Program.cs preloads the assemblies, loads the precompiled Lisp from a manifest and calls RUN-PLAYA. What remains in C# is the code that must hand a function pointer to the OS — the low-level mouse hook and DWM thumbnail on Windows, XI2 / XDND on X11, NSEvent / NSView on macOS — because the libvlc video surface swallows the input Avalonia would otherwise deliver.
  • Lisp is ahead-of-time compiled at build time. DotCL.Runtime's project-core build (<DotclProjectAsd>) compiles the ASDF system (src/playa.asd) to fasl in-process and puts the dependency fasls + manifest in the output. At runtime nothing is compiled and no sources ship.
  • Metadata follows the content, not the path. Scene bookmarks are keyed by a content id (size + head/tail hash, OpenSubtitles style), so renaming or moving a file keeps its marks (src/meta.lisp).

Features

  • Split-tile layout with any number of panes, each playing independently; the split geometry is restored on the next launch
  • Per-pane playlist — drag to reorder, repeat / shuffle, jump by cursor, with track lengths fetched in the background
  • Scene bookmarks: mark a point or an A→B range, then jump between them; stored per content id so they survive a rename or move
  • A-B repeat, per-pane volume, mute; pause-all / mute-all across every pane at once
  • vi-style numeric prefixes — 3230g seeks to 32:30, 30h rewinds 30 s, 5e jumps to scene #5
  • Recent files panel and menu, shared across panes
  • Controls (transport strip, selection frame, cursor) auto-hide when idle and come back on any input
  • Drag & drop files or folders onto a pane; dropping onto a specific pane loads it there
  • Customizable key bindings in the settings window, persisted to keymap.lisp
  • Windows: single instance (opening a file reuses the running window), per-user file association without admin rights, and a blur-free Alt+Tab / taskbar thumbnail via DWM iconic bitmaps
  • macOS: native menu bar, Finder "Open With" / Dock drop, and a self-contained .app that bundles libvlc

Build and run

Requires: .NET 10 SDK, plus libvlc for your platform.

OS libvlc
Windows bundled automatically (VideoLAN.LibVLC.Windows)
macOS supplied at runtime from the bundle, Homebrew (brew install vlc) or an installed VLC.app — no usable NuGet package exists for arm64
Linux the system's own (apt install vlc libvlc-dev or the distro equivalent)
make build                          # or: dotnet build
make run                            # or: dotnet run
make run ARGS=/path/to/movie.mp4    # open a file at startup

Key bindings (defaults)

Key Action
Space / Shift+Space Play / pause the selected pane — or every pane at once
H L Seek back / forward (default 10 s, [count] = seconds)
K J Volume up / down ([count] = that value)
N / P Next / previous track
M / Shift+M Mute the pane / mute everything
[count]g Jump to a time — 3230g = 32:30
R A-B repeat
B / Shift+B Mark a scene here / mark a scene range (start, then end)
E / Shift+E / Control+B Next scene ([count]e = scene #n) / previous / delete nearest
F Fullscreen
O / Shift+O Playlist panel / recent-files panel
Return / Delete In a list: play the selection / remove it
Tab / Shift+Tab Next / previous pane
S / Shift+S / X Split left-right / top-bottom / close pane
Shift+HJKL Swap the selected pane with its neighbour (in a list: move the selected row)
Control+O / Control+Shift+O Open file / open folder
Q Quit

Keys can be reassigned in the settings window; the full list is also under Help → Key list.

Architecture

The Lisp side (src/, in load order — earlier files' definitions are visible to later ones):

package.lisp package definition
util.lisp globals, logging, config directory + atomic s-expr I/O
dnd.lisp drag & drop
pane.lisp a pane: VideoView + MediaPlayer, its transport, context menu, construction
playlist.lisp shared Playlist panel, repeat / shuffle / reorder
recent-panel.lisp shared Recent Files panel
meta.lisp per-content-id metadata and scene bookmarks
tree.lisp the split tree and its rearrangement
transport.lisp bottom transport bar for the selected pane
dialog.lisp native file / folder pickers
command.lisp command table, keymap, key dispatch for every input source
config-io.lisp persisted recent files / prefs / session geometry
assoc.lisp Windows file association (HKCU)
about.lisp About and Key list dialogs
settings-ui.lisp Settings window
menu.lisp menu bar and macOS application menu
app.lisp the Avalonia.Application subclass, main window, run-playa

The C# side is boot plus the shims that need a native callback: Program.cs (startup), NativeVideoInput*.cs (input over the video surface — Windows hook, X11 XI2, macOS NSEvent), WindowThumbnail.cs (DWM Alt+Tab thumbnail), Mixer.cs (per-pane volume), FileId.cs (content id), SingleInstance.cs.

Configuration lives in one place per user — %APPDATA%\playa\ on Windows, ~/.config/playa/ on Linux, ~/Library/Application Support/playa/ on macOS — holding prefs.lisp, keymap.lisp, session.lisp, recent.lisp and meta/. All of them are plain readable s-expressions, written atomically and read back with *read-eval* disabled.

Dependencies

  • dotcl (DotCL.Runtime) — Common Lisp on .NET
  • Avalonia — cross-platform UI
  • LibVLCSharp + libvlc — playback
  • NAudio — per-pane volume mixing (Windows only)

Linux (AppImage / tar.gz)

make appimage       # -> dist/playa-<version>-<arch>.AppImage
make tarball        # -> dist/playa-<version>-<arch>.tar.gz (with install.sh)

Both publish self-contained, so the .NET runtime comes along. libvlc does not: VideoLAN publishes no usable Linux NuGet package, so playback uses the distro's own VLC (apt install vlc). Both launchers check for it up front and name the package to install — without that, a missing engine surfaces as libvlc.so: cannot open shared object file, which says nothing useful. Set PLAYA_SKIP_LIBVLC_CHECK=1 if your libvlc lives outside the ldconfig cache.

install.sh from the tarball installs under ~/.local/ (binary, .desktop entry and icon) with no root access; uninstall.sh removes exactly those.

Windows installer

make win-installer                  # both x64 and arm64
make win-installer ARCH=arm64       # one architecture

Requires Inno Setup 6: winget install --id JRSoftware.InnoSetup. Produces dist/playa-setup-<arch>.exe — a per-user install under %LOCALAPPDATA%\Programs\playa, so no administrator rights are needed. The .NET runtime is bundled. File associations are not touched by the installer; register them from Settings → File association, which writes only to HKCU.

Each installer carries libvlc for its own architecture only. The NuGet package ships x86, x64 and arm64 side by side (~280 MB together), which would otherwise triple the download for no benefit.

macOS .app bundle

Releases carry Windows and Linux builds only — on macOS, build from source. The bundling step needs a VLC.app of the matching architecture, which the release runners cannot supply for both, and an untested bundle is worse than a build instruction. A macOS release will follow once one can be verified on real hardware.

make app        # -> dist/playa.app

Publishes self-contained (the .NET runtime is bundled, so a Finder launch does not depend on shell environment variables) and bundles libvlc + its plugins from an installed VLC.app.

License

MIT — see LICENSE.

About

A split-tiling video / music player written in Common Lisp on .NET (dotcl + Avalonia + LibVLCSharp)

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages