feat: podcast player frontend + media service with 7 podcast feeds
Frontend: - PodcastPlayer.svelte — full podcast experience - Two-panel layout (show sidebar + episode list) - Sticky audio player bar with play/pause, seek, speed control - Queue panel with reorder/remove - Add podcast via RSS URL - Progress tracking every 30s - Auto-advance to next queued episode - Mobile responsive with show overlay Media Service: - 7 podcasts imported from Audiobookshelf (1,931 episodes) - Gateway wired with user auth headers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1551
frontend-v2/src/lib/components/media/PodcastPlayer.svelte
Normal file
1551
frontend-v2/src/lib/components/media/PodcastPlayer.svelte
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,11 +3,17 @@
|
||||
import BookSearch from '$lib/components/media/BookSearch.svelte';
|
||||
import MusicSearch from '$lib/components/media/MusicSearch.svelte';
|
||||
import BookLibrary from '$lib/components/media/BookLibrary.svelte';
|
||||
import PodcastPlayer from '$lib/components/media/PodcastPlayer.svelte';
|
||||
|
||||
type MediaTab = 'books' | 'music' | 'library';
|
||||
type MediaTab = 'books' | 'music' | 'library' | 'podcasts';
|
||||
|
||||
const urlMode = page.url.searchParams.get('mode');
|
||||
let activeTab = $state<MediaTab>(urlMode === 'music' ? 'music' : urlMode === 'library' ? 'library' : 'books');
|
||||
let activeTab = $state<MediaTab>(
|
||||
urlMode === 'music' ? 'music'
|
||||
: urlMode === 'library' ? 'library'
|
||||
: urlMode === 'podcasts' ? 'podcasts'
|
||||
: 'books'
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="page">
|
||||
@@ -17,6 +23,7 @@
|
||||
<div class="page-subtitle">
|
||||
{#if activeTab === 'books'}Book Downloads
|
||||
{:else if activeTab === 'music'}Music Downloads
|
||||
{:else if activeTab === 'podcasts'}Podcast Player
|
||||
{:else}Book Library{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,6 +37,10 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
||||
Music
|
||||
</button>
|
||||
<button class="tab" class:active={activeTab === 'podcasts'} onclick={() => activeTab = 'podcasts'}>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 11a9 9 0 0 1 9 9"/><path d="M4 4a16 16 0 0 1 16 16"/><circle cx="5" cy="20" r="1"/></svg>
|
||||
Podcasts
|
||||
</button>
|
||||
<button class="tab" class:active={activeTab === 'library'} onclick={() => activeTab = 'library'}>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
|
||||
Library
|
||||
@@ -40,6 +51,8 @@
|
||||
<BookSearch />
|
||||
{:else if activeTab === 'music'}
|
||||
<MusicSearch />
|
||||
{:else if activeTab === 'podcasts'}
|
||||
<PodcastPlayer />
|
||||
{:else}
|
||||
<BookLibrary />
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user