From 28e456950791cd0d5cedb88bac0437afa00da9c1 Mon Sep 17 00:00:00 2001 From: Eclypsed Date: Mon, 22 Apr 2024 14:18:42 -0400 Subject: [PATCH] Added fullscreen mode to miniplayer --- src/lib/components/media/mediaCard.svelte | 4 +- src/lib/components/media/mediaPlayer.svelte | 288 +++++++++++++++----- src/lib/components/util/searchBar.svelte | 3 + src/lib/components/util/slider.svelte | 3 +- src/lib/server/youtube-music.ts | 171 +++++++----- src/lib/stores.ts | 74 +++-- src/routes/(app)/+layout.svelte | 6 +- src/routes/(app)/search/+page.svelte | 6 +- src/routes/api/audio/+server.ts | 2 +- 9 files changed, 391 insertions(+), 166 deletions(-) diff --git a/src/lib/components/media/mediaCard.svelte b/src/lib/components/media/mediaCard.svelte index 7bcb4a6..9cf70a8 100644 --- a/src/lib/components/media/mediaCard.svelte +++ b/src/lib/components/media/mediaCard.svelte @@ -5,6 +5,8 @@ import { goto } from '$app/navigation' import { queue } from '$lib/stores' + let queueRef = $queue // This nonsense is to prevent an bug that causes svelte to throw an error when setting a property of the queue directly + let image: HTMLImageElement, captionText: HTMLDivElement @@ -28,7 +30,7 @@ { - if (mediaItem.type === 'song') $queue.enqueue(mediaItem) + if (mediaItem.type === 'song') queueRef.current = mediaItem }} > diff --git a/src/lib/components/media/mediaPlayer.svelte b/src/lib/components/media/mediaPlayer.svelte index 9db2e91..79344d9 100644 --- a/src/lib/components/media/mediaPlayer.svelte +++ b/src/lib/components/media/mediaPlayer.svelte @@ -5,7 +5,9 @@ // import { FastAverageColor } from 'fast-average-color' import Slider from '$lib/components/util/slider.svelte' - $: currentlyPlaying = $queue.current() + $: currentlyPlaying = $queue.current + + let expanded = false let paused = true, shuffle = false, @@ -14,7 +16,8 @@ let volume: number, muted = false - $: if (volume) localStorage.setItem('volume', volume.toString()) + $: muted ? (volume = 0) : (volume = Number(localStorage.getItem('volume'))) + $: if (volume && !muted) localStorage.setItem('volume', volume.toString()) const formatTime = (seconds: number): string => { seconds = Math.floor(seconds) @@ -25,6 +28,18 @@ return hours > 0 ? `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}` : `${minutes}:${seconds.toString().padStart(2, '0')}` } + $: if (currentlyPlaying) updateMediaSession(currentlyPlaying) + const updateMediaSession = (media: Song) => { + if ('mediaSession' in navigator) { + navigator.mediaSession.metadata = new MediaMetadata({ + title: media.name, + artist: media.artists?.map((artist) => artist.name).join(', ') || media.createdBy?.name, + album: media.album?.name, + artwork: [{ src: `/api/remoteImage?url=${media.thumbnail}`, sizes: '256x256', type: 'image/png' }], + }) + } + } + onMount(() => { const storedVolume = localStorage.getItem('volume') if (storedVolume) { @@ -33,6 +48,14 @@ localStorage.setItem('volume', '0.5') volume = 0.5 } + + if ('mediaSession' in navigator) { + navigator.mediaSession.setActionHandler('play', () => (paused = false)) + navigator.mediaSession.setActionHandler('pause', () => (paused = true)) + navigator.mediaSession.setActionHandler('stop', () => $queue.clear()) + navigator.mediaSession.setActionHandler('nexttrack', () => $queue.next()) + navigator.mediaSession.setActionHandler('previoustrack', () => $queue.previous()) + } }) let currentTime: number = 0 @@ -42,84 +65,211 @@ let progressBar: Slider let durationTimestamp: HTMLSpanElement + let expandedCurrentTimeTimestamp: HTMLSpanElement + let expandedProgressBar: Slider + let expandedDurationTimestamp: HTMLSpanElement + let seeking: boolean = false $: if (!seeking && currentTimeTimestamp) currentTimeTimestamp.innerText = formatTime(currentTime) $: if (!seeking && progressBar) progressBar.$set({ value: currentTime }) $: if (!seeking && durationTimestamp) durationTimestamp.innerText = formatTime(duration) + $: if (!seeking && expandedCurrentTimeTimestamp) expandedCurrentTimeTimestamp.innerText = formatTime(currentTime) + $: if (!seeking && expandedProgressBar) expandedProgressBar.$set({ value: currentTime }) + $: if (!seeking && expandedDurationTimestamp) expandedDurationTimestamp.innerText = formatTime(duration) {#if currentlyPlaying} -
-
-
- {#key currentlyPlaying} -
- {/key} -
-
-
{currentlyPlaying.name}
-
{currentlyPlaying.artists?.map((artist) => artist.name).join(', ') || currentlyPlaying.createdBy?.name}
-
-
-
-
- - - - - -
-
- -
- { - currentTimeTimestamp.innerText = formatTime(event.detail.value) - seeking = true - }} - on:seeked={(event) => { - currentTime = event.detail.value - seeking = false - }} - /> -
- -
-
-
-
- -
- -
-
- -
+
+ {#if !expanded} +
+
+
+ {#key currentlyPlaying} +
+ {/key} +
+
+
{currentlyPlaying.name}
+
{currentlyPlaying.artists?.map((artist) => artist.name).join(', ') || currentlyPlaying.createdBy?.name}
+
+
+
+
+ + + + + +
+
+ +
+ { + currentTimeTimestamp.innerText = formatTime(event.detail.value) + seeking = true + }} + on:seeked={(event) => { + currentTime = event.detail.value + seeking = false + }} + /> +
+ +
+
+
+
+ +
+ +
+
+ + +
+
+ {:else} +
+
+
+ {#key currentlyPlaying} + + {/key} +
+
+
Up next
+ {#each $queue.list as item, index} + {@const isCurrent = item === currentlyPlaying} + + {/each} +
+
+
+
+ + { + expandedCurrentTimeTimestamp.innerText = formatTime(event.detail.value) + seeking = true + }} + on:seeked={(event) => { + currentTime = event.detail.value + seeking = false + }} + /> + +
+
+
+
{currentlyPlaying.name}
+
+ {currentlyPlaying.artists?.map((artist) => artist.name).join(', ') || currentlyPlaying.createdBy?.name}{currentlyPlaying.album ? ` - ${currentlyPlaying.album.name}` : ''} +
+
+
+ + + + + +
+
+
+ +
+ +
+
+ + +
+
+
+
+ {/if}
+ {/if} diff --git a/src/lib/components/util/searchBar.svelte b/src/lib/components/util/searchBar.svelte index fe74d4d..b232f4c 100644 --- a/src/lib/components/util/searchBar.svelte +++ b/src/lib/components/util/searchBar.svelte @@ -31,4 +31,7 @@ if (event.key === 'Enter') triggerSearch(searchInput.value) }} /> + diff --git a/src/lib/components/util/slider.svelte b/src/lib/components/util/slider.svelte index 859ae94..961a349 100644 --- a/src/lib/components/util/slider.svelte +++ b/src/lib/components/util/slider.svelte @@ -1,5 +1,5 @@
@@ -44,7 +46,5 @@
-
- -
+ diff --git a/src/routes/(app)/search/+page.svelte b/src/routes/(app)/search/+page.svelte index 6ae5a0f..1b2a5f0 100644 --- a/src/routes/(app)/search/+page.svelte +++ b/src/routes/(app)/search/+page.svelte @@ -2,6 +2,8 @@ import { queue } from '$lib/stores' import type { PageServerData } from './$types' + let queueRef = $queue // This nonsense is to prevent an bug that causes svelte to throw an error when setting a property of the queue directly + export let data: PageServerData const formatTime = (seconds: number): string => { @@ -22,7 +24,7 @@
-
{searchResult.name}
+
{searchResult.name}{searchResult.type === 'song' && searchResult.album?.name ? ` - ${searchResult.album.name}` : ''}
{#if 'artists' in searchResult && searchResult.artists}
{searchResult.artists.map((artist) => artist.name).join(', ')}
{:else if 'createdBy' in searchResult && searchResult.createdBy} diff --git a/src/routes/api/audio/+server.ts b/src/routes/api/audio/+server.ts index 1aad665..9fd6219 100644 --- a/src/routes/api/audio/+server.ts +++ b/src/routes/api/audio/+server.ts @@ -18,7 +18,7 @@ export const GET: RequestHandler = async ({ url, request }) => { console.error(`Audio stream fetch failed: ${reason}`) return null }) - if (!stream || !stream.body) continue + if (!stream || !stream.ok) continue return stream }