Dropped ytdl, YT audio now fetched with Android Client. Began work on YT Premium support
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import { queue } from '$lib/stores'
|
||||
// import { FastAverageColor } from 'fast-average-color'
|
||||
import Slider from '$lib/components/util/slider.svelte'
|
||||
import Loader from '$lib/components/util/loader.svelte'
|
||||
|
||||
$: currentlyPlaying = $queue.current
|
||||
|
||||
@@ -16,6 +17,10 @@
|
||||
let volume: number,
|
||||
muted = false
|
||||
|
||||
const maxVolume = 0.5
|
||||
|
||||
let waiting: boolean
|
||||
|
||||
$: muted ? (volume = 0) : (volume = Number(localStorage.getItem('volume')))
|
||||
$: if (volume && !muted) localStorage.setItem('volume', volume.toString())
|
||||
|
||||
@@ -45,7 +50,7 @@
|
||||
if (storedVolume) {
|
||||
volume = Number(storedVolume)
|
||||
} else {
|
||||
localStorage.setItem('volume', '0.5')
|
||||
localStorage.setItem('volume', (maxVolume / 2).toString())
|
||||
volume = 0.5
|
||||
}
|
||||
|
||||
@@ -76,6 +81,14 @@
|
||||
$: if (!seeking && expandedCurrentTimeTimestamp) expandedCurrentTimeTimestamp.innerText = formatTime(currentTime)
|
||||
$: if (!seeking && expandedProgressBar) expandedProgressBar.$set({ value: currentTime })
|
||||
$: if (!seeking && expandedDurationTimestamp) expandedDurationTimestamp.innerText = formatTime(duration)
|
||||
|
||||
let slidingText: HTMLElement
|
||||
let slidingTextWidth: number, slidingTextWrapperWidth: number
|
||||
let scrollDirection: 1 | -1 = 1
|
||||
$: scrollDistance = slidingTextWidth - slidingTextWrapperWidth
|
||||
$: if (slidingText && scrollDistance > 0) slidingText.style.animationDuration = `${scrollDistance / 50}s`
|
||||
|
||||
let audioElement: HTMLAudioElement
|
||||
</script>
|
||||
|
||||
{#if currentlyPlaying}
|
||||
@@ -90,7 +103,7 @@
|
||||
</div>
|
||||
<section class="flex flex-col justify-center gap-1">
|
||||
<div class="line-clamp-2 text-sm">{currentlyPlaying.name}</div>
|
||||
<div class="text-xs">{currentlyPlaying.artists?.map((artist) => artist.name).join(', ') || currentlyPlaying.uploader?.name}</div>
|
||||
<div class="text-xs">{currentlyPlaying.artists?.map((artist) => artist.name).join(', ') ?? currentlyPlaying.uploader?.name}</div>
|
||||
</section>
|
||||
</section>
|
||||
<section class="flex min-w-max flex-col items-center justify-center gap-1">
|
||||
@@ -101,8 +114,12 @@
|
||||
<button class="aspect-square h-8" on:click={() => $queue.previous()}>
|
||||
<i class="fa-solid fa-backward-step" />
|
||||
</button>
|
||||
<button on:click={() => (paused = !paused)} class="grid aspect-square h-8 place-items-center rounded-full bg-white">
|
||||
<i class="fa-solid {paused ? 'fa-play' : 'fa-pause'} text-black" />
|
||||
<button on:click={() => (paused = !paused)} class="relative grid aspect-square h-8 place-items-center rounded-full bg-white text-black">
|
||||
{#if waiting}
|
||||
<Loader size={1} />
|
||||
{:else}
|
||||
<i class="fa-solid {paused ? 'fa-play' : 'fa-pause'}" />
|
||||
{/if}
|
||||
</button>
|
||||
<button class="aspect-square h-8" on:click={() => $queue.next()}>
|
||||
<i class="fa-solid fa-forward-step" />
|
||||
@@ -133,10 +150,10 @@
|
||||
<section class="flex items-center justify-end gap-2 pr-2 text-lg">
|
||||
<div id="volume-slider" class="flex h-10 flex-row-reverse items-center gap-2">
|
||||
<button on:click={() => (muted = !muted)} class="aspect-square h-8">
|
||||
<i class="fa-solid {volume > 0.5 ? 'fa-volume-high' : volume > 0 ? 'fa-volume-low' : 'fa-volume-xmark'} w-full text-center" />
|
||||
<i class="fa-solid {volume > maxVolume / 2 ? 'fa-volume-high' : volume > 0 ? 'fa-volume-low' : 'fa-volume-xmark'} w-full text-center" />
|
||||
</button>
|
||||
<div id="slider-wrapper" class="w-24 transition-all duration-500">
|
||||
<Slider bind:value={volume} max={1} />
|
||||
<Slider bind:value={volume} max={maxVolume} />
|
||||
</div>
|
||||
</div>
|
||||
<button class="aspect-square h-8" on:click={() => (expanded = true)}>
|
||||
@@ -148,30 +165,32 @@
|
||||
</section>
|
||||
</main>
|
||||
{:else}
|
||||
<main in:fade={{ delay: 500 }} out:fade={{ duration: 75 }} class="expanded-player h-full" style="--currentlyPlayingImage: url(/api/remoteImage?url={currentlyPlaying.thumbnailUrl});">
|
||||
<main in:fade={{ delay: 500 }} out:fade={{ duration: 75 }} class="expanded-player relative h-full" style="--currentlyPlayingImage: url(/api/remoteImage?url={currentlyPlaying.thumbnailUrl});">
|
||||
<img class="absolute -z-10 h-full w-full object-cover object-center blur-xl brightness-[25%]" src="/api/remoteImage?url={currentlyPlaying.thumbnailUrl}" alt="" />
|
||||
<section class="song-queue-wrapper h-full px-24 py-20">
|
||||
<section class="relative">
|
||||
{#key currentlyPlaying}
|
||||
<img transition:fade={{ duration: 300 }} class="absolute h-full max-w-full object-contain py-8" src="/api/remoteImage?url={currentlyPlaying.thumbnailUrl}" alt="" />
|
||||
{/key}
|
||||
</section>
|
||||
<section class="flex flex-col gap-2">
|
||||
<div class="ml-2 text-2xl">Up next</div>
|
||||
{#each $queue.list as item, index}
|
||||
<section class="no-scrollbar flex max-h-full flex-col gap-3 overflow-y-scroll">
|
||||
<strong class="ml-2 text-2xl">UP NEXT</strong>
|
||||
{#each $queue.list as item}
|
||||
{@const isCurrent = item === currentlyPlaying}
|
||||
<button
|
||||
on:click={() => {
|
||||
if (!isCurrent) $queue.current = item
|
||||
}}
|
||||
class="queue-item w-full items-center gap-3 rounded-xl p-3 {isCurrent ? 'bg-[rgba(64,_64,_64,_0.5)]' : 'bg-[rgba(10,_10,_10,_0.5)]'}"
|
||||
class="queue-item h-20 w-full shrink-0 items-center gap-3 overflow-clip rounded-lg bg-neutral-900 {isCurrent
|
||||
? 'pointer-events-none border-[1px] border-neutral-300'
|
||||
: 'hover:bg-neutral-800'}"
|
||||
>
|
||||
<div class="justify-self-center">{index + 1}</div>
|
||||
<img class="justify-self-center" src="/api/remoteImage?url={item.thumbnailUrl}" alt="" draggable="false" />
|
||||
<div class="h-20 w-20 bg-cover bg-center" style="background-image: url('/api/remoteImage?url={item.thumbnailUrl}');" />
|
||||
<div class="justify-items-left text-left">
|
||||
<div class="line-clamp-2">{item.name}</div>
|
||||
<div class="mt-[.15rem] text-neutral-500">{currentlyPlaying.artists?.map((artist) => artist.name).join(', ') || currentlyPlaying.uploader?.name}</div>
|
||||
<div class="line-clamp-1">{item.name}</div>
|
||||
<div class="mt-[.15rem] line-clamp-1 text-neutral-400">{item.artists?.map((artist) => artist.name).join(', ') ?? item.uploader?.name}</div>
|
||||
</div>
|
||||
<span class="text-right">{formatTime(item.duration)}</span>
|
||||
<span class="mr-4 text-right">{formatTime(item.duration)}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</section>
|
||||
@@ -194,10 +213,42 @@
|
||||
<span bind:this={expandedDurationTimestamp} class="text-left" />
|
||||
</div>
|
||||
<div class="expanded-controls">
|
||||
<div>
|
||||
<div class="mb-2 line-clamp-2 text-3xl">{currentlyPlaying.name}</div>
|
||||
<div class="line-clamp-1 text-lg">
|
||||
{currentlyPlaying.artists?.map((artist) => artist.name).join(', ') || currentlyPlaying.uploader?.name}{currentlyPlaying.album ? ` - ${currentlyPlaying.album.name}` : ''}
|
||||
<div class="flex flex-col gap-2 overflow-hidden">
|
||||
<div bind:clientWidth={slidingTextWrapperWidth} class="relative h-9 w-full">
|
||||
<strong
|
||||
bind:this={slidingText}
|
||||
bind:clientWidth={slidingTextWidth}
|
||||
on:animationend={() => (scrollDirection *= -1)}
|
||||
class="{scrollDistance > 0 ? (scrollDirection > 0 ? 'scrollLeft' : 'scrollRight') : ''} scrollingText absolute whitespace-nowrap text-3xl">{currentlyPlaying.name}</strong
|
||||
>
|
||||
</div>
|
||||
<div class="line-clamp-1 flex flex-nowrap" style="font-size: 0;">
|
||||
{#if 'artists' in currentlyPlaying && currentlyPlaying.artists && currentlyPlaying.artists.length > 0}
|
||||
{#each currentlyPlaying.artists as artist, index}
|
||||
<a
|
||||
on:click={() => (expanded = false)}
|
||||
class="line-clamp-1 flex-shrink-0 text-lg hover:underline focus:underline"
|
||||
href="/details/artist?id={artist.id}&connection={currentlyPlaying.connection.id}">{artist.name}</a
|
||||
>
|
||||
{#if index < currentlyPlaying.artists.length - 1}
|
||||
<span class="mr-1 text-lg">,</span>
|
||||
{/if}
|
||||
{/each}
|
||||
{:else if 'uploader' in currentlyPlaying && currentlyPlaying.uploader}
|
||||
<a
|
||||
on:click={() => (expanded = false)}
|
||||
class="line-clamp-1 flex-shrink-0 text-lg hover:underline focus:underline"
|
||||
href="/details/user?id={currentlyPlaying.uploader.id}&connection={currentlyPlaying.connection.id}">{currentlyPlaying.uploader.name}</a
|
||||
>
|
||||
{/if}
|
||||
{#if currentlyPlaying.album}
|
||||
<span class="mx-1.5 text-lg">-</span>
|
||||
<a
|
||||
on:click={() => (expanded = false)}
|
||||
class="line-clamp-1 flex-shrink-0 text-lg hover:underline focus:underline"
|
||||
href="/details/album?id={currentlyPlaying.album.id}&connection={currentlyPlaying.connection.id}">{currentlyPlaying.album.name}</a
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full items-center justify-center gap-2 text-2xl">
|
||||
@@ -207,8 +258,12 @@
|
||||
<button class="aspect-square h-16" on:click={() => $queue.previous()}>
|
||||
<i class="fa-solid fa-backward-step" />
|
||||
</button>
|
||||
<button on:click={() => (paused = !paused)} class="grid aspect-square h-16 place-items-center rounded-full bg-white">
|
||||
<i class="fa-solid {paused ? 'fa-play' : 'fa-pause'} text-black" />
|
||||
<button on:click={() => (paused = !paused)} class="relative grid aspect-square h-16 place-items-center rounded-full bg-white text-black">
|
||||
{#if waiting}
|
||||
<Loader size={2.5} />
|
||||
{:else}
|
||||
<i class="fa-solid {paused ? 'fa-play' : 'fa-pause'}" />
|
||||
{/if}
|
||||
</button>
|
||||
<button class="aspect-square h-16" on:click={() => $queue.next()}>
|
||||
<i class="fa-solid fa-forward-step" />
|
||||
@@ -220,10 +275,10 @@
|
||||
<section class="flex items-center justify-end gap-2 text-xl">
|
||||
<div id="volume-slider" class="flex h-10 flex-row-reverse items-center gap-2">
|
||||
<button on:click={() => (muted = !muted)} class="aspect-square h-8">
|
||||
<i class="fa-solid {volume > 0.5 ? 'fa-volume-high' : volume > 0 ? 'fa-volume-low' : 'fa-volume-xmark'} w-full text-center" />
|
||||
<i class="fa-solid {volume > maxVolume / 2 ? 'fa-volume-high' : volume > 0 ? 'fa-volume-low' : 'fa-volume-xmark'} w-full text-center" />
|
||||
</button>
|
||||
<div id="slider-wrapper" class="w-24 transition-all duration-500">
|
||||
<Slider bind:value={volume} max={1} />
|
||||
<Slider bind:value={volume} max={maxVolume} />
|
||||
</div>
|
||||
</div>
|
||||
<button class="aspect-square h-8" on:click={() => (expanded = false)}>
|
||||
@@ -237,18 +292,27 @@
|
||||
</section>
|
||||
</main>
|
||||
{/if}
|
||||
<audio autoplay bind:paused bind:volume bind:currentTime bind:duration on:ended={() => $queue.next()} src="/api/audio?connection={currentlyPlaying.connection.id}&id={currentlyPlaying.id}" />
|
||||
<audio
|
||||
bind:this={audioElement}
|
||||
autoplay
|
||||
bind:paused
|
||||
bind:volume
|
||||
bind:currentTime
|
||||
bind:duration
|
||||
on:canplay={() => (waiting = false)}
|
||||
on:loadstart={() => (waiting = true)}
|
||||
on:waiting={() => (waiting = true)}
|
||||
on:ended={() => $queue.next()}
|
||||
on:error={() => setTimeout(() => audioElement.load(), 5000)}
|
||||
src="/api/audio?connection={currentlyPlaying.connection.id}&id={currentlyPlaying.id}"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.expanded-player {
|
||||
display: grid;
|
||||
grid-template-rows: auto 12rem;
|
||||
/* background: linear-gradient(to left, rgba(16, 16, 16, 0.9), rgb(16, 16, 16)), var(--currentlyPlayingImage); */
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: cover !important;
|
||||
background-position: center !important;
|
||||
grid-template-rows: calc(100% - 12rem) 12rem;
|
||||
}
|
||||
.song-queue-wrapper {
|
||||
display: grid;
|
||||
@@ -257,10 +321,7 @@
|
||||
}
|
||||
.queue-item {
|
||||
display: grid;
|
||||
grid-template-columns: 1rem 50px auto min-content;
|
||||
}
|
||||
.queue-item:hover {
|
||||
background-color: rgba(64, 64, 64, 0.5);
|
||||
grid-template-columns: 5rem auto min-content;
|
||||
}
|
||||
.progress-bar-expanded {
|
||||
display: grid;
|
||||
@@ -270,6 +331,44 @@
|
||||
}
|
||||
.expanded-controls {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min-content 1fr;
|
||||
gap: 1rem;
|
||||
grid-template-columns: 1fr min-content 1fr !important;
|
||||
}
|
||||
|
||||
.scrollingText {
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: both;
|
||||
animation-delay: 10s;
|
||||
}
|
||||
.scrollingText:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
.scrollLeft {
|
||||
animation-name: scrollLeft;
|
||||
}
|
||||
.scrollRight {
|
||||
animation-name: scrollRight;
|
||||
}
|
||||
|
||||
@keyframes scrollLeft {
|
||||
0% {
|
||||
left: 0%;
|
||||
transform: translateX(0%);
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scrollRight {
|
||||
0% {
|
||||
left: 100%;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
left: 0%;
|
||||
transform: translateX(0%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user