接入H5讲解后端数据闭环
This commit is contained in:
@@ -18,12 +18,16 @@ import {
|
||||
audioPlayInfoRepository,
|
||||
audioReasonToText,
|
||||
type AudioPlayInfo,
|
||||
type AudioPlayInfoRepository
|
||||
type AudioPlayInfoRepository,
|
||||
type AudioTextInfo
|
||||
} from '@/repositories/AudioPlayInfoRepository'
|
||||
import {
|
||||
publishedExhibitAudioRepository,
|
||||
type PublishedExhibitAudioRepository
|
||||
} from '@/repositories/PublishedExhibitAudioRepository'
|
||||
import {
|
||||
dataSourceConfig
|
||||
} from '@/config/dataSource'
|
||||
|
||||
export interface ExplainAudioSelection {
|
||||
exhibit: MuseumExhibit
|
||||
@@ -34,6 +38,11 @@ export interface ExplainAudioSelection {
|
||||
playInfo?: AudioPlayInfo
|
||||
}
|
||||
|
||||
export interface ExhibitDetailAudioOptions {
|
||||
enrichAudio?: boolean
|
||||
includeText?: boolean
|
||||
}
|
||||
|
||||
export class ExplainUseCase {
|
||||
constructor(
|
||||
private readonly explain: ExplainRepository = explainRepository,
|
||||
@@ -56,6 +65,100 @@ export class ExplainUseCase {
|
||||
}
|
||||
}
|
||||
|
||||
private resolveAudioTarget(exhibit: MuseumExhibit, track?: ExplainTrack | null) {
|
||||
const targetType: AudioPlayTargetType = track?.playTargetType
|
||||
|| exhibit.playTargetType
|
||||
|| 'ITEM'
|
||||
const targetId = track?.playTargetId
|
||||
|| exhibit.playTargetId
|
||||
|| exhibit.id
|
||||
|
||||
return { targetType, targetId }
|
||||
}
|
||||
|
||||
private applyPlayInfo(
|
||||
exhibit: MuseumExhibit,
|
||||
playInfo: AudioPlayInfo,
|
||||
options: { preserveStaticFallback?: boolean } = {}
|
||||
): MuseumExhibit {
|
||||
const apiPlayable = playInfo.playable === true && Boolean(playInfo.playUrl)
|
||||
const canUseStaticAudioFallback = !playInfo.reason || playInfo.reason === 'TARGET_NOT_FOUND'
|
||||
const fallbackAudioUrl = options.preserveStaticFallback && canUseStaticAudioFallback && exhibit.audioUrl
|
||||
? exhibit.audioUrl
|
||||
: undefined
|
||||
const nextAudioUrl = apiPlayable ? playInfo.playUrl || undefined : fallbackAudioUrl
|
||||
const nextAudioDuration = typeof playInfo.duration === 'number'
|
||||
? playInfo.duration
|
||||
: exhibit.audioDuration
|
||||
|
||||
return {
|
||||
...exhibit,
|
||||
guideTitle: playInfo.title || exhibit.guideTitle,
|
||||
audioUrl: nextAudioUrl,
|
||||
audioDuration: nextAudioDuration,
|
||||
audioLanguage: playInfo.lang || exhibit.audioLanguage,
|
||||
audioSubtitleUrl: playInfo.subtitleUrl || exhibit.audioSubtitleUrl,
|
||||
audioHasText: playInfo.hasText === true || exhibit.audioHasText,
|
||||
audioNarrationTier: playInfo.narrationTier || exhibit.audioNarrationTier,
|
||||
audioUnavailableReason: apiPlayable ? undefined : audioReasonToText(playInfo.reason),
|
||||
audioAvailable: apiPlayable || Boolean(fallbackAudioUrl)
|
||||
}
|
||||
}
|
||||
|
||||
private applyTextInfo(exhibit: MuseumExhibit, textInfo: AudioTextInfo | null): MuseumExhibit {
|
||||
if (!textInfo?.available || !textInfo.text) return exhibit
|
||||
|
||||
return {
|
||||
...exhibit,
|
||||
guideTitle: textInfo.title || exhibit.guideTitle,
|
||||
guideText: textInfo.text,
|
||||
audioText: textInfo.text,
|
||||
audioTextLength: textInfo.textLength || exhibit.audioTextLength,
|
||||
audioTextHash: textInfo.textHash || exhibit.audioTextHash,
|
||||
audioLanguage: textInfo.lang || exhibit.audioLanguage,
|
||||
audioNarrationTier: textInfo.narrationTier || exhibit.audioNarrationTier,
|
||||
audioHasText: true
|
||||
}
|
||||
}
|
||||
|
||||
private async enrichExhibitAudio(
|
||||
exhibit: MuseumExhibit,
|
||||
options: {
|
||||
includeText?: boolean
|
||||
preserveStaticFallback?: boolean
|
||||
track?: ExplainTrack | null
|
||||
} = {}
|
||||
): Promise<MuseumExhibit> {
|
||||
const track = options.track ?? await this.explain.getTrackByExhibitId(exhibit.id).catch(() => null)
|
||||
const baseExhibit = this.applyTrackAudioState(exhibit, track)
|
||||
const { targetType, targetId } = this.resolveAudioTarget(baseExhibit, track)
|
||||
|
||||
try {
|
||||
const playInfo = await this.audioPlayInfo.getPlayInfo({ targetType, targetId })
|
||||
let nextExhibit = this.applyPlayInfo(baseExhibit, playInfo, {
|
||||
preserveStaticFallback: options.preserveStaticFallback
|
||||
})
|
||||
|
||||
if (options.includeText && playInfo.hasText) {
|
||||
try {
|
||||
const textInfo = await this.audioPlayInfo.getTextInfo({
|
||||
targetType,
|
||||
targetId,
|
||||
lang: playInfo.lang
|
||||
})
|
||||
nextExhibit = this.applyTextInfo(nextExhibit, textInfo)
|
||||
} catch (error) {
|
||||
console.warn('讲解词正文加载失败,将使用静态讲解文稿:', error)
|
||||
}
|
||||
}
|
||||
|
||||
return nextExhibit
|
||||
} catch (error) {
|
||||
console.warn('讲解播放信息加载失败,将使用静态讲解数据:', error)
|
||||
return baseExhibit
|
||||
}
|
||||
}
|
||||
|
||||
private async getTrackByExhibitIdMap() {
|
||||
const tracks = await this.explain.listTracks()
|
||||
const trackByExhibitId = new Map<string, ExplainTrack>()
|
||||
@@ -97,19 +200,35 @@ export class ExplainUseCase {
|
||||
}
|
||||
}
|
||||
|
||||
async getExhibitById(id: string) {
|
||||
async getExhibitById(id: string, options: ExhibitDetailAudioOptions = {}) {
|
||||
const exhibit = await this.explain.getExhibitById(id)
|
||||
if (!exhibit) return null
|
||||
|
||||
try {
|
||||
const track = await this.explain.getTrackByExhibitId(id)
|
||||
return this.applyTrackAudioState(exhibit, track)
|
||||
const baseExhibit = this.applyTrackAudioState(exhibit, track)
|
||||
if (!options.enrichAudio) {
|
||||
return baseExhibit
|
||||
}
|
||||
|
||||
return this.enrichExhibitAudio(exhibit, {
|
||||
includeText: options.includeText,
|
||||
preserveStaticFallback: true,
|
||||
track
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn('讲解详情音频状态加载失败,将使用展项原始音频状态:', error)
|
||||
return exhibit
|
||||
}
|
||||
}
|
||||
|
||||
async enrichExhibitDetailAudio(id: string) {
|
||||
return this.getExhibitById(id, {
|
||||
enrichAudio: true,
|
||||
includeText: true
|
||||
})
|
||||
}
|
||||
|
||||
listHalls(): Promise<MuseumHall[]> {
|
||||
return this.explain.listHalls()
|
||||
}
|
||||
@@ -130,15 +249,20 @@ export class ExplainUseCase {
|
||||
async selectAudioForExhibit(exhibitId: string): Promise<ExplainAudioSelection | null> {
|
||||
const track = await this.explain.getTrackByExhibitId(exhibitId)
|
||||
const explainExhibits = await this.explain.listExplainExhibits()
|
||||
const exhibit = explainExhibits.find((item) => item.id === exhibitId)
|
||||
|| await this.explain.getExhibitById(exhibitId)
|
||||
const summaryExhibit = explainExhibits.find((item) => item.id === exhibitId)
|
||||
const detailExhibit = await this.explain.getExhibitById(exhibitId).catch(() => null)
|
||||
const exhibit = detailExhibit || summaryExhibit
|
||||
if (!exhibit) return null
|
||||
|
||||
const fallbackMedia = track?.mediaId
|
||||
? await this.media.getMediaById(track.mediaId)
|
||||
const fallbackMedia = await (track?.mediaId
|
||||
? this.media.getMediaById(track.mediaId)
|
||||
: exhibit.guideContentId
|
||||
? await this.media.getMediaById(`media-${exhibit.guideContentId}`)
|
||||
: null
|
||||
? this.media.getMediaById(`media-${exhibit.guideContentId}`)
|
||||
: Promise.resolve(null)
|
||||
).catch((error) => {
|
||||
console.warn('静态讲解媒体读取失败,将继续尝试播放接口:', error)
|
||||
return null
|
||||
})
|
||||
const targetType: AudioPlayTargetType = track?.playTargetType
|
||||
|| exhibit.playTargetType
|
||||
|| 'ITEM'
|
||||
@@ -203,6 +327,15 @@ export class ExplainUseCase {
|
||||
}
|
||||
}
|
||||
|
||||
const directMediaSelection = toDirectMediaSelection({
|
||||
playable: false,
|
||||
targetType,
|
||||
targetId,
|
||||
lang: dataSourceConfig.audioLanguage as AudioPlayInfo['lang'],
|
||||
reason: 'DIRECT_MEDIA_FALLBACK'
|
||||
})
|
||||
if (directMediaSelection) return directMediaSelection
|
||||
|
||||
try {
|
||||
const initialPlayInfo = await this.audioPlayInfo.getPlayInfo({ targetType, targetId })
|
||||
|
||||
@@ -222,9 +355,6 @@ export class ExplainUseCase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const directMediaSelection = toDirectMediaSelection(initialPlayInfo)
|
||||
if (directMediaSelection) return directMediaSelection
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -237,13 +367,23 @@ export class ExplainUseCase {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('语音播放解析失败:', error)
|
||||
const serviceErrorPlayInfo: AudioPlayInfo = {
|
||||
playable: false,
|
||||
targetType,
|
||||
targetId,
|
||||
lang: dataSourceConfig.audioLanguage as AudioPlayInfo['lang'],
|
||||
reason: 'SERVICE_ERROR'
|
||||
}
|
||||
const directMediaSelection = toDirectMediaSelection(serviceErrorPlayInfo)
|
||||
if (directMediaSelection) return directMediaSelection
|
||||
|
||||
return {
|
||||
exhibit,
|
||||
track,
|
||||
media: null,
|
||||
playable: false,
|
||||
unavailableMessage: '语音服务暂不可用,请稍后重试'
|
||||
playInfo: serviceErrorPlayInfo,
|
||||
unavailableMessage: '语音服务暂不可用,当前提供图文讲解'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user