3425 Commits
Author SHA1 Message Date
amarildoandGitHub 33557f0eda chore(deps): update golang docker tag to 1.27.1 (#2289) 2026-09-08 16:21:35 +02:00
edde746 a4f3c5393b fix(release): scope symbol uploads to shipped platform artifacts
Mixed-platform build trees sent stale artifacts in unbounded DIF assembles. Select shipped identities explicitly, bound uploads, and require native, Dart-map, source, and release phases to complete.

Verified the actual mixed-build Android release against a local Bugs server, 10 uploader tests, 39 release tests, and scoped Dart analysis. The repository-wide analyzer reports unrelated warnings in nested worktrees; the pre-commit hook is skipped for that known check failure.
2026-09-08 12:58:11 +02:00
edde746 533028fa0e build(android): pin mpv-build with the audio-output stall watchdog
Moves the native pin to edde746/mpv-build@6fcf0f2 so Android MPV ends
playback with an audio-output error instead of freezing when the audio
device stops taking audio. Also picks up 5521906 (hardware subtitles
aligned with the video rectangle), which sits between the previous pin and
this one.
2026-09-08 02:38:04 +02:00
edde746 d7f1d4a8fc fix(player): exit with an audio-output error when the Android audio device stalls
On a Fire TV whose audio HAL stopped taking audio (#2255 investigation),
mpv playback froze silently: the AudioTrack writer blocked forever and
nothing surfaced. The native side now gives up after one bounded recovery
and ends the file with MPV_ERROR_AO_INIT_FAILED; this carries that verdict
to the UI.

The JNI bridge forwards mpv_event_end_file.error (previously discarded),
MpvEvent.EndFile exposes it as a typed MpvError, and
MpvEndFileDiagnostics tags an AO failure with the audio-output-failed
cause. The Dart failure policy treats that cause as terminal before any
HTTP diagnosis or the live-TV ladder, since re-opening the stream would
only run the same dead output again, and the player shows localized
guidance instead of raw log text. Progress is preserved; nothing is
marked completed.

Requires the mpv-build revision carrying the audiotrack stall watchdog;
until the lock is bumped, the new cause can only come from audio-only
media whose output fails to open.
2026-09-08 02:27:10 +02:00
github-actions[bot] c6c0ce8bea chore: update cask to 2.19.1 2026-09-07 20:20:58 +00:00
edde746 5a7e0966a3 chore: bump version to 2.19.1 2.19.1 2026-09-07 20:50:59 +02:00
edde746 c84b12eacd fix(livetv): allow cold MediaBrowser tuners time to open
Emby and Jellyfin Live TV could fail before playback when opening a cold tuner took longer than ten seconds. Use the existing thirty-second tune timeout only for live PlaybackInfo requests that open a source, preserving other request budgets and transport aborts.

Cover delayed initial tuning and recovery, bounded cancellation without replay, and unchanged VOD and metadata timeouts.

close #2274
2026-09-07 20:32:16 +02:00
edde746 f9b9fbf611 fix(player): restore Android MPV surfaces after screensaver
Paused playback could remain stuck after the screensaver destroyed its video and OSD surfaces, leaving later resume and initialization blocked.

Attach video and OSD surfaces together, drain placeholder frames, and bound surface retirement with explicit failure handling. Preserve native surface ownership through teardown and cover recreation, pause preservation, and disposal.

close #2249
2026-09-07 20:32:16 +02:00
edde746 e727f5bef4 fix(emby): negotiate MPEG-TS HLS for live TV
Emby Live TV can fail to parse fMP4 HLS fragments, and retrying the same negotiated URL cannot change the segment container.

Restrict Emby Live TV negotiation to the existing MPEG-TS profile on initial tune and direct-play recovery. Preserve direct play, stream copy, Jellyfin negotiation, and Emby VOD. Cover transport scoping and recovery with protocol regressions.

close #2273
2026-09-07 20:32:16 +02:00
edde746 3cf412b9c8 fix(player): use native AV1 decoding when Android hardware support is absent
Software MediaCodec AV1 decoding on Pixel fails during ambient lighting and lock/unlock surface transitions, leaving corrupt or black video.

Check hardware AV1 capability before decoder initialization and reuse the per-file software decoding policy to select dav1d with GPU output. Preserve hardware decoding for supported files.

close #2272
2026-09-07 20:32:15 +02:00
edde746 048bf688b2 fix(player): await terminal playback reporting before desktop exit
Closing the app during video playback left the backend session alive until it timed out. Await shared shutdown completion and the terminal video report before closing server clients, while keeping exit bounded when teardown stalls.

close #2275
2026-09-07 20:09:12 +02:00
edde746 8238705d3e fix(android): pin MPV with stable frame scheduling
Android MPV could turn timing jitter into uneven frame presentation without reporting dropped frames.

Pin mpv-build d38d607fd299e106a83f3557e310ac4b8202b39c to consume cadence prediction and refresh-selection hysteresis. Update the unified pin and Apple lock mirrors; only Android binary artifacts change.

Refs #2262
2026-09-07 07:01:09 +02:00
edde746 722c4ddd00 fix(homebrew): migrate cask to postflight steps
Homebrew warns when loading the deprecated postflight hook. Use structured steps with install-time appdir expansion while preserving recursive extended-attribute removal.

close #2259
2026-09-07 07:01:09 +02:00
56b990c04c feat(jellyfin): report the platform in the MediaBrowser client name (#2261)
Jellyfin and Emby sessions expose Client and DeviceName but no platform,
so dashboards and session trackers (Tracearr's normalizeClient, for one)
keyword-match the Client string the way they do for "Jellyfin Android TV"
and "Swiftfin tvOS". Plezy sent Client="Plezy" on every platform, so every
install showed up as platform "Plezy", and an unresolvable device name
became Device="Plezy" as well.

Add jellyfinClientName, which appends the platform ("Plezy Android TV",
"Plezy tvOS", "Plezy iOS", ...), and jellyfinDeviceName, which falls back
through the hardware model and the platform before the app name, and use
both at the two Jellyfin/Emby header call sites. Every emitted client
string was checked against Tracearr's matcher. The Plex headers and the
DisplayPreferences client key are unchanged.


Claude-Session: https://claude.ai/code/session_0134eXvhukgvG6Szg5NTwAim

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 01:39:07 +02:00
edde746 b904c11750 fix(network): use default ports for websocket connections
Watch Together could not reach relays when their URLs omitted a port because Dart passes port 0 to custom WebSocket connection factories.

Resolve port 0 to the HTTP/HTTPS default in the Happy Eyeballs factory while preserving explicit destination and proxy ports. Cover portless ws/wss connections with isolated regression tests.

close #2263
2026-09-07 01:30:08 +02:00
edde746 8870852a9e fix(player): reset TrueHD passthrough state after seeks
TrueHD passthrough retains stale muxer timing and partial MAT frames across seeks, including Android's startup decoder refresh. Pin mpv-build d3a1e12c9da054026aa0d546fee29e2ceeae0226, which publishes the upstream SPDIF reset backport for every native platform, and synchronize all Apple and non-Apple locks.

Refs #2262. The timing-warning regression is reproduced and fixed; sustained Fire TV judder still needs reporter confirmation.
2026-09-07 01:18:57 +02:00
github-actions[bot] 6039fde33b chore: update cask to 2.19.0 2026-09-06 21:34:04 +00:00
edde746 82ae3609f6 chore: bump version to 2.19.0 2.19.0 2026-09-06 22:11:54 +02:00
edde746 32f72c6dd2 chore(deps): normalize lockfile git revision quoting 2026-09-06 22:08:01 +02:00
edde746 8c5d0b4213 fix(seerr): refresh authority without crossing session ownership
Permission changes could be confused with expired authentication, and delayed session persistence could cross a profile boundary.

Refresh raw authority after route denials and foreground resumes without login or request replay. Fence refreshes to the current binding, serialize persisted session ownership, and reconcile open request surfaces after grants and revocations.
2026-09-06 20:21:11 +02:00
edde746 3dd5b5d48e fix(details): invalidate deleted items and pending actions
Deleting a detail item or its ancestor could leave stale controls visible and allow pending actions to complete.

Make exact and complete bulk deletion terminal for the detail subtree, invalidate asynchronous continuations, and retain a localized unavailable state when the first route cannot pop. Normalize translation sources and regenerate the affected output.
2026-09-06 20:21:11 +02:00
edde746 b6aa83af74 fix(watch-together): separate room authority from player bindings
Source reloads and role changes could reset room state or let obsolete player continuations change the current session.

Separate room authority from output bindings, fence selection and playback work with ownership leases, preserve room state across failed opens, and serialize native seek completion before newer intent.
2026-09-06 20:21:11 +02:00
edde746 4712414b3d fix(focus): reclaim detached grid nodes without stealing live focus
Historical focus contexts kept detached grid nodes alive, while stale menu captures could queue focus after their row was gone.

Track actual attachment lifetime, preserve pending and remembered destinations, and restore captured focus only to a live eligible leaf without overriding a covering route.
2026-09-06 20:21:11 +02:00
edde746 89b326e3f3 fix(catalog): retain native titles within the bounded matching budget
Native-title library copies could be missed when only display and romaji titles reached the bounded search budget.

Prefer typed Japanese titles, preserve row and detail aliases, and keep matching within the existing four-query budget and external-ID verification rules.
2026-09-06 20:21:11 +02:00
edde746 cf0dd0607e fix(profiles): keep playback preferences bound to their authenticated account
Picker ordering could select another account for playback preferences, and delayed operations could publish data after the active account changed.

Resolve playback authority independently of the account picker and fence repository, controller, and inline settings operations to their original authenticated account.
2026-09-06 20:21:11 +02:00
edde746 9a44f062a6 fix(network): own connection races and rearm exhausted push channels
One stalled DNS family or a cancelled WebSocket upgrade could keep a connection attempt alive. Push channels also stayed exhausted after the app returned to the foreground.

Race address families independently, own connection tasks through upgrade handoff, and rearm exhausted push channels on a genuine foreground resume.
2026-09-06 20:21:11 +02:00
edde746 178634e943 fix(watch-together): authenticate retained room recovery
A transport reconnect could recover the wrong room or retain obsolete host authority.

Authenticate retained membership and recover the relay-authoritative role without falling back to create or join. Release retained membership through authenticated teardown and document the coordinated relay rollout.
2026-09-06 20:21:11 +02:00
edde746 99f4fda55a fix(android): retire mpv sessions without blocking hook callbacks 2026-09-06 19:11:25 +02:00
edde746 908ccbb233 fix(android): retain mpv callbacks and end dismissed TV input sessions
Minified MPV initialization aborted because callback keep rules retained obsolete JNI signatures. Android TV IME dismissal also left editing active, requiring an extra Back press to leave the screen.

Keep only the named JNI callbacks without duplicating argument signatures and exercise them through the permanent minified reachability gate. Centralize native editing-session termination, observe owning-view keyboard dismissal, and reject stale completion or reopening work.

Verified the minified gate on Galaxy Z Fold3, debug MPV lifecycle and log-level suites on SHIELD and Pixel 7, native Done/Previous and two-Back behavior on SHIELD and Fire TV, 76 focused Flutter tests, analyzer parity, and shrinker checks. Both new regressions reject the original implementations.
2026-09-06 17:53:30 +02:00
edde746 2b176861b7 fix(mobile): keep landscape foreground controls clear of the trailing system inset
On a landscape phone, the app's mobile shell lets the navigation rail absorb the leading inset and leaves the trailing one for each screen to own — but several screens never did. The Plex-only alphabet scroll handle sat under the notch or rounded corner, the library grid's last column and the folder tree ran under it, the player's landscape header and timeline dropped horizontal insets entirely, and the catalog detail and Discover hero content used fixed margins. Automotive was already protected at the root.

Each region owner now applies a horizontal-only SafeArea to its foreground content: the browse tab's alpha overlay and content scroll view, the mobile video controls in landscape, the catalog detail's content column, and the Discover hero text and buttons. Backdrops, hero artwork, and the video surface stay full-bleed, and the root inset policy is unchanged.
2026-09-06 15:33:36 +02:00
edde746 647971a5d6 fix(seerr): follow permission grants and revocations on open request surfaces
A Seerr permission change reached the app only as a sign-in-time snapshot: a catalog detail decided once, when opened, whether it could show Request, and the request sheet only re-read the mask when something else rebuilt it. A grant left the action hidden until the screen was reopened, a revocation left it visible, and submitting then surfaced Seerr's raw English 403 body while the sheet stayed open with gates it no longer had. A disconnect while a detail was open left it holding a source wrapping a disposed client.

Permission changes now propagate through the existing provider graph without replacing clients: the client adopts the fresh `/auth/me` body it already fetches while telling a permission miss from a dead session, and a live-session denial is a typed `SeerrPermissionException`. The detail screen derives its Request eligibility from the account's current mask and source on every build; the sheet reconciles grants (loads destinations, shows 4K) and revocations (trims them, or closes with a localized reason the host surfaces) and re-checks authority locally before submitting.
2026-09-06 15:25:49 +02:00
edde746 a18c54f5ce fix(logs): keep record boundaries in copied log selections
Copying a drag or keyboard selection from the logs screen produced one run-on line: every log entry and the device header is its own paragraph, and Flutter's selection area concatenates adjacent paragraphs with no separator.

Each record is now its own selection container whose selected text ends with a newline, and the screen-level container drops the one dangling after the last selected record, so a partial drag copies exactly what was highlighted. Rows stay lazily built with their geometry unchanged, and the toolbar's copy-all export keeps its own format.
2026-09-06 15:16:37 +02:00
edde746 c5ed666aa9 fix(navigation): leave a deleted detail through its own route, not whatever is on top
Deleting the last episode or season from a detail page popped the wrong route whenever something covered the detail: the download progress dialog (the deletion event fires before the delete returns), a player, a sheet fallback modal, or another detail. The cover disappeared and the empty detail stayed on screen.

The detail now leaves through the route it was pushed as: popped when it is current, removed from under the cover otherwise, with the same null result; a detail that is the navigator's only route stays, and a route already leaving is not touched twice. A delayed player launch checks that the route it was started from is still active before pushing, so a launch whose detail was deleted during its awaits does not land on an unrelated screen.
2026-09-06 15:06:08 +02:00
edde746 6580c4cf97 fix(library): credit a content push only when the reload it triggered lands
A library that a server push had marked stale stayed stale for good if the reload triggered on the next activation failed: the tab wrote the push's epoch down as consumed before starting the reload, so every later activation, tab switch, and visibility replay saw nothing to do until another push arrived. The same shared load-start snapshot was also read by whichever load committed last rather than the one that had taken it, and in folder grouping the epoch was credited from a paged fetch nobody renders while the displayed folder tree was never reloaded.

Each load now owns its epoch snapshot and commits it only when its data actually landed and it is still the current load; a failed or dropped load releases the snapshot without credit. An in-flight snapshot still dedupes an activation that lands mid-fetch. Folder grouping refreshes the displayed tree and credits the push only when that refresh succeeded, and switching libraries resets the previous library's credit.
2026-09-06 15:04:51 +02:00
edde746 3ca565baa9 fix(watch-together): adopt the room on promotion independently of the player, and fence old-role continuations
A guest promoted to host while its player was detached — mid-reload, in the lobby, between episodes — took the room over with no epoch: the coordinator could not answer state requests, and when the player rebound it opened a fresh epoch that resumed a paused room, reset the room rate to the new host's saved speed, and published the reload position as the room's timeline. Separately, commands issued under the previous role kept landing after the role changed: a guest's end-of-file hard seek still played the player after promotion, leaving the new host broadcasting a moving position while gating on peers, and a demoted host's rate continuation still reported a room rate change.

The host coordinator now adopts the room — epoch identity, rate, intent, phase, and the inherited anchor — the moment the role changes, whether or not a player is bound; a player bound later for the same media rebinds into that epoch and keeps its pause, rate, and timeline. Authority over the player is a per-engine generation, revoked before every role change, epoch change, detach, and disposal, and every asynchronous follow-up captures and re-checks it; the attachment's acknowledgement ledger still survives role swaps, and the coordinator now watches consumed acks so a play its predecessor set in motion cannot run under a stopped room. The reconciler ends its epoch when the host leaves the player, so a promotion after that adopts nothing.
2026-09-06 15:04:39 +02:00
edde746 bc1a715411 fix(linux): read the restart position off the GTK main thread
Every seek on Linux stalled the UI thread: the playback-restart handler read `time-pos` with a synchronous mpv_get_property from the GLib main context, which parks that thread on mpv's playloop for as long as the core takes — and the core's render path can itself be waiting on the main thread to post the next render job.

The position is now requested through the existing asynchronous property registry when the restart is dequeued, with the source identity captured at that moment, and the unchanged playback-restart envelope is sent when the reply lands. A source boundary — start-file or end-file — delivers any restart still waiting as a position-less envelope for its captured source and retires the reply, so no restart is ever reported after its source's boundary or attributed to the next source. No new event type or thread.
2026-09-06 15:00:49 +02:00
edde746 c20a6cf162 fix(catalog): keep a library copy's label through a retry that lost its stamp
A catalog detail's library labels disappeared for the rest of the session after a re-lookup: the matcher replaced each answering server's copies with the raw items of the new wave, so a Jellyfin retry whose best-effort ancestors stamp failed overwrote a labeled copy with an unlabeled one, and the unlabeled copy was then memoized as the authoritative hit.

Membership stays the answering server's to define, but every copy it returns again is folded onto its predecessor through the shared merge policy, which now treats library id and title as a pair: a copy naming a different library takes the new id with its own (possibly null) title instead of inheriting the old library's name, one naming the same library fills a missing title, and one naming no library keeps the known pair.
2026-09-06 15:00:27 +02:00
edde746 ab1c9b9afd fix(tv): claim the whole Back press when the text-input host closes an IME session
On Android TV, pressing Back to dismiss the keyboard while editing a field with no back handler of its own — an mpv config row, for instance — also popped the screen. The shared native text-input host consumed only the Back key-down that closed the IME session; the matching key-up then reached an ancestor that pops on key-up, and the same press's platform back callback was never deduped.

The host now marks the back coordinator and arms the key-up suppressor when it closes the session, and consults the suppressor before the field's own onBack so a field without one still swallows its in-flight key-up. IME completion and a second, intentional Back behave as before.
2026-09-06 15:00:27 +02:00
edde746 9ef3de985f fix(livetv): keep backed channel logos legible when adapting them to light surfaces
A light logo that carries its own dark ink — dark text on a white plate, white letters inside a dark outline — lost its contrast on light theme surfaces: the tone analysis granted the light-neutral remap to any mostly-light frame, and the remap then folded the plate and its ink into one dark tone.

A light majority no longer grants the remap. The analysis first decides whether the transformation is safe: on the sampled grid, four linear scans find neutral pixels enclosed on every side by the opposite neutral tone, and when most of either tone is enclosed the artwork is classified as backed and left untouched, like a dark disc already was. Dark ink that only touches the mark along an edge — an underline, a badge — stays open and keeps adapting, so accented and mixed-color marks remap as before. `LogoTone.dark` is renamed `backed` to name what the gate protects.
2026-09-06 15:00:09 +02:00
edde746 54438c8d7a test(android): drive fallback opens through an explicit native-command seam
Three ExoPlayerPluginTest fallback cases failed after commandForSource started rejecting a load with no native player: the test core seamed property writes but not commands, so every fallback loadfile reported OPEN_FAILED. The core now takes an optional command runner beside the property writer; with none, a command without a native player still fails. The tests assert what mpv was handed — the loadfile URI as open ownership and its pause option as playback intent — instead of reflecting private flags, and one sets up its held state through setPauseIntentForLoad. ExoPlayer production behavior is unchanged.
2026-09-06 14:53:05 +02:00
edde746 1d5b175cc8 fix(android): bind every libmpv JNI call and callback to its native session
Tearing down the mpv player while a hook handler or property write was still in flight could reach the replacement player: the native side kept one process-global handle, the Kotlin wrapper was published before the native session existed, callbacks were routed to whichever wrapper was current, and hook continuations were validated only against a `closed` flag read before the native call. A retiring player's on_preloaded handler could therefore reconfigure the successor's tracks, its late `mpv_hook_continue` could land on the successor's core, and the successor could ingest the predecessor's end-file and property tail.

Every native session now has an immutable monotonic id minted by nativeCreate. Create/init/destroy hold a write lock; every other JNI entry takes a read lock and is refused when it names a retired session, so a call admitted under a live session keeps that handle alive until it returns and retirement waits for it. The event thread is bound to its session before it starts and stamps every callback with it; Kotlin dispatches a callback only to the wrapper published for that session and passes its session on every native call. create() publishes the wrapper only after the native session exists, and a superseded create fails instead of publishing a dead session.
2026-09-06 14:50:51 +02:00
edde746 b80aa1916e chore(watch-together): regenerate the recent-rooms serializer with the pinned toolchain 2026-09-06 14:43:03 +02:00
edde746 5c40ba0505 build(android): pin mpv-build with the pending-vid property
Move the native pin to edde746/mpv-build@3fd89c2, whose android binaries carry
fork patch 0010 (read-only pending-vid: mpv's own default video selection,
readable inside on_preloaded). The Android decode-policy hook reads it to route
DV-P5 and Hi10 for the track mpv will actually select. Only the android group
moved (key 23f2764e9e97 -> d109720a9b8e); linux, windows and Apple bytes are
unchanged.
2026-09-06 13:49:41 +02:00
edde746 cbc47790c3 test(watch-together): lift the widget-test HTTP mock for loopback relay tests
The relay-authority tests drive a real loopback relay; with the upgrade now
running on a per-attempt HttpClient, the widget binding's mock client
answered every upgrade with 400.
2026-09-06 13:33:39 +02:00
edde746 baa31742cb fix(player): bind live TV clock generations to the source the load reports
Live-TV clock generations were matched to mpv sources first-in-first-out: every
start-file popped the oldest registered open, on the assumption of exactly one
start-file per open in dispatch order. Opens without a generation on the same
player, an Android loadfile rejected silently by nativeCommand, and the
independent delivery of the command ack and the start-file event all broke
that, so a seek that reopened the stream could calibrate against the wrong
source.

The loadfile reply now carries mpv's playlist_entry_id on Android, Apple, Linux
and Windows, PlayerNative.open resolves with it, and the live session binds
each generation to that id explicitly. Source events that land before the
reply are buffered per id and replayed on binding; a rejected or unreachable
load fails its generation instead of leaving a phantom; opens with no
generation are invisible to clock binding. Android now reports a rejected mpv
command as COMMAND_FAILED like the other cores.
2026-09-06 13:29:37 +02:00
edde746 01750ad92b fix(network): own a WebSocket connection attempt through cancellation and TLS
Cancelling a WebSocket connect (deadline, stop, dispose, a newer attempt)
only dropped or timed out a future: the shared never-closed HttpClient kept
the address race and TLS handshake alive, and once SecureSocket.secure had
detached the raw socket even a real task cancel was a no-op, so after
ClientHello the peer stayed open and the connect future never settled.

The happy-eyeballs task now owns the transport through the TLS handoff:
cancel is idempotent, settles the task at once, destroys an undelivered raw
winner, and destroys whatever a handshake in flight eventually yields. Each
WebSocket upgrade runs on its own HttpClient inside a connection attempt
with completion plus cancellation; the library-event, companion-remote and
Watch Together relay sockets cancel it on stop, disposal, deadline and
supersession. dart:io offers no handle to abort an in-flight handshake, so
a black-holed ClientHello closes when the handshake settles, not at cancel.
2026-09-06 13:28:51 +02:00
edde746 5de2d28166 fix(seerr): only a rejection Seerr itself emits may invalidate a session
A JSON-bodied 401 or 403 from a gateway or auth proxy in front of Seerr was
read as a Seerr session rejection, so a valid Quick Connect session was
unlinked by a Cloudflare or forward-auth wall, and the /auth/me confirmation
probe was never checked for the same.

One endpoint-aware classifier now recognizes only the two rejection shapes
Seerr's middleware and error handler actually produce (403 with their exact
bodies; the forwarded Jellyfin 401 on /auth/jellyfin) and is applied to the
primary request, the identity probe, the post-reauth retry, login and the
post-login identity read. Anything else keeps the stored credentials and
surfaces as an intermediary failure; genuine expiry and credential rejection
keep their re-auth and unlink behavior.
2026-09-06 13:24:52 +02:00
edde746 88e6d33221 fix(seerr): drop stale destination detail loads in the request sheet
Switching the destination server or the 4K variant while a previous server's
details were still loading let the late response install the old profiles,
root folders and tags, clear the loading state, and overwrite tags the user
had edited; adopting no server left the spinner stuck.

Every destination/variant adoption, including null, now advances a
selection generation; a detail load applies neither its success nor its
failure unless it still belongs to the accepted generation, defaults hydrate
once per generation, and an explicitly emptied tag list stays empty.
2026-09-06 13:24:52 +02:00
edde746 ac7fb87ade fix(android): route decode policy by the video track mpv will select
The Dolby Vision and Hi10 decode policies run in mpv's on_preloaded hook,
before track selection, and read the first demuxed video track. A file whose
first video track is not the one mpv picks (attached pictures, a
default-flagged second track, --vlang, an explicit vid) got the wrong
routing.

The hook now resolves one pending track for both policies: an explicit
vid=no or vid=N is authoritative on its own, and auto asks the fork's
pending-vid property, which runs mpv's own default selection ahead of time.
On a libmpv without that property the first-track guess stays, logged once.
The predecoder gate is unchanged (#2065). The fork patch lands separately
through the native pin flow.
2026-09-06 13:22:20 +02:00
edde746 ce3a3b177c fix(watch-together): keep the room position across a host promotion
A guest promoted to host published its own player position as the room's
anchor the moment its readiness gates passed, but readiness is not
alignment: that player could be mid-correction, drifted, or at a stale
pre-seek spot, and every guest was then pulled to the wrong position.

The controller now reads the room position on the old host's clock before
discarding it and hands it to the new coordinator as a transition anchor.
The promoted host broadcasts that anchor, gates the group start, and aligns
its own player to it through the normal seek path; only once the seek has
rendered (or cannot: live, or a render timeout) does its local position
become the room's. A player replaced mid-handover inherits the anchor, and
a paused room hands over its paused position unchanged.
2026-09-06 13:22:01 +02:00