refactor(audio): [PKG-AUDIO-03] separate track ownership and native lifecycle

This commit is contained in:
Harvey Zhao committed 2026-09-12 02:22:13 +08:00
1 parent 803e87252e
commit 9c07ea830c
20 files changed
+12654 -250

No files matched your search

@@ -1,101 +1,95 @@
import { createAudioTrack } from './track'
export default function artplayerPluginAudioTrack(option) {
return (art) => {
let { url, offset = 0, sync = 0.3 } = option
const audio = new Audio()
audio.preload = 'auto'
if (url) {
audio.src = url
}
const track = createAudioTrack(option)
const { audio } = track
const subscriptions = []
let active = true
function syncAudio() {
if (!art.video || !url)
return
const videoTime = art.currentTime
const targetTime = videoTime + offset
if (Math.abs(audio.currentTime - targetTime) > sync) {
audio.currentTime = targetTime
}
if (art.video)
track.sync(art.currentTime)
}
art.on('play', () => {
if (!url)
return
syncAudio()
audio.play().catch((err) => {
console.warn(err)
})
})
art.on('pause', () => {
audio.pause()
})
art.on('seek', () => {
syncAudio()
})
art.on('video:timeupdate', () => {
if (art.playing) {
syncAudio()
function listen(event, callback) {
const listener = () => {
if (active)
callback()
}
})
subscriptions.push([event, listener])
art.on(event, listener)
}
art.on('video:ratechange', () => {
audio.playbackRate = art.video.playbackRate
})
function destroy() {
if (!active)
return
active = false
const failures = []
for (const [event, listener] of subscriptions.splice(0)) {
try {
art.off(event, listener)
}
catch (error) { failures.push(error) }
}
try {
track.destroy()
}
catch (error) { failures.push(error) }
if (failures.length)
throw failures[0]
}
art.on('video:volumechange', () => {
try {
listen('play', () => {
syncAudio()
track.play()
})
for (const event of ['pause', 'video:pause', 'video:ended', 'video:waiting', 'video:emptied', 'video:seeking'])
listen(event, track.pause)
listen('seek', syncAudio)
listen('video:seeked', () => {
syncAudio()
if (art.playing)
track.play()
})
listen('video:timeupdate', () => {
if (art.playing)
syncAudio()
})
listen('video:ratechange', () => {
audio.playbackRate = art.video.playbackRate
})
listen('video:volumechange', () => {
audio.volume = art.volume
audio.muted = art.muted
})
listen('video:playing', () => {
if (art.playing) {
syncAudio()
track.play()
}
})
listen('destroy', destroy)
audio.volume = art.volume
audio.muted = art.muted
})
art.on('video:waiting', () => {
audio.pause()
})
art.on('video:playing', () => {
if (url && art.playing) {
audio.play().catch((err) => {
console.warn(err)
})
}
})
art.on('destroy', () => {
audio.pause()
audio.src = ''
audio.load()
})
function update(newOption) {
if (newOption.url && newOption.url !== url) {
url = newOption.url
audio.src = url
if (art.playing) {
audio.play().catch(err => console.warn(err))
}
}
if (newOption.offset !== undefined) {
offset = newOption.offset
}
if (newOption.sync !== undefined) {
sync = newOption.sync
}
audio.playbackRate = art.video?.playbackRate || 1
}
catch (error) {
try {
destroy()
}
catch {}
throw error
}
audio.volume = art.volume
audio.muted = art.muted
audio.playbackRate = art.video?.playbackRate || 1
return {
name: 'artplayerPluginAudioTrack',
audio,
update,
update(newOption) {
if (active)
track.update(newOption, art.playing)
},
}
}
}
@@ -0,0 +1,75 @@
export function createAudioTrack(option) {
let { url, offset = 0, sync = 0.3 } = option
const audio = new Audio()
let closed = false
function play() {
if (closed || !url)
return
audio.play().catch((error) => {
if (!closed)
console.warn(error)
})
}
function pause() {
if (!closed)
audio.pause()
}
function destroy() {
if (closed)
return
closed = true
const failures = []
for (const release of [() => audio.pause(), () => audio.removeAttribute('src'), () => audio.load()]) {
try {
release()
}
catch (error) { failures.push(error) }
}
if (failures.length)
throw failures[0]
}
try {
audio.preload = 'auto'
if (url)
audio.src = url
}
catch (error) {
try {
destroy()
}
catch {}
throw error
}
return {
audio,
play,
pause,
destroy,
sync(time) {
if (closed || !url)
return
const target = time + offset
if (Math.abs(audio.currentTime - target) > sync)
audio.currentTime = target
},
update(newOption, playing) {
if (closed)
return
if (newOption.url && newOption.url !== url) {
url = newOption.url
audio.src = url
if (playing)
play()
}
if (newOption.offset !== undefined)
offset = newOption.offset
if (newOption.sync !== undefined)
sync = newOption.sync
},
}
}