New queue implementation && YouTubeMusic.getConnectionInfo() will garuntteed return db data
This commit is contained in:
4
src/app.d.ts
vendored
4
src/app.d.ts
vendored
@@ -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 {
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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}')"
|
||||||
|
|||||||
Reference in New Issue
Block a user