- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换 - 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片 - 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转 - 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出 - 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志 - 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票 - 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏 - 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
210 行
5.0 KiB
Plaintext
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 = '';
|
|
}
|
|
}
|