Updated Jellyfin media item parsers, added startIndex && limit params to YTMusic getPlaylistItems() method
This commit is contained in:
24
src/routes/(app)/details/playlist/+page.svelte
Normal file
24
src/routes/(app)/details/playlist/+page.svelte
Normal file
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import Loader from '$lib/components/util/loader.svelte'
|
||||
import type { PageData } from './$types'
|
||||
|
||||
export let data: PageData
|
||||
</script>
|
||||
|
||||
<main>
|
||||
{#await data.playlistDetails}
|
||||
<Loader />
|
||||
{:then [playlist, items]}
|
||||
<section class="flex gap-8">
|
||||
<img class="h-60" src="/api/remoteImage?url={playlist.thumbnailUrl}" alt="{playlist.name} cover art" />
|
||||
<div>
|
||||
<div class="text-4xl">{playlist.name}</div>
|
||||
</div>
|
||||
</section>
|
||||
{#each items as item}
|
||||
<div>{item.name}</div>
|
||||
{/each}
|
||||
{:catch}
|
||||
<div>Failed to fetch playlist</div>
|
||||
{/await}
|
||||
</main>
|
||||
22
src/routes/(app)/details/playlist/+page.ts
Normal file
22
src/routes/(app)/details/playlist/+page.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { PageLoad } from './$types'
|
||||
|
||||
export const load: PageLoad = async ({ fetch, url }) => {
|
||||
const connectionId = url.searchParams.get('connection')
|
||||
const id = url.searchParams.get('id')
|
||||
|
||||
async function getPlaylist() {
|
||||
const playlistResponse = (await fetch(`/api/connections/${connectionId}/playlist?id=${id}`, {
|
||||
credentials: 'include',
|
||||
}).then((response) => response.json())) as { playlist: Playlist }
|
||||
return playlistResponse.playlist
|
||||
}
|
||||
|
||||
async function getPlaylistItems() {
|
||||
const itemsResponse = (await fetch(`/api/connections/${connectionId}/playlist/${id}/items`, {
|
||||
credentials: 'include',
|
||||
}).then((response) => response.json())) as { items: Song[] }
|
||||
return itemsResponse.items
|
||||
}
|
||||
|
||||
return { playlistDetails: Promise.all([getPlaylist(), getPlaylistItems()]) }
|
||||
}
|
||||
@@ -1,66 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
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
|
||||
import MediaCard from '$lib/components/media/mediaCard.svelte'
|
||||
|
||||
export let data: PageServerData
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
seconds = Math.floor(seconds)
|
||||
const hours = Math.floor(seconds / 3600)
|
||||
seconds = seconds - hours * 3600
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
seconds = seconds - minutes * 60
|
||||
return hours > 0 ? `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}` : `${minutes}:${seconds.toString().padStart(2, '0')}`
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if data.searchResults}
|
||||
{#await data.searchResults then searchResults}
|
||||
<section class="flex w-full flex-col items-center gap-2">
|
||||
<section class="flex w-full flex-wrap gap-6">
|
||||
{#each searchResults as searchResult}
|
||||
<div class="flex h-20 w-full max-w-screen-md gap-4 bg-black p-2">
|
||||
<button
|
||||
id="searchResult"
|
||||
on:click={() => {
|
||||
if (searchResult.type === 'song') {
|
||||
queueRef.current = searchResult
|
||||
} else {
|
||||
goto(`/details/${searchResult.type}?id=${searchResult.id}&connection=${searchResult.connection.id}`)
|
||||
}
|
||||
}}
|
||||
class="grid aspect-square h-full place-items-center bg-cover bg-center bg-no-repeat"
|
||||
style="--thumbnail: url('/api/remoteImage?url={'thumbnailUrl' in searchResult ? searchResult.thumbnailUrl : searchResult.profilePicture}')"
|
||||
>
|
||||
<i class="fa-solid fa-play opacity-0" />
|
||||
</button>
|
||||
<div>
|
||||
<div>{searchResult.name}{searchResult.type === 'song' && searchResult.album?.name ? ` - ${searchResult.album.name}` : ''}</div>
|
||||
{#if 'artists' in searchResult && searchResult.artists}
|
||||
<div>{searchResult.artists === 'Various Artists' ? searchResult.artists : searchResult.artists.map((artist) => artist.name).join(', ')}</div>
|
||||
{:else if 'createdBy' in searchResult && searchResult.createdBy}
|
||||
<div>{searchResult.createdBy?.name}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if 'duration' in searchResult && searchResult.duration}
|
||||
<span class="justify-self-end">{formatTime(searchResult.duration)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<MediaCard mediaItem={searchResult} />
|
||||
{/each}
|
||||
</section>
|
||||
{/await}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
#searchResult {
|
||||
background-image: var(--thumbnail);
|
||||
}
|
||||
#searchResult:hover {
|
||||
background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), var(--thumbnail);
|
||||
}
|
||||
#searchResult:hover > i {
|
||||
opacity: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,7 +16,7 @@ export const GET: RequestHandler = async ({ url, request }) => {
|
||||
// * Withing the .getAudioStream() method of connections, a TypeError should be thrown if the request was invalid (e.g. non-existent id)
|
||||
// * A standard Error should be thrown if the fetch to the service's server failed or the request returned invalid data
|
||||
.catch((error: TypeError | Error) => {
|
||||
if (error instanceof TypeError) return new Response('Malformed Request', { status: 400 })
|
||||
if (error instanceof TypeError) return new Response('Bad Request', { status: 400 })
|
||||
return new Response('Failed to fetch valid audio stream', { status: 502 })
|
||||
})
|
||||
|
||||
|
||||
@@ -9,8 +9,13 @@ export const GET: RequestHandler = async ({ params, url }) => {
|
||||
const playlistId = url.searchParams.get('id')
|
||||
if (!playlistId) return new Response(`Missing id search parameter`, { status: 400 })
|
||||
|
||||
const playlist = await connection.getPlaylist(playlistId).catch(() => undefined)
|
||||
if (!playlist) return new Response(`Failed to fetch playlist with id: ${playlistId}`, { status: 400 })
|
||||
const response = await connection
|
||||
.getPlaylistItems(playlistId)
|
||||
.then((playlist) => Response.json({ playlist }))
|
||||
.catch((error: TypeError | Error) => {
|
||||
if (error instanceof TypeError) return new Response('Bad Request', { status: 400 })
|
||||
return new Response('Failed to fetch playlist items', { status: 502 })
|
||||
})
|
||||
|
||||
return Response.json({ playlist })
|
||||
return response
|
||||
}
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit'
|
||||
import { Connections } from '$lib/server/connections'
|
||||
|
||||
export const GET: RequestHandler = async ({ params }) => {
|
||||
export const GET: RequestHandler = async ({ params, url }) => {
|
||||
const { connectionId, playlistId } = params
|
||||
const connection = Connections.getConnection(connectionId!)
|
||||
if (!connection) return new Response('Invalid connection id', { status: 400 })
|
||||
|
||||
const items = await connection.getPlaylistItems(playlistId!).catch((reason) => console.error(reason))
|
||||
if (!items) return new Response(`Failed to fetch playlist with id: ${playlistId!}`, { status: 400 })
|
||||
const startIndexString = url.searchParams.get('startIndex')
|
||||
const limitString = url.searchParams.get('limit')
|
||||
|
||||
return Response.json({ items })
|
||||
const numberStartIndex = Number(startIndexString)
|
||||
const numberLimit = Number(limitString)
|
||||
|
||||
const startIndex = Number.isInteger(numberStartIndex) && numberStartIndex > 0 ? numberStartIndex : undefined
|
||||
const limit = Number.isInteger(numberLimit) && numberLimit > 0 ? numberLimit : undefined
|
||||
|
||||
const response = await connection
|
||||
.getPlaylistItems(playlistId!, startIndex, limit)
|
||||
.then((items) => Response.json({ items }))
|
||||
.catch((error: TypeError | Error) => {
|
||||
if (error instanceof TypeError) return new Response('Bad Request', { status: 400 })
|
||||
return new Response('Failed to fetch playlist items', { status: 502 })
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user