Wire up playback reporting, fix duration flash, hide video from audio mini player
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:
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { commands } from "$lib/api/bindings";
|
||||
import { truncateMiddle } from "$lib/utils/truncateMiddle";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
import { downloads } from "$lib/stores/downloads";
|
||||
import { goto } from "$app/navigation";
|
||||
@@ -152,7 +153,7 @@
|
||||
class="flex-1 min-w-0 text-left"
|
||||
>
|
||||
<p class="text-sm font-medium text-white truncate group-hover:text-[var(--color-jellyfin)] transition-colors">
|
||||
{album.album_name}
|
||||
{truncateMiddle(album.album_name, 40)}
|
||||
</p>
|
||||
<p class="text-xs text-gray-400 truncate">
|
||||
{album.artist_name || "Unknown Artist"} • {album.track_count} {album.track_count === 1 ? "track" : "tracks"}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { truncateMiddle } from "$lib/utils/truncateMiddle";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
import type { MediaItem } from "$lib/api/types";
|
||||
import LibraryGrid from "./LibraryGrid.svelte";
|
||||
@@ -171,7 +172,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-sm font-medium text-white truncate group-hover:text-[var(--color-jellyfin)] transition-colors">
|
||||
{album.name}
|
||||
{truncateMiddle(album.name, 40)}
|
||||
</p>
|
||||
{#if album.productionYear}
|
||||
<p class="text-xs text-gray-400">{album.productionYear}</p>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { truncateMiddle } from "$lib/utils/truncateMiddle";
|
||||
import type { MediaItem } from "$lib/api/types";
|
||||
import CachedImage from "$lib/components/common/CachedImage.svelte";
|
||||
|
||||
@@ -159,7 +160,7 @@
|
||||
|
||||
<!-- Episode title -->
|
||||
<h1 class="text-4xl font-bold text-white drop-shadow-lg">
|
||||
{episode.name}
|
||||
{truncateMiddle(episode.name, 64)}
|
||||
</h1>
|
||||
|
||||
<!-- Metadata -->
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { truncateMiddle } from "$lib/utils/truncateMiddle";
|
||||
import type { MediaItem } from "$lib/api/types";
|
||||
import { downloads } from "$lib/stores/downloads";
|
||||
import { formatDuration } from "$lib/utils/duration";
|
||||
@@ -134,7 +135,7 @@
|
||||
{episodeNumber}.
|
||||
</span>
|
||||
<h3 class="text-white font-medium truncate group-hover/row:text-[var(--color-jellyfin)] transition-colors">
|
||||
{episode.name}
|
||||
{truncateMiddle(episode.name, 56)}
|
||||
</h3>
|
||||
<!-- Played indicator -->
|
||||
{#if episode.userData?.isPlayed}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { truncateMiddle } from "$lib/utils/truncateMiddle";
|
||||
import { goto } from "$app/navigation";
|
||||
import { navigateBack } from "$lib/utils/navigation";
|
||||
import { currentLibrary } from "$lib/stores/library";
|
||||
@@ -229,7 +230,7 @@
|
||||
/>
|
||||
</div>
|
||||
<p class="font-medium text-white truncate group-hover:text-[var(--color-jellyfin)] transition-colors">
|
||||
{item.name}
|
||||
{truncateMiddle(item.name, 40)}
|
||||
</p>
|
||||
{#if item.productionYear}
|
||||
<p class="text-sm text-gray-400">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { MediaItem, Library } from "$lib/api/types";
|
||||
import { truncateMiddle } from "$lib/utils/truncateMiddle";
|
||||
import { downloads } from "$lib/stores/downloads";
|
||||
import { formatDuration } from "$lib/utils/duration";
|
||||
import CachedImage from "$lib/components/common/CachedImage.svelte";
|
||||
@@ -106,7 +107,7 @@
|
||||
<!-- Title & Subtitle -->
|
||||
<div class="flex-1 min-w-0 text-left">
|
||||
<p class="text-sm font-medium text-white truncate group-hover:text-[var(--color-jellyfin)] transition-colors">
|
||||
{item.name}
|
||||
{truncateMiddle(item.name, 56)}
|
||||
</p>
|
||||
{#if subtitle}
|
||||
<p class="text-xs text-gray-400 truncate">{subtitle}</p>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { MediaItem, Library } from "$lib/api/types";
|
||||
import { truncateMiddle } from "$lib/utils/truncateMiddle";
|
||||
import { downloads } from "$lib/stores/downloads";
|
||||
import CachedImage from "$lib/components/common/CachedImage.svelte";
|
||||
|
||||
@@ -166,7 +167,7 @@
|
||||
|
||||
<div class="mt-2 space-y-0.5">
|
||||
<p class="text-sm font-medium text-white truncate group-hover/card:text-[var(--color-jellyfin)] transition-colors">
|
||||
{item.name}
|
||||
{truncateMiddle(item.name, 40)}
|
||||
</p>
|
||||
{#if subtitle()}
|
||||
<p class="text-xs text-gray-400 truncate">{subtitle()}</p>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { commands } from "$lib/api/bindings";
|
||||
import { truncateMiddle } from "$lib/utils/truncateMiddle";
|
||||
import type { PlayTracksContext } from "$lib/api/bindings";
|
||||
import { goto } from "$app/navigation";
|
||||
import { queue } from "$lib/stores/queue";
|
||||
@@ -242,7 +243,7 @@
|
||||
<span
|
||||
class="text-white font-medium truncate group-hover:text-[var(--color-jellyfin)] transition-colors {currentlyPlayingId === track.id ? 'text-[var(--color-jellyfin)]' : ''}"
|
||||
>
|
||||
{track.name}
|
||||
{truncateMiddle(track.name, 48)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -356,7 +357,7 @@
|
||||
{#if currentlyPlayingId === track.id}
|
||||
<span class="inline-block mr-1">▶</span>
|
||||
{/if}
|
||||
{track.name}
|
||||
{truncateMiddle(track.name, 48)}
|
||||
</p>
|
||||
<p class="text-sm text-gray-400 truncate flex flex-wrap items-center gap-1">
|
||||
{#if showArtist && showAlbum}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<!-- TRACES: UR-004, UR-005, UR-028 | DR-009 -->
|
||||
<script lang="ts">
|
||||
import { commands } from "$lib/api/bindings";
|
||||
import { truncateMiddle } from "$lib/utils/truncateMiddle";
|
||||
import { goto } from "$app/navigation";
|
||||
import type { MediaItem } from "$lib/api/types";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
@@ -242,7 +243,7 @@
|
||||
<div class="p-6 space-y-6 flex-shrink-0">
|
||||
<!-- Title & Artist -->
|
||||
<div class="text-center">
|
||||
<h1 class="text-2xl font-bold text-white truncate">{displayMedia?.name}</h1>
|
||||
<h1 class="text-2xl font-bold text-white truncate">{truncateMiddle(displayMedia?.name, 48)}</h1>
|
||||
<div class="text-lg text-gray-400 mt-1 flex items-center justify-center gap-1 flex-wrap">
|
||||
{#if displayMedia?.artistItems?.length}
|
||||
{#each displayMedia?.artistItems as artist, i}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { commands } from "$lib/api/bindings";
|
||||
import { truncateMiddle } from "$lib/utils/truncateMiddle";
|
||||
import { goto } from "$app/navigation";
|
||||
import type { MediaItem } from "$lib/api/types";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
@@ -322,7 +323,7 @@
|
||||
<div
|
||||
class="text-sm font-medium text-white truncate block w-full text-left"
|
||||
>
|
||||
{displayMedia?.name}
|
||||
{truncateMiddle(displayMedia?.name, 40)}
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 truncate flex items-center gap-1">
|
||||
{#if displayMedia?.artistItems?.length}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { commands } from "$lib/api/bindings";
|
||||
import { truncateMiddle } from "$lib/utils/truncateMiddle";
|
||||
import { dndzone, SOURCES, TRIGGERS } from "svelte-dnd-action";
|
||||
import type { MediaItem } from "$lib/api/types";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
@@ -198,7 +199,7 @@
|
||||
<!-- Info -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium truncate {currentIndex === index ? 'text-[var(--color-jellyfin)]' : 'text-white'}">
|
||||
{item.name}
|
||||
{truncateMiddle(item.name, 48)}
|
||||
</p>
|
||||
{#if item.artists?.length}
|
||||
<p class="text-xs text-gray-400 truncate">
|
||||
|
||||
Reference in New Issue
Block a user