修复讲解全局音频播放器交互
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user