Official Music Assistant image plus local patches (Jellyfin playback reporting)
  • Python 87.3%
  • Dockerfile 12.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-11-01 11:41:24 +00:00
.forgejo/workflows Music Assistant image with Jellyfin playback reporting 2026-10-31 11:19:43 +00:00
patches Hold back Jellyfin favorites for the playing track until it stops 2026-11-01 11:29:30 +00:00
tests Hold back Jellyfin favorites for the playing track until it stops 2026-11-01 11:29:30 +00:00
.upstream-version Built Music Assistant 2.10.9 [skip ci] 2026-10-31 13:52:38 +00:00
Dockerfile Music Assistant image with Jellyfin playback reporting 2026-10-31 11:19:43 +00:00
README.md Hold back Jellyfin favorites for the playing track until it stops 2026-11-01 11:29:30 +00:00

Music Assistant

The official Music Assistant image (ghcr.io/music-assistant/server) with the patches in patches/ applied. Published as forgejo.mcy.space/music-assistant/music-assistant:<major.minor>.<run>.

Patches

jellyfin-playback-reporting.patch

Upstream's Jellyfin provider never tells Jellyfin what was played, so plays from Music Assistant are missing from Jellyfin's play counts and from anything that learns from them (the Harmonie plugin's mixes and "On Repeat" lists).

The patch implements the provider's on_played hook, the same hook the Plex and Emby providers use. Each track becomes a Jellyfin playback session, reported with the provider's existing Jellyfin login:

Music Assistant Jellyfin call
first report for a track POST /Sessions/Playing
every 30 s while playing POST /Sessions/Playing/Progress
played to the end, stopped or skipped POST /Sessions/Playing/Stopped with the position
marked unplayed in the UI DELETE /UserPlayedItems/{id}

Jellyfin then sets the play count, last-played date and resume position itself, and server plugins see the playback events as they would for any other client. A failed report is logged as a warning and never interrupts playback.

The patch can go away once upstream's provider reports playback itself.

jellyfin-sync-favorites.patch

Upstream's provider reads Jellyfin favorites but never writes them, so a favorite set in Music Assistant stays there and Jellyfin, Symfonium, Harmonie and the ListenBrainz plugin never see it.

The patch declares the provider able to edit favorites for tracks, albums, artists and playlists, and implements set_favorite: favoriting sends POST /UserFavoriteItems/{id}, unfavoriting sends DELETE. It applies on top of the playback patch and reuses its request helper, so a failure is logged, never raised.

A favorite change for the track that is playing is held back and sent right after that track's stopped report. Jellyfin saves each progress and stopped report with a copy of the track's user data taken when playback started, so a favorite sent mid-track is overwritten with the old value at the next progress report, within 30 seconds. Held back, the change lands after the last report of the session. Only the latest change is sent, so liking and then unliking a track during playback sends nothing but the unlike.

Build

.forgejo/workflows/build.yml runs daily, on a push to main and by hand:

  1. Reads the latest stable release from GitHub (pre-releases and nightlies are ignored). A scheduled run builds only when it differs from .upstream-version.
  2. Builds FROM ghcr.io/music-assistant/server:<version> and applies the patches with patch --fuzz=0. A patch that no longer applies exactly fails the build, so a broken patch never ships silently.
  3. Runs tests/ inside the built image. Each test drives the patched provider against a fake Jellyfin server and checks every request sent.
  4. Pushes <major.minor>.<run number> and records the version in .upstream-version.

Keel follows the tag with policy patch and approval. A new upstream minor release (for example 2.11) starts a new series, which needs a manual bump in the Deployment.

Secret

REGISTRY_TOKEN (repo Actions secret) is a Forgejo PAT with write:package, read:package and write:repository: it pushes the image and the .upstream-version commit.

Building locally

podman build --build-arg MA_VERSION=2.10.9 -t music-assistant:test .
for t in tests/*.py; do podman run --rm -i --entrypoint /app/venv/bin/python music-assistant:test - < $t; done

Updating the patch

Edit the provider from the upstream tag and regenerate the diff with paths relative to site-packages (a/music_assistant/..., b/music_assistant/...), then build and run the tests as above.