Split software arch desc for easier manintenance. Many fixes related to next video playing and remote playback
🏗️ Build and Test JellyTau / Run Tests (push) Failing after 12s
🏗️ Build and Test JellyTau / Build Android APK (push) Has been skipped
Traceability Validation / Check Requirement Traces (push) Failing after 1s

This commit is contained in:
2026-03-01 19:47:46 +01:00
parent 3a9c126dfe
commit 09780103a7
45 changed files with 5663 additions and 3332 deletions
+4
View File
@@ -19,6 +19,7 @@
import PersonDetailView from "$lib/components/library/PersonDetailView.svelte";
import RelatedItemsSection from "$lib/components/library/RelatedItemsSection.svelte";
import ArtistDetailView from "$lib/components/library/ArtistDetailView.svelte";
import PlaylistDetailView from "$lib/components/library/PlaylistDetailView.svelte";
import CrewLinks from "$lib/components/library/CrewLinks.svelte";
import GenreTags from "$lib/components/library/GenreTags.svelte";
import CachedImage from "$lib/components/common/CachedImage.svelte";
@@ -545,6 +546,9 @@
{:else if item.type === "MusicArtist"}
<!-- Enhanced artist detail view with discography -->
<ArtistDetailView artist={item} />
{:else if item.type === "Playlist"}
<!-- Playlist detail view with track management -->
<PlaylistDetailView playlist={item} />
{:else}
<!-- Other content in grid view -->
<LibraryGrid
@@ -1,13 +1,17 @@
<script lang="ts">
import GenericMediaListPage from "$lib/components/library/GenericMediaListPage.svelte";
import CreatePlaylistModal from "$lib/components/playlist/CreatePlaylistModal.svelte";
/**
* Playlist browser
* @req: UR-007 - Navigate media in library
* @req: UR-008 - Search media across libraries
* @req: DR-007 - Library browsing screens
* @req: UR-014 - Make and edit playlists
*/
let showCreateModal = $state(false);
const config = {
itemType: "Playlist" as const,
title: "Playlists",
@@ -20,4 +24,22 @@
};
</script>
<GenericMediaListPage {config} />
<div class="relative">
<!-- Floating create button -->
<button
onclick={() => showCreateModal = true}
class="fixed bottom-20 right-4 z-40 w-14 h-14 bg-[var(--color-jellyfin)] hover:bg-[var(--color-jellyfin-dark)] rounded-full shadow-lg flex items-center justify-center transition-colors"
aria-label="Create playlist"
>
<svg class="w-7 h-7 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>
</button>
<GenericMediaListPage {config} />
</div>
<CreatePlaylistModal
isOpen={showCreateModal}
onClose={() => showCreateModal = false}
/>
+14 -16
View File
@@ -18,7 +18,7 @@
reportPlaybackProgress,
reportPlaybackStopped,
} from "$lib/services/playbackReporting";
import { cleanup as cleanupNextEpisode, handleEpisodeEnded } from "$lib/services/nextEpisodeService";
import { cleanup as cleanupNextEpisode } from "$lib/services/nextEpisodeService";
const itemId = $derived($page.params.id);
const queueParam = $derived($page.url.searchParams.get("queue"));
@@ -62,25 +62,12 @@
let pollInterval: ReturnType<typeof setInterval> | null = null;
let loadedItemId: string | null = null;
// Handle next episode navigation event from popup
function handlePlayNextEpisode(event: Event) {
const customEvent = event as CustomEvent<{ episode: MediaItem }>;
const episode = customEvent.detail.episode;
if (episode) {
goto(`/player/${episode.id}`);
}
}
onMount(() => {
// Start position polling (only for audio via MPV backend)
pollInterval = setInterval(updateStatus, 1000);
// Listen for next episode navigation events
window.addEventListener("playNextEpisode", handlePlayNextEpisode);
return () => {
if (pollInterval) clearInterval(pollInterval);
window.removeEventListener("playNextEpisode", handlePlayNextEpisode);
};
});
@@ -92,6 +79,7 @@
$effect(() => {
const id = itemId;
if (id && id !== loadedItemId) {
console.log("[AutoPlay] $effect triggered: loading new item", id, "(was:", loadedItemId, ")");
loadAndPlay(id);
}
});
@@ -526,10 +514,20 @@
async function handleVideoEnded() {
// Call backend to handle autoplay decision (works on both Android and Linux)
// Pass the item ID and repository handle so the backend can look up the item
// and check for next episodes. HTML5 video plays independently of the Rust
// backend queue, so the backend needs these to know what just finished.
const mediaId = currentMedia?.id ?? null;
console.log("[AutoPlay] Video ended. currentMedia:", mediaId, currentMedia?.name, "itemId (URL):", itemId);
try {
await invoke("player_on_playback_ended");
const repo = auth.getRepository();
const repoHandle = repo.getHandle();
await invoke("player_on_playback_ended", {
itemId: mediaId,
repositoryHandle: repoHandle,
});
} catch (e) {
console.error("[VideoPlayer] Failed to handle playback ended:", e);
console.error("[AutoPlay] Failed to handle playback ended:", e);
}
}