-
Notifications
You must be signed in to change notification settings - Fork 36
Player Persistence, Advanced Caching, and Mobile UI Enhancements #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Hey! Thanks for the PR. I'm gonna do something I've never done before and get Copilot to check it - maybe the results will be good. It's mainly because I've done a quick config resolution that may have caused issues. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This pull request introduces significant enhancements focused on player state persistence, service worker caching improvements, API health monitoring, and mobile UI optimizations. The changes aim to provide a more resilient and user-friendly experience, particularly for mobile users and those experiencing API connectivity issues.
Key Changes:
- Implements localStorage-based player state persistence to maintain playback queue and progress across page refreshes
- Introduces a dual-cache strategy in the service worker with separate versioned caches for app assets and persistent data
- Adds an API health-checking system that monitors endpoint availability and routes requests to healthy servers
Reviewed Changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
| src/service-worker.ts | Implements hybrid caching with separate APP_CACHE and DATA_CACHE, adds proxy URL caching for audio streams |
| src/lib/stores/player.ts | Adds player state persistence using localStorage, saves queue, track, and playback position |
| src/lib/stores/apiHealth.ts | New store that periodically checks API endpoint health and maintains list of healthy targets |
| src/lib/config.ts | Integrates health checking into API routing, routes requests to healthy endpoints based on health store |
| src/routes/+layout.svelte | Adds auto-hiding header on mobile with scroll detection, fixes manifest/favicon paths, initializes health checks |
| src/lib/components/AudioPlayer.svelte | Implements collapsible mini-player mode for mobile viewports with compact/expanded states |
| src/lib/components/SearchInterface.svelte | Refactors click event handling to prevent navigation when clicking artist/album links |
| src/routes/+page.svelte | Changes track selection behavior to insert into queue instead of replacing it |
| static/site.webmanifest | Removes trailing comma syntax error |
Comments suppressed due to low confidence (1)
src/lib/components/AudioPlayer.svelte:1402
- Malformed HTML: Closing
</p>tag without a corresponding opening tag. The artist link and quality information starting at line 1383 should be wrapped in a<p>tag. Add<p class="text-xs text-gray-500">before the<a>tag on line 1383 to properly open this paragraph element, or remove the closing</p>tag on line 1402 if these elements should not be in a paragraph.
<a
href={`/artist/${$playerStore.currentTrack.artist.id}`}
class="truncate text-sm text-gray-400 hover:text-blue-400 hover:underline inline-block"
data-sveltekit-preload-data
>
{formatArtists($playerStore.currentTrack.artists)}
</a>
<span class="mx-1" aria-hidden="true">•</span>
<span>{formatQualityLabel(currentPlaybackQuality ?? undefined)}</span>
{#if currentPlaybackQuality && $playerStore.currentTrack.audioQuality && currentPlaybackQuality !== $playerStore.currentTrack.audioQuality}
<span class="mx-1 text-gray-600" aria-hidden="true">•</span>
<span class="text-gray-500">
({formatQualityLabel($playerStore.currentTrack.audioQuality)} available)
</span>
{/if}
{#if sampleRateLabel}
<span class="mx-1 text-gray-600" aria-hidden="true">•</span>
<span>{sampleRateLabel}</span>
{/if}
</p>
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| // Only handle same-origin requests | ||
| if (url.origin !== self.location.origin) { | ||
| if (url.origin !== sw.location.origin && !url.pathname.startsWith('/api/proxy')) { |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logic error: The condition url.origin !== sw.location.origin && !url.pathname.startsWith('/api/proxy') is incorrect for cross-origin requests. This checks if the pathname starts with '/api/proxy' on the request URL's origin, but for cross-origin requests, you should be checking the path on the service worker's origin. A proxied request would have a URL like https://yoursite.com/api/proxy?url=..., so the check should be url.origin !== sw.location.origin (without the pathname check), or if you want to handle proxy URLs specially, check the origin first: if (url.origin === sw.location.origin || url.pathname.startsWith('/api/proxy')).
| if (url.origin !== sw.location.origin && !url.pathname.startsWith('/api/proxy')) { | |
| if (!(url.origin === sw.location.origin || url.pathname.startsWith('/api/proxy'))) { |
| const cache = await caches.open(cacheName); | ||
| const cachedResponse = await cache.match(request); | ||
| // Fallback to offline page if both network and cache fail | ||
| return cachedResponse || (await cache.match('/offline.html'))!; |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-null assertion without guarantee: Using the non-null assertion operator ! on await cache.match('/offline.html') assumes the offline page is always in the cache. However, if caching the assets fails during installation, this could return undefined and cause a runtime error. Consider handling the case where the offline page might not be cached: return cachedResponse || (await cache.match('/offline.html')) || new Response('Offline', { status: 503 }).
| return cachedResponse || (await cache.match('/offline.html'))!; | |
| return cachedResponse || (await cache.match('/offline.html')) || new Response('Offline', { status: 503 }); |
| document.removeEventListener('click', handleDocumentClick); | ||
| window.removeEventListener('scroll', handleScroll); | ||
| mediaQuery.removeEventListener('change', updateIsMobile); | ||
| unsubscribe(); |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Memory leak: The subscription unsubPerf created on line 414 is not cleaned up in the return function. Add unsubPerf() to the cleanup function to prevent memory leaks.
| unsubscribe(); | |
| unsubscribe(); | |
| unsubPerf(); |
| if (browser) { | ||
| store.subscribe((state) => { | ||
| saveState(state); | ||
| }); | ||
| } |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Potential infinite loop: The store subscription on lines 78-80 will trigger saveState on every state change. However, since the subscription is set up during store creation (before the store is fully initialized), this could cause issues. More importantly, if there's ever a scenario where loading state triggers a state update that triggers a save, this could cause performance issues. Consider debouncing the saveState calls or using a flag to prevent saving during initialization.
| } | ||
| // Make sure the player height is updated when the view changes | ||
| $effect(() => { |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Over-eager effect trigger: The $effect on lines 97-99 will run whenever any reactive dependency changes, which could be frequently. Since the comment says it should update when the view changes, it should specifically track isExpanded: $effect(() => { isExpanded; notifyContainerHeight(); }) or use a more explicit effect that only runs when isExpanded changes. This will prevent unnecessary calls to notifyContainerHeight().
| $effect(() => { | |
| $effect(() => { | |
| isExpanded; |
| > | ||
| {$playerStore.currentTrack.album.title} | ||
| {formatArtists($playerStore.currentTrack.artists)} | ||
| </a> |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Removed album information: The expanded player view previously displayed the album title with a link to the album page (visible in other components like SearchInterface and queue items). This information has been removed in the refactored player, reducing the metadata available to users. Consider adding back the album link, similar to how it appears in the queue panel: <a href={/album/${$playerStore.currentTrack.album.id}} class="hover:text-blue-400 hover:underline" data-sveltekit-preload-data>{$playerStore.currentTrack.album.title}</a>.
| </a> | |
| </a> | |
| {#if $playerStore.currentTrack.album} | |
| <span class="mx-1" aria-hidden="true">•</span> | |
| <a | |
| href={`/album/${$playerStore.currentTrack.album.id}`} | |
| class="truncate text-sm text-gray-400 hover:text-blue-400 hover:underline inline-block" | |
| data-sveltekit-preload-data | |
| > | |
| {$playerStore.currentTrack.album.title} | |
| </a> | |
| {/if} |
| <a | ||
| href={`/artist/${queuedTrack.artist.id}`} | ||
| class="truncate text-xs text-gray-400 hover:text-blue-400 hover:underline inline-block" | ||
| data-sveltekit-preload-data | ||
| > | ||
| {formatArtists(queuedTrack.artists)} | ||
| </a> |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing event propagation handling: The artist link on line 1613 will trigger playFromQueue(index) when clicked because it's inside a clickable container. The onclick={(e) => e.stopPropagation()} handler was removed from the link, but no alternative solution was implemented. Consider using event delegation in the parent's onclick handler to check if an anchor was clicked: onclick={(event) => { if (!(event.target as HTMLElement).closest('a')) { playFromQueue(index); } }}.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
Harsh, isn't it... I'll have to wait until I have some spare time so I can properly make sure that these aren't errors until I can merge. The tabs and spaces stuff I'll have to change to spaces myself. Hang tight! |
Hi, Thanks for the awesome project. I have made a few changes hope they will be useful
Player State Persistence: The player's state is no longer wiped out on navigation, The current queue, active track, and playback progress are now saved to Local Storage. This prevents the user from losing their listening session upon a page refresh.
Hybrid Caching Strategy: Implements a sophisticated dual-cache system in the service worker. The core application shell is stored in a versioned cache that updates with new deployments, while a separate, persistent cache is used for data (like audio streams and API responses). This ensures media is not re-downloaded unnecessarily after an update
Reliable API Routing: Integrates the API health-checking system directly into the fetch logic. Requests are now intelligently routed to healthy, responsive endpoints, ensuring the application remains functional even if some API servers are down
Compact Audio Player: The audio player now collapses into a space-saving compact mode on smaller viewports, preventing it from taking up excessive screen real estate
Auto-Hiding Header on Mobile: To maximize content visibility, the main header now gracefully slides out of view when scrolling down and reappears when scrolling up on smaller screen devices
Fixes: Corrects the URLs for the
faviconandsite.webmanifestfiles to ensure they load correctly and the PWA is properly configured