文件
OmniCard/mpuniapp/pages/portal/useAudioPlayer.uts
wikiboss eec259a3a7 feat: 完成全系统30个功能点开发与E2E全链路验收 (8轮调度32个子代理闭环)
- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换
- 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片
- 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转
- 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出
- 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志
- 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票
- 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏
- 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
2026-09-20 23:47:47 +08:00

210 行
5.0 KiB
Plaintext

/**
* 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 = '';
}
}