Repository navigation
Tell native clients how to play a file they cannot read - #301
Merged
Merged
Conversation
MKVs do not play on iOS: AVPlayer cannot demux Matroska whatever is inside it,
and nothing converted them. TranscodeMediaJob writes a converted_path that
playbackPath() already prefers, and needsConversion() already detects an
unplayable container -- but the job was dispatched from one place only, a
per-file button in the admin table, so a library of MKVs stayed unplayable
until somebody clicked each one.
GET /api/v1/items/{item}/playback answers with the same StreamPolicy the web
player uses, so one rule governs every client, and queues the permanent copy
when the container needs one. HLS makes it play now; the MP4 makes later plays
direct and makes an offline download possible at all.
GET /api/v1/items/{item}/stream?variant=original serves the file as it sits on
disk, because both are legitimate: a phone needs the MP4, a desktop wants the
original with its full bitrate and its other audio and subtitle tracks. The
playback endpoint reports direct_playable so the app can offer the choice
honestly rather than storing a gigabyte that shows black.
The clients do not call this yet.
Owner
Author
|
Reviewed on a5 — clean. 12 tests pass. Based on current main, no migration. This is the right server-side fix for the Avatar/MKV finding.
Nothing blocking. Note it's server-only (clients don't call it yet), so the iOS app still needs to adopt |
This was referenced Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
From your note on #300: MKVs do not play on iOS. Server side only — the clients do not call this yet.
Why it was broken
Two things meeting:
TranscodeMediaJobwrites aconverted_paththatplaybackPath()already prefers, andMediaTranscoder::needsConversion()already detects an unplayable container — but the job was dispatched from exactly one place: a per-file button in the admin table. A library of MKVs stayed unplayable until somebody clicked each one.The web player has asked
media.hls.decidewhich way to play something for a long time. The apps had no equivalent, so they fetched the file and hoped.GET /api/v1/items/{item}/playbackReuses
StreamPolicy, so one rule governs every client rather than the web and the apps drifting apart. Returns the decision, the URL,direct_playableandconverting.Asking also queues the permanent copy when the container needs one — HLS to play now, the MP4 so later plays are direct and an offline download is possible at all. Idempotent via
needsConversion().?variant=originalon the stream endpointPer the user's call: when a download is requested on a device with a container conflict, the app should ask whether they want the original, the compatible copy, or both. This is the server half — the client can now fetch either, and
direct_playableis what lets it ask honestly rather than storing a gigabyte that shows black.Testing
12 new tests, 221 pass across
Playback|Stream|Hls|Media|Api|Transcode. Three mutations confirmed to fail:asking_to_play_an_unplayable_file_queues_a_copyvariantignoredthe_original_can_be_asked_for_by_nameContentGateremoveda_restricted_profile_is_refusedTwo things worth your eye
1. The content gate filters by rating, not ownership. While testing I found the pre-existing
/streamand/detailsendpoints both answer 200 to a different account for the same item. So this endpoint matching them is correct rather than a hole I introduced — but if a shared server is meant to scope items per account, that is a real gap and it is the same everywhere. I did not change it here; it deserves its own decision.2.
needsConversion()is called on every playback request. It touches the filesystem to check whether the converted copy exists. That is cheap per call but it is on the path of every play. If you think it belongs behind a cache, say so.Not done
The clients do not call this yet, so the MKV is still a black rectangle on the phone until the app is taught to ask first.