/** * OmniCard AI (CRMmp) - 移动微官网双语发音语音条与多媒体播放器业务驱动层 * 遵循 uts 强类型规范与 AGENTS.md 约束,杜绝 any,无 emoji */ export type AudioPlayerState = { isPlaying: boolean; currentDuration: number; currentTime: number; waveformActive: boolean; currentUrl: string; errorMessage: string; }; export type AudioErrorEvent = { errMsg?: string; errCode?: number; }; export type InnerAudioPlayerLike = { src: string; duration?: number; currentTime?: number; paused?: boolean; play: () => void; pause?: () => void; stop: () => void; destroy: () => void; onPlay: (cb: () => void) => void; onEnded: (cb: () => void) => void; onError: (cb: (err: AudioErrorEvent) => void) => void; onTimeUpdate?: (cb: () => void) => void; }; type UniAudioRuntime = { createInnerAudioContext?: () => InnerAudioPlayerLike; }; let singletonAudioContext: InnerAudioPlayerLike | null = null; let activePlayerState: AudioPlayerState | null = null; /** * 创建发音播放器初始状态 */ export function createAudioPlayerState(): AudioPlayerState { return { isPlaying: false, currentDuration: 0, currentTime: 0, waveformActive: false, currentUrl: '', errorMessage: '', }; } /** * 获取或初始化单例音频实例 */ function getOrCreateAudioContext(state: AudioPlayerState): InnerAudioPlayerLike | null { activePlayerState = state; if (singletonAudioContext != null) { return singletonAudioContext; } const runtime = uni as unknown as UniAudioRuntime; if (typeof runtime.createInnerAudioContext !== 'function') { state.errorMessage = '当前环境不支持音频播放'; return null; } const ctx = runtime.createInnerAudioContext(); ctx.onPlay(() => { if (activePlayerState != null) { activePlayerState!.isPlaying = true; activePlayerState!.waveformActive = true; activePlayerState!.errorMessage = ''; } }); ctx.onEnded(() => { if (activePlayerState != null) { activePlayerState!.isPlaying = false; activePlayerState!.waveformActive = false; activePlayerState!.currentTime = 0; } }); ctx.onError((err: AudioErrorEvent) => { if (activePlayerState != null) { activePlayerState!.isPlaying = false; activePlayerState!.waveformActive = false; activePlayerState!.errorMessage = err.errMsg != null ? err.errMsg! : '音频播放失败'; } }); if (typeof ctx.onTimeUpdate === 'function') { ctx.onTimeUpdate(() => { if (activePlayerState != null) { if (typeof ctx.currentTime === 'number') { activePlayerState!.currentTime = ctx.currentTime!; } if (typeof ctx.duration === 'number') { activePlayerState!.currentDuration = ctx.duration!; } } }); } singletonAudioContext = ctx; return singletonAudioContext; } /** * 播放指定音频链接 */ export function playAudio(state: AudioPlayerState, audioUrl: string): boolean { if (audioUrl == null) { state.errorMessage = '音频链接不能为空'; return false; } const trimmed = audioUrl.trim(); if (trimmed === '') { state.errorMessage = '音频链接不能为空'; return false; } const ctx = getOrCreateAudioContext(state); if (ctx == null) { return false; } try { // 若当前正在播放不同音频,先停止当前音频 if (state.isPlaying && state.currentUrl !== trimmed) { try { ctx.stop(); } catch (_e) {} } ctx.src = trimmed; ctx.play(); state.currentUrl = trimmed; state.isPlaying = true; state.waveformActive = true; state.errorMessage = ''; return true; } catch (err) { state.isPlaying = false; state.waveformActive = false; const msg = err instanceof Error ? err.message : '播放启动失败'; state.errorMessage = msg; return false; } } /** * 停止当前播放 */ export function stopAudio(state: AudioPlayerState): void { if (singletonAudioContext != null) { try { singletonAudioContext!.stop(); } catch (_e) {} } state.isPlaying = false; state.waveformActive = false; } /** * 切换播放/停止 */ export function toggleAudio(state: AudioPlayerState, audioUrl?: string): boolean { const targetUrl = audioUrl != null ? audioUrl.trim() : state.currentUrl; if (targetUrl === '') { return false; } if (state.isPlaying && (state.currentUrl === targetUrl || audioUrl == null)) { stopAudio(state); return false; } return playAudio(state, targetUrl); } /** * 单例音频实例销毁释放,杜绝内存泄漏 */ export function releaseAudioPlayer(state?: AudioPlayerState): void { if (singletonAudioContext != null) { try { singletonAudioContext!.stop(); } catch (_e) {} try { singletonAudioContext!.destroy(); } catch (_e) {} singletonAudioContext = null; } activePlayerState = null; if (state != null) { state.isPlaying = false; state.waveformActive = false; state.currentTime = 0; state.currentDuration = 0; state.currentUrl = ''; } }