Files
evening_detective_frontend/src/composables/usePlaylistDurations.ts
T
2026-10-27 22:38:29 +07:00

81 lines
2.6 KiB
TypeScript

import { computed, onScopeDispose, type Ref, shallowReactive, watch } from 'vue'
import type { Playlist } from '@/api/generated/crabs/evening_detective_server'
import { getURL } from '@/api/generated/crabs/evening_detective_server/client'
import { playlistDurationLabel } from '@/utils/playlistDuration'
export function usePlaylistDurations(playlist: Ref<Playlist | undefined>) {
const durations = shallowReactive(new Map<string, number | null>())
const files = computed(() =>
JSON.stringify(
[...new Set(playlist.value?.tracks?.map((track) => track.filename ?? '') ?? [])].sort(),
),
)
const cancelLoads = new Set<() => void>()
let version = 0
function cancel() {
version++
for (const stop of cancelLoads) stop()
}
function load(filename: string): Promise<number | null | undefined> {
return new Promise((resolve) => {
const audio = new Audio()
const finish = (duration: number | null | undefined) => {
clearTimeout(timeout)
audio.onloadedmetadata = null
audio.onerror = null
audio.removeAttribute('src')
audio.load()
cancelLoads.delete(stop)
resolve(duration)
}
const stop = () => finish(undefined)
const timeout = setTimeout(() => finish(null), 15000)
cancelLoads.add(stop)
audio.preload = 'metadata'
audio.onloadedmetadata = () =>
finish(Number.isFinite(audio.duration) && audio.duration > 0 ? audio.duration : null)
audio.onerror = () => finish(null)
audio.src = `${getURL()}/api/files/${encodeURIComponent(filename)}`
})
}
watch(
files,
() => {
cancel()
const currentVersion = version
const pending = [
...new Set(playlist.value?.tracks?.map((track) => track.filename ?? '') ?? []),
].filter((filename) => !durations.has(filename))
async function worker() {
while (currentVersion === version && pending.length) {
const filename = pending.shift()!
if (!filename) {
durations.set(filename, null)
continue
}
const duration = await load(filename)
if (currentVersion !== version) return
if (duration !== undefined) durations.set(filename, duration)
}
}
for (let index = 0; index < 3; index++) void worker()
},
{ immediate: true },
)
onScopeDispose(cancel)
function groupDuration(groupID: number) {
return playlistDurationLabel(
(playlist.value?.tracks ?? [])
.filter((track) => (track.groupId ?? 0) === groupID)
.map((track) => durations.get(track.filename ?? '')),
)
}
return { groupDuration }
}