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
+19
View File
@@ -76,6 +76,25 @@ describe("shouldShowAudioMiniPlayer", () => {
expect(get(shouldShowAudioMiniPlayer)).toBe(false);
});
it("hides a video queue item that has no Jellyfin `type` but mediaType 'video'", () => {
// Regression: player_play_item (every Movie/Episode via VideoPlayer) pushes
// a backend PlayerMediaItem onto the queue with item_type: None but
// media_type: Video → serialized with no `type` and mediaType: "video".
// Without the mediaType check this slipped past the Movie/Episode guard and
// surfaced the last-played video in the audio mini player after leaving
// /player.
const videoQueueItem = { id: "v2", mediaType: "video" } as unknown as MediaItem;
currentQueueItem.set(videoQueueItem);
player.setIdle();
expect(get(shouldShowAudioMiniPlayer)).toBe(false);
});
it("hides for a live TV channel", () => {
const channelItem = { id: "c1", type: "TvChannel" } as unknown as MediaItem;
player.setPlaying(channelItem, 0, 0);
expect(get(shouldShowAudioMiniPlayer)).toBe(false);
});
it("shows in remote mode when the session has a now-playing item", () => {
isRemoteMode.set(true);
selectedSession.set({ nowPlayingItem: { id: "r1" } });