修复讲解全局音频播放器交互

This commit is contained in:
lyf
2026-07-08 21:22:27 +08:00
parent 89923ab861
commit 9cdb2c08b2
12 changed files with 1715 additions and 324 deletions

View File

@@ -1,6 +1,12 @@
import { computed, ref } from 'vue'
import type { AudioItem } from '@/components/audio/AudioPlayer.vue'
import type { AudioLanguage } from '@/repositories/AudioPlayInfoRepository'
import type {
AudioDisplayMode,
AudioItem
} from '@/components/audio/AudioPlayer.vue'
import {
audioPlayInfoRepository,
type AudioLanguage
} from '@/repositories/AudioPlayInfoRepository'
import type { AudioPlayTargetType } from '@/domain/museum'
export interface GlobalAudioSource {
@@ -18,6 +24,7 @@ export interface GlobalAudioPlayOptions {
source?: GlobalAudioSource
retryOnError?: GlobalAudioRetryHandler
retryAttempt?: boolean
displayMode?: AudioDisplayMode
}
const currentAudio = ref<AudioItem | null>(null)
@@ -28,7 +35,11 @@ const loading = ref(false)
const error = ref('')
const currentTime = ref(0)
const duration = ref(0)
const displayMode = ref<AudioDisplayMode>('mini')
const pendingLanguage = ref<AudioLanguage | ''>('')
const activeHostId = ref('')
const lastClosedSource = ref<GlobalAudioSource | null>(null)
const closeVersion = ref(0)
let audioElement: HTMLAudioElement | null = null
let retryOnError: GlobalAudioRetryHandler | null = null
@@ -110,6 +121,8 @@ const resetState = () => {
error.value = ''
currentTime.value = 0
duration.value = 0
displayMode.value = 'mini'
pendingLanguage.value = ''
retryOnError = null
retrying = false
retryUsed = false
@@ -134,9 +147,52 @@ const stopAudioElement = () => {
resetStopping()
}
const stopPlayback = () => {
stopAudioElement()
playing.value = false
loading.value = false
pendingLanguage.value = ''
currentTime.value = 0
duration.value = currentAudio.value?.duration || 0
}
const updateDetailRouteLanguage = (route: string | undefined, lang: AudioLanguage) => {
if (!route) return route
const [path, rawQuery = ''] = route.split('?')
const params = new URLSearchParams(rawQuery)
params.set('lang', lang)
return `${path}?${params.toString()}`
}
const toSwitchedAudioItem = (
playInfo: Awaited<ReturnType<typeof audioPlayInfoRepository.getPlayInfo>>,
lang: AudioLanguage
): AudioItem => ({
id: `play-${playInfo.audioId || `${playInfo.targetType}-${playInfo.targetId}-${lang}`}`,
name: currentAudio.value?.name || currentSource.value?.title || playInfo.title || '讲解音频',
audioUrl: playInfo.playUrl || '',
image: currentAudio.value?.image,
duration: typeof playInfo.duration === 'number' ? playInfo.duration : currentAudio.value?.duration,
language: lang,
supportedLanguages: currentAudio.value?.supportedLanguages
})
const play = async (audio: AudioItem, options: GlobalAudioPlayOptions = {}) => {
if (!audio.audioUrl) {
handleError('当前语言暂无语音讲解。')
const preservedMode = options.displayMode || displayMode.value
stopAudioElement()
currentAudio.value = audio
currentSource.value = options.source || currentSource.value
retryOnError = options.retryOnError || retryOnError
visible.value = true
displayMode.value = preservedMode
playing.value = false
loading.value = false
error.value = '当前语言暂无语音讲解'
currentTime.value = 0
duration.value = 0
showToast(error.value)
return false
}
@@ -155,6 +211,9 @@ const play = async (audio: AudioItem, options: GlobalAudioPlayOptions = {}) => {
currentSource.value = options.source || currentSource.value
retryOnError = options.retryOnError || retryOnError
visible.value = true
if (options.displayMode) {
displayMode.value = options.displayMode
}
loading.value = true
error.value = ''
@@ -194,17 +253,122 @@ const resume = () => {
if (!currentAudio.value) return Promise.resolve(false)
return play(currentAudio.value, {
source: currentSource.value || undefined,
retryOnError: retryOnError || undefined
retryOnError: retryOnError || undefined,
displayMode: displayMode.value
})
}
const switchLanguage = async (lang: AudioLanguage) => {
if (pendingLanguage.value) {
return false
}
const source = currentSource.value
if (!source?.targetType || !source.targetId) {
error.value = '当前讲解暂不支持语言切换'
showToast(error.value)
return false
}
if (currentAudio.value?.language === lang && source.lang === lang) {
return true
}
const preservedMode = displayMode.value
pendingLanguage.value = lang
loading.value = true
error.value = ''
const nextSource: GlobalAudioSource = {
...source,
lang,
detailRoute: updateDetailRouteLanguage(source.detailRoute, lang)
}
try {
const playInfo = await audioPlayInfoRepository.getPlayInfo({
targetType: source.targetType,
targetId: source.targetId,
lang,
refresh: true
})
if (!playInfo.playable || !playInfo.playUrl) {
stopAudioElement()
currentSource.value = nextSource
currentAudio.value = currentAudio.value
? {
...currentAudio.value,
id: `unavailable-${source.targetType}-${source.targetId}-${lang}`,
audioUrl: '',
duration: 0,
language: lang
}
: null
visible.value = Boolean(currentAudio.value)
displayMode.value = preservedMode
playing.value = false
loading.value = false
currentTime.value = 0
duration.value = 0
error.value = '当前语言暂无语音讲解'
showToast(error.value)
return false
}
currentSource.value = nextSource
return await play(toSwitchedAudioItem(playInfo, lang), {
source: nextSource,
retryOnError: retryOnError || undefined,
displayMode: preservedMode
})
} catch (err) {
console.warn('讲解音频语言切换失败:', err)
stopAudioElement()
currentSource.value = nextSource
if (currentAudio.value) {
currentAudio.value = {
...currentAudio.value,
id: `unavailable-${source.targetType}-${source.targetId}-${lang}`,
audioUrl: '',
duration: 0,
language: lang
}
visible.value = true
}
displayMode.value = preservedMode
playing.value = false
loading.value = false
currentTime.value = 0
duration.value = 0
error.value = '当前语言暂无语音讲解'
showToast(error.value)
return false
} finally {
pendingLanguage.value = ''
}
}
const stop = () => {
stopPlayback()
}
const close = () => {
lastClosedSource.value = currentSource.value ? { ...currentSource.value } : null
closeVersion.value += 1
stopAudioElement()
resetState()
}
const close = () => {
stop()
const setDisplayMode = (mode: AudioDisplayMode) => {
displayMode.value = mode
if (currentAudio.value) {
visible.value = true
}
}
const collapse = (mode: AudioDisplayMode = 'mini') => {
setDisplayMode(mode)
}
function handleEnded() {
@@ -230,13 +394,14 @@ async function handleError(message: string) {
return play(retryAudio, {
source: currentSource.value || undefined,
retryOnError: retryOnError || undefined,
retryAttempt: true
retryAttempt: true,
displayMode: displayMode.value
})
}
}
showToast(message || '讲解音频播放失败')
stop()
stopPlayback()
return false
}
@@ -287,13 +452,20 @@ export const useGlobalAudioPlayer = () => ({
error,
currentTime,
duration,
displayMode,
pendingLanguage,
activeHostId,
lastClosedSource,
closeVersion,
hasAudio: computed(() => Boolean(currentAudio.value)),
play,
pause,
resume,
stop,
close,
setDisplayMode,
collapse,
switchLanguage,
handleEnded,
handleError,
seekToPercent,