File size: 5,479 Bytes
00a912e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | 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
}
|