Commit Graph
21 Commits
Author SHA1 Message Date
toluLikesToCode 8740a19f36 feat(player): start Plex transcodes at the resume position (#1817)
A Plex transcode session always starts producing at zero: the decision
request never sent offset=, so any non-zero open - resuming a
transcoded title, or switching from Direct Play to a transcoded
quality mid-playback - opened a session whose produced window begins
at the start of the file and seeked it. mpv immediately requests a
segment the transcoder has not produced, PMS answers 404 for it and
every subsequent segment, and playback buffers forever.

Send offset=<seconds> (6dp) with the decision and start request - the
view offset on initial open, the resolved resume position on every
in-place reload - so the session begins producing at the position the
player consumes first. The playlist timeline is unchanged: an offset
session's media playlist still covers the full title from segment
zero, so the player keeps opening with start: at the resume position
and in-stream seeks work as before.

Before a native player opens an offset playlist, waitForTranscodeReady
walks the master playlist, the media playlist, and the segment
containing the offset, because PMS can publish a manifest before that
segment is fetchable and mpv treats the 404 as an HLS error. The probe
is best-effort: it never fails an open, hands off immediately on HTTP
500 (on the response and exception paths alike) so the server-limit
dialog stays prompt, stops on cancellation, skips itself when the
playlist durations never reach the offset, and stays out of the
endpoint-failover cascade. In-place reloads resolve the replacement
source only after the old stop report has gone out, so Plex cannot use
that stop to terminate the replacement transcode.

close #1840
2026-08-09 06:28:59 +02:00
edde746 4607d165fd fix(automotive): keep video from starting while a car is driving
DD-3 gives video no exemption: a restricted vehicle must not play it at all. The
gate is read at the single point where media actually opens, so every path that
can start a picture - an explicit play, a gapless arm, a track or channel switch,
a frame-rate-match resume, a reload, and the queue navigation commands of the OS
media session - is covered by one check rather than by a guard at each call site.
A seek can also start playback with no play call, because mpv resumes when it
seeks off the end of a file, so a restricted seek is followed by a pause.

Watch Together needed the pause to be local. A vehicle stopping one peer is not a
room-wide intent: a guest's forced pause is swallowed by the attachment's ledger
rather than published, while a host's still pauses the room, because a host that
kept broadcasting a frozen anchor would stall or rewind every guest it was meant
to protect. The layer that owns a pause owns the resume for it, and one
acknowledgement is recorded per event, so a surplus cannot eat the user's next
real pause.
2026-08-06 03:45:09 +02:00
edde746 f93952ba6f fix(android): stop tunneling 24p video on the Fire TV Stick 4K
Tunneled playback on an AFTMM judders continuously through 23.976p direct play.
The #1802 reporter isolated it: turning off Tunneled Playback with every other
setting unchanged makes it smooth, and their log shows tunneling active for the
whole session with E-AC3 bitstreamed and the decoded-PCM guard never firing.

Audio Passthrough looked like the trigger only because it is the one user-facing
switch that decides it. Passthrough off, or Downmix to Stereo on, both force the
Dolby track to decode to PCM, which trips the #1458 guard and takes tunneling
down with it. Passthrough on with downmix off is the only combination that keeps
a bitstreamed track, so it is the only one that stays tunneled.

Withdraw tunneling on that model for content at or below 30fps. The cut-off
keeps 4K50/60 tunneled, which is the workload Amazon documents the feature for.
The mechanism stays unconfirmed: tunneling fires no VideoFrameMetadataListener
and stops media3 counting frames in the codec, so nothing app-side can measure
the cadence. Only the trigger is established, and the quirk is scoped to it.

That needs a frame rate the app did not have. Neither MatroskaExtractor nor
Mp4Extractor populates Format.frameRate, and a tunneled session renders no
frames back for the native detector, so the server's rate now rides on the open
call. It is sent only for direct play, matching _primeDisplayCriteria: a
transcode's metadata describes the source, not what the server is about to send.

Also move Audio Passthrough out of the in-player settings sheet. It configures
the audio output route rather than the current playback, and applying it
mid-stream bounces the audio renderer and re-decides tunneling. Settings > Video
Playback already owns it, next to Tunneled Playback, which is applied the same
way. That description now mentions stutter, not only black HDR video, so the
workaround is findable on hardware this quirk does not cover.

The mpv backend failing to start the same 4K file is a separate defect and is
not addressed here; its uploaded log is no longer retrievable.
2026-08-06 03:45:09 +02:00
edde746 86c8011b72 fix(player): tell the user when the server cannot read the media file
A 404 on the media stream means the server resolved the item but could not
open the file behind it — moved, deleted, or on storage that went away.
Jellyfin maps the resulting FileNotFoundException to 404, and PlaybackInfo
never stats the file, so negotiation succeeds and only the stream request
fails. Playback then died with a snackbar reading "Failed to open
[REDACTED_URL]" before popping the route, which tells the user nothing and
leaves nothing useful in a bug report.

Generalize the HTTP-500 log probe into PlayerError.httpStatusFromLog and
latch every status in fatalPlaybackHttpStatuses. Each latches on its own so
the 503 that stream-lavf-o deliberately retries cannot mask the fatal status
behind it. A 404 now raises a dedicated modal naming the cause and the fix.

On Android a 404 previously failed the "Response code: 500" string test and
fell through to the ExoPlayer→MPV fallback, showing "switching to compatible
player" before failing again on the same request. Read the real status off
HttpDataSource.InvalidResponseCodeException instead and skip the fallback:
an HTTP status is not a codec problem.
2026-07-31 21:45:33 +02:00
edde746 daab4f1e24 fix(player): preserve the forced-subtitle class across episode boundaries
Plex treats a subtitle stream as forced when its title says "Forced" even
with the API flag unset. Every forced comparison now uses that effective
forced-ness on both sides: the match scorer, the low-metadata hard gate,
the Jellyfin OnlyForced/Smart profile modes, and stream-index negotiation.

Carrying a track choice into the next episode no longer reuses the
same-item identity matchers. A sealed SubtitlePreference (off / track
reference / semantic intent) replaces the id-'navigation' pseudo-track
through the whole preference channel, and cross-item intents hard-require
language and forced-class parity. When the next episode has no track of
the same class, the intent declines and selection falls through to the
server's own per-episode choice instead of latching onto a full track by
position and persisting that mistake back to the server.

Intents wait for pending native tracks under the same catalog-completeness
rule as source ids, so an early decline cannot retire the selection
listener before the real track arrives.

Ref #1716
2026-07-30 02:46:10 +02:00
edde746 2b3853a882 fix(player): preserve transcoded subtitles at high speed
close #1622
2026-07-25 04:21:37 +02:00
edde746 0643787fbe fix(android): prevent ghost playback after autoplay failures
close #1673
2026-07-25 04:12:54 +02:00
edde746 e0bf66eea8 fix(runtime): harden application service boundaries 2026-07-24 03:46:46 +02:00
edde746 0707d4d9b1 fix(live-tv): stabilize HLS playback 2026-07-18 20:04:09 +02:00
edde746 a1b6a89714 fix(player): load subtitle sidecars with media
close #1583
2026-07-17 23:09:07 +02:00
edde746 abf1027b77 feat(plex): migrate video transcoding to HLS 2026-07-14 18:11:04 +02:00
edde746 9b6c12d086 style: format playback_open 2026-07-10 21:26:25 +02:00
edde746 3bf72d7237 fix(player): auto-reconnect network streams on transient HTTP 503
ffmpeg's reconnect gave up on the first HTTP-error response, so a PMS
answering 503 (startup maintenance) after an idle-pause disconnect
surfaced as a clean mid-file EOF instead of riding through the outage.
2026-07-10 19:07:29 +02:00
edde746 21c4ab430e feat(android): tune frame rate and subtitle rendering 2026-06-24 01:34:03 +02:00
edde746 618251b8eb fix(player): attach external subtitles during open 2026-06-15 17:21:08 +02:00
edde746 8bf5dbb379 fix(player): resume mpv startup frame after subtitles 2026-06-15 12:06:04 +02:00
edde746 7195702242 fix(player): repair Plex transcode seeking
close #1341
2026-06-14 23:39:05 +02:00
edde746 f3cb508649 fix(player): wait for mpv subtitles after load
close #1331
2026-06-13 23:52:19 +02:00
edde746 90662e2173 fix(player): scale mpv stream ring buffer for poorly interleaved mp4/mov 2026-06-12 22:21:17 +02:00
edde746 a5c7d5b52a refactor(watch-together): host-authoritative declarative sync protocol
Replaces the imperative play/pause/seek/positionSync message soup with a
single host-authored PlaybackState (seq-ordered, anchor-extrapolated,
phase machine: loading/waitingForPeers/paused/playing) that doubles as
the heartbeat, plus guest status reports and host-applied control
requests. Fixes the guest seek-back loop while the host loads (readiness
was keyed on a pre-load !buffering snapshot and heartbeats broadcast
frozen positions), adds real group buffering coordination (stall grace,
scheduled simultaneous resumes, 15s safety timeout), rate-nudge drift
correction with passthrough-aware seek fallback, session-scoped message
handling (no lost messages during episode-switch detach gaps), and an
expected-state ledger replacing the racy remote-action flag.
2026-06-11 09:42:38 +02:00
edde746 cd9498abb8 refactor(player): centralize playback opening
close #1280
2026-06-10 02:26:26 +02:00