Wire up playback reporting, fix duration flash, hide video from audio mini player
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 4m14s
Traceability Validation / Check Requirement Traces (push) Successful in 21s
🏗️ Build and Test JellyTau / Build Android APK (push) Successful in 19m3s

Playback reporting (position sync / resume-on-another-device):
- player_configure_jellyfin now builds a PlaybackReporter sharing the player
  controller's Arc, so Start/Progress/Stopped actually reach Jellyfin on every
  auth path (login/restore/reauth); previously they never did.
- The PlaybackReporterWrapper now shares the same Arc the controller and MPV
  progress loop report through, instead of a dead parallel Option.
- Android position callbacks now emit throttled progress reports (30s/item),
  mirroring the MPV backend.

Duration flash on pause:
- resolveDuration() prefers the live store duration for the already-loaded
  track over the runTimeTicks estimate, so pausing no longer clobbers the
  slider's max to 0 when runTimeTicks is missing.

Video leaking into audio mini player:
- isVideoItem() also checks the backend PlayerMediaItem mediaType
  discriminator, so a video started via player_play_item (no Jellyfin `type`,
  mediaType "video") no longer surfaces in the audio mini player.

Middle-truncation of long media names:
- New truncateMiddle util applied to track/episode/card/mini-player titles so
  distinguishing tails (episode numbers, suffixes) stay visible.

Adds regression tests for the duration and mini-player fixes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 21:52:27 +02:00
co-authored by Claude Opus 4.8
parent dcee342c47
commit 342f95cac1
21 changed files with 420 additions and 28 deletions
+25 -2
View File
@@ -250,6 +250,29 @@ export const mergedVolume = derived(
}
);
/**
* Whether an item is video content and therefore must NOT appear in the audio
* mini player. Checks the Jellyfin item type (Movie/Episode/live TV) AND the
* backend queue item's `mediaType` discriminator.
*
* The `mediaType` check is essential: a video started via `player_play_item`
* (every Movie/Episode goes through VideoPlayer) is pushed onto the backend
* queue as a PlayerMediaItem with NO `type` field but `mediaType: "video"`.
* Without this, that queue item's absent `type` slips past the Movie/Episode
* check and the video surfaces in the audio mini player after leaving /player.
*/
function isVideoItem(item: MediaItem | null): boolean {
if (!item) return false;
const type = item.type;
if (type === "Movie" || type === "Episode" || type === "TvChannel") {
return true;
}
// Backend PlayerMediaItem carries a lowercase mediaType discriminator that is
// present even when the Jellyfin `type` is absent (video-only play requests).
const mediaType = (item as { mediaType?: string }).mediaType;
return mediaType === "video";
}
/**
* Should show audio miniplayer - state machine gated
* Only true when:
@@ -268,8 +291,8 @@ export const shouldShowAudioMiniPlayer = derived(
// Determine media type from the player state, falling back to the queue
// item (the player store can momentarily lack media during transitions).
const mediaType = $media?.type ?? $queueItem?.type;
if (mediaType === "Movie" || mediaType === "Episode") {
const item = $media ?? $queueItem;
if (isVideoItem(item)) {
return false;
}