mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-18 19:01:20 -08:00
89 lines
2.3 KiB
TypeScript
89 lines
2.3 KiB
TypeScript
import type { WrappedAudioBuffer } from 'mediabunny'
|
|
import type { AudioClock } from './audio-clock'
|
|
import { bufferTiming } from './audio-clock'
|
|
|
|
interface QueueHost extends AudioClock {
|
|
gainNode: GainNode | null
|
|
queuedNodes: Set<AudioBufferSourceNode>
|
|
latestScheduledEndTime: number
|
|
}
|
|
|
|
export default class NodeQueue {
|
|
constructor(private host: QueueHost) {}
|
|
|
|
private release(node: AudioBufferSourceNode, stop: boolean): void {
|
|
this.host.queuedNodes.delete(node)
|
|
node.onended = null
|
|
let failed = false
|
|
let failure: unknown
|
|
try {
|
|
if (stop)
|
|
node.stop()
|
|
}
|
|
catch (error) {
|
|
failed = true
|
|
failure = error
|
|
}
|
|
try {
|
|
node.disconnect()
|
|
}
|
|
catch (error) {
|
|
if (!failed) {
|
|
failed = true
|
|
failure = error
|
|
}
|
|
}
|
|
if (failed)
|
|
throw failure
|
|
}
|
|
|
|
stop(): void {
|
|
const failures: unknown[] = []
|
|
for (const node of [...this.host.queuedNodes]) {
|
|
try {
|
|
this.release(node, true)
|
|
}
|
|
catch (error) { failures.push(error) }
|
|
}
|
|
if (failures.length)
|
|
throw failures[0]
|
|
}
|
|
|
|
schedule({ buffer, timestamp }: WrappedAudioBuffer): void {
|
|
const context = this.host.audioContext
|
|
const gain = this.host.gainNode
|
|
if (!context || !gain)
|
|
return
|
|
const timing = bufferTiming(this.host, timestamp, buffer.duration, context.currentTime)
|
|
if (timing.offset !== null && timing.offset >= buffer.duration)
|
|
return
|
|
const node = context.createBufferSource()
|
|
this.host.queuedNodes.add(node)
|
|
try {
|
|
node.buffer = buffer
|
|
node.connect(gain)
|
|
node.playbackRate.value = this.host.playbackRate
|
|
node.onended = () => {
|
|
if (!this.host.queuedNodes.has(node))
|
|
return
|
|
try {
|
|
this.release(node, false)
|
|
}
|
|
catch (error) { console.warn('MediaBunny ended node cleanup:', error) }
|
|
}
|
|
if (timing.offset === null)
|
|
node.start(timing.startAt)
|
|
else
|
|
node.start(timing.startAt, timing.offset)
|
|
this.host.latestScheduledEndTime = Math.max(this.host.latestScheduledEndTime, timing.endMediaTime)
|
|
}
|
|
catch (error) {
|
|
try {
|
|
this.release(node, true)
|
|
}
|
|
catch (failure) { console.warn('MediaBunny audio node cleanup:', failure) }
|
|
throw error
|
|
}
|
|
}
|
|
}
|