New queue implementation && YouTubeMusic.getConnectionInfo() will garuntteed return db data

This commit is contained in:
Eclypsed
2024-04-17 14:23:54 -04:00
parent f11fa95aea
commit 2ee60ef302
5 changed files with 47 additions and 37 deletions

4
src/app.d.ts vendored
View File

@@ -36,8 +36,8 @@ declare global {
} | { } | {
type: 'youtube-music' type: 'youtube-music'
youtubeUserId: string youtubeUserId: string
username: string username?: string
profilePicture: string profilePicture?: string
}) })
interface Connection { interface Connection {

View File

@@ -5,7 +5,7 @@
// import { FastAverageColor } from 'fast-average-color' // import { FastAverageColor } from 'fast-average-color'
import Slider from '$lib/components/util/slider.svelte' import Slider from '$lib/components/util/slider.svelte'
$: console.log(`Queue is now: ${$queue}`) $: currentlyPlaying = $queue.current()
let paused = true, let paused = true,
shuffle = false, shuffle = false,
@@ -48,8 +48,7 @@
$: if (!seeking && durationTimestamp) durationTimestamp.innerText = formatTime(duration) $: if (!seeking && durationTimestamp) durationTimestamp.innerText = formatTime(duration)
</script> </script>
{#if $queue.queue.length > 0} {#if currentlyPlaying}
{@const currentlyPlaying = $queue.getCurrent()}
<main transition:slide class="relative m-4 grid h-20 grid-cols-[minmax(auto,_20rem)_auto_minmax(auto,_20rem)] gap-4 overflow-clip rounded-xl bg-neutral-925 text-white transition-colors duration-1000"> <main transition:slide class="relative m-4 grid h-20 grid-cols-[minmax(auto,_20rem)_auto_minmax(auto,_20rem)] gap-4 overflow-clip rounded-xl bg-neutral-925 text-white transition-colors duration-1000">
<section class="flex gap-3"> <section class="flex gap-3">
<div class="relative h-full w-20 min-w-20"> <div class="relative h-full w-20 min-w-20">
@@ -112,7 +111,7 @@
<i class="fa-solid fa-xmark" /> <i class="fa-solid fa-xmark" />
</button> </button>
</section> </section>
<audio autoplay bind:paused bind:volume bind:currentTime bind:duration on:ended={() => console.log('next')} src="/api/audio?connection={currentlyPlaying.connection}&id={currentlyPlaying.id}" /> <audio autoplay bind:paused bind:volume bind:currentTime bind:duration on:ended={() => $queue.next()} src="/api/audio?connection={currentlyPlaying.connection}&id={currentlyPlaying.id}" />
</main> </main>
{/if} {/if}

View File

@@ -72,16 +72,25 @@ export class YouTubeMusic implements Connection {
public getConnectionInfo = async (): Promise<Extract<ConnectionInfo, { type: 'youtube-music' }>> => { public getConnectionInfo = async (): Promise<Extract<ConnectionInfo, { type: 'youtube-music' }>> => {
const youtube = google.youtube('v3') const youtube = google.youtube('v3')
const userChannelResponse = await youtube.channels.list({ mine: true, part: ['snippet'], access_token: await this.getAccessToken() }) const access_token = await this.getAccessToken().catch(() => {
const userChannel = userChannelResponse.data.items![0] return null
})
let username, profilePicture
if (access_token) {
const userChannelResponse = await youtube.channels.list({ mine: true, part: ['snippet'], access_token })
const userChannel = userChannelResponse?.data.items?.[0]
username = userChannel?.snippet?.title ?? undefined // ?? undefined will simply ensure that if it is null it get's converted to undefined
profilePicture = userChannel?.snippet?.thumbnails?.default?.url ?? undefined
}
return { return {
id: this.id, id: this.id,
userId: this.userId, userId: this.userId,
type: 'youtube-music', type: 'youtube-music',
youtubeUserId: this.ytUserId, youtubeUserId: this.ytUserId,
username: userChannel.snippet?.title!, username,
profilePicture: userChannel.snippet?.thumbnails?.default?.url!, profilePicture,
} }
} }
@@ -182,10 +191,12 @@ export class YouTubeMusic implements Connection {
const parseTwoRowItemRenderer = (connection: string, rowContent: InnerTube.musicTwoRowItemRenderer): Song | Album | Artist | Playlist => { const parseTwoRowItemRenderer = (connection: string, rowContent: InnerTube.musicTwoRowItemRenderer): Song | Album | Artist | Playlist => {
const name = rowContent.title.runs[0].text const name = rowContent.title.runs[0].text
const thumbnail = refineThumbnailUrl(rowContent.thumbnailRenderer.musicThumbnailRenderer.thumbnail.thumbnails[0].url)
let artists: (Song | Album)['artists'], createdBy: (Song | Playlist)['createdBy'] let artists: (Song | Album)['artists'], createdBy: (Song | Playlist)['createdBy']
for (const run of rowContent.subtitle.runs) { for (const run of rowContent.subtitle.runs) {
if (!run.navigationEndpoint) continue if (!run.navigationEndpoint) continue
const pageType = run.navigationEndpoint.browseEndpoint.browseEndpointContextSupportedConfigs.browseEndpointContextMusicConfig.pageType const pageType = run.navigationEndpoint.browseEndpoint.browseEndpointContextSupportedConfigs.browseEndpointContextMusicConfig.pageType
if (pageType === 'MUSIC_PAGE_TYPE_ARTIST') { if (pageType === 'MUSIC_PAGE_TYPE_ARTIST') {
const artist = { id: run.navigationEndpoint.browseEndpoint.browseId, name: run.text } const artist = { id: run.navigationEndpoint.browseEndpoint.browseId, name: run.text }
@@ -195,8 +206,6 @@ const parseTwoRowItemRenderer = (connection: string, rowContent: InnerTube.music
} }
} }
const thumbnail = refineThumbnailUrl(rowContent.thumbnailRenderer.musicThumbnailRenderer.thumbnail.thumbnails[0].url)
if ('watchEndpoint' in rowContent.navigationEndpoint) { if ('watchEndpoint' in rowContent.navigationEndpoint) {
const id = rowContent.navigationEndpoint.watchEndpoint.videoId const id = rowContent.navigationEndpoint.watchEndpoint.videoId
return { connection, id, name, type: 'song', artists, createdBy, thumbnail } satisfies Song return { connection, id, name, type: 'song', artists, createdBy, thumbnail } satisfies Song
@@ -222,6 +231,7 @@ const parseResponsiveListItemRenderer = (connection: string, listContent: InnerT
let artists: (Song | Album)['artists'], createdBy: (Song | Playlist)['createdBy'] let artists: (Song | Album)['artists'], createdBy: (Song | Playlist)['createdBy']
for (const run of listContent.flexColumns[1].musicResponsiveListItemFlexColumnRenderer.text.runs) { for (const run of listContent.flexColumns[1].musicResponsiveListItemFlexColumnRenderer.text.runs) {
if (!run.navigationEndpoint) continue if (!run.navigationEndpoint) continue
const pageType = run.navigationEndpoint.browseEndpoint.browseEndpointContextSupportedConfigs.browseEndpointContextMusicConfig.pageType const pageType = run.navigationEndpoint.browseEndpoint.browseEndpointContextSupportedConfigs.browseEndpointContextMusicConfig.pageType
if (pageType === 'MUSIC_PAGE_TYPE_ARTIST') { if (pageType === 'MUSIC_PAGE_TYPE_ARTIST') {
const artist = { id: run.navigationEndpoint.browseEndpoint.browseId, name: run.text } const artist = { id: run.navigationEndpoint.browseEndpoint.browseId, name: run.text }

View File

@@ -1,4 +1,4 @@
import { writable, readable, type Writable, type Readable } from 'svelte/store' import { writable, readable, readonly, type Writable, type Readable } from 'svelte/store'
import type { AlertType } from '$lib/components/util/alert.svelte' import type { AlertType } from '$lib/components/util/alert.svelte'
export const pageWidth: Writable<number> = writable() export const pageWidth: Writable<number> = writable()
@@ -9,43 +9,44 @@ const youtubeMusicBackground: string = 'https://www.gstatic.com/youtube/media/yt
export const backgroundImage: Writable<string> = writable(youtubeMusicBackground) export const backgroundImage: Writable<string> = writable(youtubeMusicBackground)
class Queue { class Queue {
public currentPos: number private currentPos: number | null
public queue: Song[] private songs: Song[]
constructor() { constructor() {
this.queue = [] this.currentPos = null
this.songs = []
}
public enqueue = (...songs: Song[]) => {
this.songs.push(...songs)
writeableQueue.set(this)
}
public next = () => {
if (this.songs.length === 0) return
if (!this.currentPos) {
this.currentPos = 0 this.currentPos = 0
} } else {
if (!(this.songs.length > this.currentPos + 1)) return
public enqueue = (...songs: Song[]): void => {
this.queue.push(...songs)
}
public getStart = (): Song | undefined => {
return this.queue[0]
}
public getCurrent = (): Song => {
return this.queue[this.currentPos]
}
public next = (): Song | undefined => {
if (this.queue.length > this.currentPos + 1) {
this.currentPos += 1 this.currentPos += 1
return this.queue[this.currentPos]
} }
return undefined writeableQueue.set(this)
} }
public previous = (): Song | undefined => { public current = () => {
if (this.currentPos > 0) { if (this.songs.length > 0) {
this.currentPos -= 1 if (!this.currentPos) this.currentPos = 0
return this.queue[this.currentPos] return this.songs[this.currentPos]
}
return null
} }
return undefined public getSongs = () => {
return this.songs
} }
} }
export const queue: Readable<Queue> = readable(new Queue()) const writeableQueue: Writable<Queue> = writable(new Queue())
export const queue: Readable<Queue> = readonly(writeableQueue)

View File

@@ -22,7 +22,7 @@
<button <button
id="searchResult" id="searchResult"
on:click={() => { on:click={() => {
if (searchResult.type === 'song') $queue.push(searchResult) if (searchResult.type === 'song') $queue.enqueue(searchResult)
}} }}
class="grid aspect-square h-full place-items-center bg-cover bg-center bg-no-repeat" class="grid aspect-square h-full place-items-center bg-cover bg-center bg-no-repeat"
style="--thumbnail: url('/api/remoteImage?url={searchResult.thumbnail}')" style="--thumbnail: url('/api/remoteImage?url={searchResult.thumbnail}')"