| 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 |
| } |
|
|
| |
| |
| |
| |
| |
| 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 |
| } |
|
|
| |
| |
| |
| |
| 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) |
| } |
| |
| 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) { |
| |
| |
| room.currentTrack = { ...room.currentTrack, ...metadata } |
| } |
| roomRepo.persist(roomId) |
| return updated |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| 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': |
| |
| 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] |
| } |
|
|
| case 'shuffle': { |
| if (room.queue.length === 1) return room.queue[0] |
| |
| 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 |
|
|
| |
| 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 |
| } |
|
|