improvements to the sleep timer
🏗️ Build and Test JellyTau / Run Tests (push) Failing after 14s
Traceability Validation / Check Requirement Traces (push) Failing after 2s
🏗️ Build and Test JellyTau / Build Android APK (push) Has been skipped

This commit is contained in:
2026-02-28 20:33:22 +01:00
parent e8e37649fa
commit c5be9eb18c
19 changed files with 475 additions and 57 deletions
+1 -1
View File
@@ -158,7 +158,7 @@
</div>
<!-- User menu -->
<div class="flex items-center gap-3">
<div class="ml-auto flex items-center gap-3">
<span class="text-sm text-gray-400 hidden md:inline">{$currentUser?.name}</span>
<!-- Desktop: Downloads icon -->
+28
View File
@@ -57,6 +57,7 @@
let error = $state<string | null>(null);
let showResumeDialog = $state(false);
let savedProgress = $state<{ positionSeconds: number; progressPercent: number } | null>(null);
let nextEpisode = $state<MediaItem | null>(null); // Next episode for video skip button
let pollInterval: ReturnType<typeof setInterval> | null = null;
let loadedItemId: string | null = null;
@@ -408,6 +409,11 @@
isPlaying = true;
loading = false;
// Fetch next episode for video episodes (for skip button)
if (isVideo && currentMedia) {
fetchNextEpisode(currentMedia);
}
} catch (e) {
console.error("loadAndPlay error:", e);
// Show detailed error including the full error object
@@ -520,6 +526,26 @@
}
}
async function fetchNextEpisode(media: MediaItem) {
nextEpisode = null;
if (media.type !== "Episode" || !media.seriesId) return;
try {
const repo = auth.getRepository();
const episodes = await repo.getNextUpEpisodes(media.seriesId, 1);
if (episodes.length > 0 && episodes[0].id !== media.id) {
nextEpisode = episodes[0];
}
} catch (e) {
console.error("Failed to fetch next episode:", e);
}
}
function handleSkipToNextEpisode() {
if (nextEpisode) {
goto(`/player/${nextEpisode.id}`);
}
}
function formatTime(seconds: number): string {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
@@ -588,6 +614,8 @@
onReportProgress={handleReportProgress}
onReportStop={handleReportStop}
onEnded={handleVideoEnded}
hasNext={nextEpisode !== null}
onNext={handleSkipToNextEpisode}
/>
<NextEpisodePopup />
{:else}
+35 -1
View File
@@ -23,8 +23,18 @@
interface VideoSettings {
autoPlayNextEpisode: boolean;
autoPlayCountdownSeconds: number;
autoPlayMaxEpisodes: number;
}
const episodeLimitOptions = [
{ value: 0, label: "Unlimited" },
{ value: 1, label: "1" },
{ value: 2, label: "2" },
{ value: 3, label: "3" },
{ value: 5, label: "5" },
{ value: 10, label: "10" },
];
let settings = $state<AudioSettings>({
crossfadeDuration: 0,
gaplessPlayback: true,
@@ -35,6 +45,7 @@
let videoSettings = $state<VideoSettings>({
autoPlayNextEpisode: true,
autoPlayCountdownSeconds: 10,
autoPlayMaxEpisodes: 0,
});
let loading = $state(true);
@@ -172,7 +183,7 @@
}
</script>
<div class="max-w-2xl mx-auto space-y-8 p-6">
<div class="max-w-2xl mx-auto space-y-8 p-6 pb-24 h-full overflow-y-auto">
<div>
<h1 class="text-3xl font-bold text-white mb-2">Audio Settings</h1>
<p class="text-gray-400">Configure playback and audio processing</p>
@@ -359,6 +370,29 @@
<span>30s</span>
</div>
</div>
<!-- Episode Limit -->
<div class="pt-4 border-t border-gray-700">
<div class="mb-4">
<p class="text-sm font-medium text-gray-300">Episode Limit</p>
<p class="text-xs text-gray-500 mt-1">
Stop auto-playing after this many consecutive episodes
</p>
</div>
<div class="grid grid-cols-3 md:grid-cols-6 gap-2">
{#each episodeLimitOptions as option}
<button
onclick={() => { videoSettings.autoPlayMaxEpisodes = option.value; }}
class="py-3 px-3 rounded-lg transition-all text-sm
{videoSettings.autoPlayMaxEpisodes === option.value
? 'bg-[var(--color-jellyfin)] text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'}"
>
<div class="font-semibold">{option.label}</div>
</button>
{/each}
</div>
</div>
{/if}
</div>
</div>