Download packages/server/src/services/queueService.ts from QSLY/ghp: direct link, hf CLI and curl.
- Browser
- Download file 5.48 kB
-
https://huggingface.co/spaces/QSLY/ghp/resolve/main/packages/server/src/services/queueService.ts
- Command line
-
hf download hf://spaces/QSLY/ghp/packages/server/src/services/queueService.ts
-
curl -L -o queueService.ts https://huggingface.co/spaces/QSLY/ghp/resolve/main/packages/server/src/services/queueService.ts
5.48 kB
| import type { PlayMode, Track } from '@music-together/shared' | |
| import { LIMITS } from '@music-together/shared' | |
| import { roomRepo } from '../repositories/roomRepository.js' | |
| export function addTrack(roomId: string, track: Track): boolean { | |
| const room = roomRepo.get(roomId) | |
| if (!room) return false | |
| if (room.queue.length >= LIMITS.QUEUE_MAX_SIZE) return false | |
| room.queue.push(track) | |
| roomRepo.persist(roomId) | |
| return true | |
| } | |
| /** | |
| * Add multiple tracks at once (from playlist import). | |
| * Respects QUEUE_MAX_SIZE — adds as many as fit. | |
| * @returns Number of tracks actually added. | |
| */ | |
| export function addBatchTracks(roomId: string, tracks: Track[]): number { | |
| const room = roomRepo.get(roomId) | |
| if (!room) return 0 | |
| const available = LIMITS.QUEUE_MAX_SIZE - room.queue.length | |
| if (available <= 0) return 0 | |
| const toAdd = tracks.slice(0, available) | |
| room.queue.push(...toAdd) | |
| roomRepo.persist(roomId) | |
| return toAdd.length | |
| } | |
| /** | |
| * Insert a new track right after the current playing track. | |
| * If current track is missing from the queue (edge race), insert to the front. | |
| */ | |
| export function insertAfterCurrent(roomId: string, track: Track): boolean { | |
| const room = roomRepo.get(roomId) | |
| if (!room) return false | |
| if (room.queue.length >= LIMITS.QUEUE_MAX_SIZE) return false | |
| const currentId = room.currentTrack?.id | |
| const currentIndex = currentId ? room.queue.findIndex((t) => t.id === currentId) : -1 | |
| const insertIndex = currentIndex >= 0 ? currentIndex + 1 : 0 | |
| room.queue.splice(insertIndex, 0, track) | |
| roomRepo.persist(roomId) | |
| return true | |
| } | |
| export function removeTrack(roomId: string, trackId: string): void { | |
| const room = roomRepo.get(roomId) | |
| if (room) { | |
| room.queue = room.queue.filter((t) => t.id !== trackId) | |
| roomRepo.persist(roomId) | |
| } | |
| } | |
| export function clearQueue(roomId: string): void { | |
| const room = roomRepo.get(roomId) | |
| if (room) { | |
| room.queue = [] | |
| roomRepo.persist(roomId) | |
| } | |
| } | |
| export function reorderTracks(roomId: string, trackIds: string[]): void { | |
| const room = roomRepo.get(roomId) | |
| if (!room) return | |
| if (!Array.isArray(trackIds) || trackIds.length === 0) return | |
| const trackMap = new Map(room.queue.map((t) => [t.id, t])) | |
| const seen = new Set<string>() | |
| const reordered: Track[] = [] | |
| for (const id of trackIds) { | |
| if (seen.has(id)) continue | |
| seen.add(id) | |
| const track = trackMap.get(id) | |
| if (track) reordered.push(track) | |
| } | |
| // Append any tracks that were NOT included in trackIds (prevent accidental drops) | |
| for (const track of room.queue) { | |
| if (!seen.has(track.id)) { | |
| reordered.push(track) | |
| } | |
| } | |
| room.queue = reordered | |
| roomRepo.persist(roomId) | |
| } | |
| export function updateBilibiliMetadata( | |
| roomId: string, | |
| trackId: string, | |
| metadata: Pick<Track, 'metadataSource' | 'lyricId' | 'picId' | 'cover'>, | |
| ): Track | null { | |
| const room = roomRepo.get(roomId) | |
| if (!room) return null | |
| const index = room.queue.findIndex((track) => track.id === trackId) | |
| const existing = room.queue[index] | |
| if (!existing || existing.source !== 'bilibili') return null | |
| const updated = { ...existing, ...metadata } | |
| room.queue[index] = updated | |
| if (room.currentTrack?.id === trackId) { | |
| // The current track includes the resolved, short-lived stream URL while | |
| // the queue entry normally does not. Preserve it when refreshing metadata. | |
| room.currentTrack = { ...room.currentTrack, ...metadata } | |
| } | |
| roomRepo.persist(roomId) | |
| return updated | |
| } | |
| /** | |
| * Get the next track based on the play mode. | |
| * | |
| * - sequential: next in queue; null at end | |
| * - loop-all: next in queue; wraps to first at end | |
| * - loop-one: returns the current track itself | |
| * - shuffle: random track from queue (excludes current; returns self if queue has 1 item) | |
| */ | |
| export function getNextTrack(roomId: string, playMode?: PlayMode): Track | null { | |
| const room = roomRepo.get(roomId) | |
| if (!room || room.queue.length === 0) return null | |
| const mode = playMode ?? room.playMode ?? 'sequential' | |
| const currentIndex = room.currentTrack ? room.queue.findIndex((t) => t.id === room.currentTrack!.id) : -1 | |
| switch (mode) { | |
| case 'loop-one': | |
| // Replay the current track; fall back to next if current is gone | |
| if (room.currentTrack && currentIndex >= 0) return room.currentTrack | |
| return room.queue[0] ?? null | |
| case 'loop-all': { | |
| const nextIndex = currentIndex + 1 | |
| return nextIndex < room.queue.length ? room.queue[nextIndex] : room.queue[0] // wrap to first | |
| } | |
| case 'shuffle': { | |
| if (room.queue.length === 1) return room.queue[0] | |
| // Pick a random track excluding the current one | |
| const candidates = room.queue.filter((_, i) => i !== currentIndex) | |
| return candidates[Math.floor(Math.random() * candidates.length)] ?? room.queue[0] | |
| } | |
| case 'sequential': | |
| default: { | |
| const nextIndex = currentIndex + 1 | |
| return nextIndex < room.queue.length ? room.queue[nextIndex] : null | |
| } | |
| } | |
| } | |
| export function getPreviousTrack(roomId: string): Track | null { | |
| const room = roomRepo.get(roomId) | |
| if (!room || room.queue.length === 0) return null | |
| const currentIndex = room.currentTrack ? room.queue.findIndex((t) => t.id === room.currentTrack!.id) : -1 | |
| // For loop-all, wrap to last track when at the beginning | |
| if (room.playMode === 'loop-all' && currentIndex <= 0) { | |
| return room.queue[room.queue.length - 1] | |
| } | |
| const prevIndex = currentIndex - 1 | |
| return prevIndex >= 0 ? room.queue[prevIndex] : null | |
| } | |