|
@@ -0,0 +1,320 @@
|
|
|
|
+export default {
|
|
|
|
+ // 音频播放器实例
|
|
|
|
+ player: null,
|
|
|
|
+
|
|
|
|
+ // 当前运行环境检测
|
|
|
|
+ isApp: null,
|
|
|
|
+
|
|
|
|
+ // 初始化方法
|
|
|
|
+ init() {
|
|
|
|
+ // 检测运行环境
|
|
|
|
+ const systemInfo = uni.getSystemInfoSync();
|
|
|
|
+ this.isApp = systemInfo.platform === 'ios' || systemInfo.platform === 'android';
|
|
|
|
+
|
|
|
|
+ // 初始化播放器
|
|
|
|
+ if (!this.player) {
|
|
|
|
+ this.player = uni.createInnerAudioContext();
|
|
|
|
+ this.player.obeyMuteSwitch = false; // iOS静音模式下也可播放
|
|
|
|
+ this.player.onError((err) => {
|
|
|
|
+ console.error('音频播放器错误:', err);
|
|
|
|
+ uni.showToast({
|
|
|
|
+ title: '播放失败,请稍后再试',
|
|
|
|
+ icon: 'none',
|
|
|
|
+ duration: 2000
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 从单词数据中提取所有音频信息(完整实现)
|
|
|
|
+ extractAudioInfo(wordData) {
|
|
|
|
+ if (!wordData || typeof wordData !== 'object') {
|
|
|
|
+ console.error('无效的单词数据');
|
|
|
|
+ return {
|
|
|
|
+ main: null,
|
|
|
|
+ syllables: [],
|
|
|
|
+ frequencies: []
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const audioInfo = {
|
|
|
|
+ main: null,
|
|
|
|
+ syllables: [],
|
|
|
|
+ frequencies: []
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 处理主音频
|
|
|
|
+ if (wordData.yinpin && typeof wordData.yinpin === 'string') {
|
|
|
|
+ audioInfo.main = {
|
|
|
|
+ id: `word_${wordData.id}_main`,
|
|
|
|
+ url: this.normalizeAudioUrl(wordData.yinpin),
|
|
|
|
+ type: 'main',
|
|
|
|
+ text: wordData.name || '未知单词',
|
|
|
|
+ lastCached: 0 // 最后缓存时间戳
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 处理音节音频
|
|
|
|
+ if (Array.isArray(wordData.yinjie)) {
|
|
|
|
+ audioInfo.syllables = wordData.yinjie
|
|
|
|
+ .filter(item => item && typeof item === 'object')
|
|
|
|
+ .map((item, index) => ({
|
|
|
|
+ id: `word_${wordData.id}_syllable_${index}`,
|
|
|
|
+ url: this.normalizeAudioUrl(item.yinpin),
|
|
|
|
+ type: 'syllable',
|
|
|
|
+ text: item.cigen || `音节${index + 1}`,
|
|
|
|
+ index,
|
|
|
|
+ lastCached: 0
|
|
|
|
+ }))
|
|
|
|
+ .filter(item => item.url);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 处理频度音频
|
|
|
|
+ if (Array.isArray(wordData.pindu)) {
|
|
|
|
+ audioInfo.frequencies = wordData.pindu
|
|
|
|
+ .filter(item => item && typeof item === 'object')
|
|
|
|
+ .map((item, index) => ({
|
|
|
|
+ id: `word_${wordData.id}_frequency_${index}`,
|
|
|
|
+ url: this.normalizeAudioUrl(item.yinpin),
|
|
|
|
+ type: 'frequency',
|
|
|
|
+ text: item.cigen || `频度${index + 1}`,
|
|
|
|
+ index,
|
|
|
|
+ lastCached: 0
|
|
|
|
+ }))
|
|
|
|
+ .filter(item => item.url);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return audioInfo;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 标准化音频URL(完整实现)
|
|
|
|
+ normalizeAudioUrl(url) {
|
|
|
|
+ if (!url || typeof url !== 'string') return null;
|
|
|
|
+
|
|
|
|
+ // 已经是完整URL
|
|
|
|
+ if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('blob:')) {
|
|
|
|
+ return url;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 处理相对路径(根据实际项目配置)
|
|
|
|
+ const baseUrl = 'https://your-audio-server.com/'; // 替换为你的音频服务器地址
|
|
|
|
+ return baseUrl + url.replace(/^\//, '');
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 缓存单词的所有音频(完整实现)
|
|
|
|
+ async cacheWordAudios(wordData) {
|
|
|
|
+ try {
|
|
|
|
+ if (!wordData || !wordData.id) {
|
|
|
|
+ throw new Error('无效的单词数据');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 提取音频信息
|
|
|
|
+ const audioInfo = this.extractAudioInfo(wordData);
|
|
|
|
+ const allAudios = [];
|
|
|
|
+
|
|
|
|
+ if (audioInfo.main) allAudios.push(audioInfo.main);
|
|
|
|
+ if (audioInfo.syllables.length) allAudios.push(...audioInfo.syllables);
|
|
|
|
+ if (audioInfo.frequencies.length) allAudios.push(...audioInfo.frequencies);
|
|
|
|
+
|
|
|
|
+ // 真实下载音频文件(仅App环境)
|
|
|
|
+ if (this.isApp) {
|
|
|
|
+ await this.downloadAudioFiles(allAudios);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 保存音频信息到缓存
|
|
|
|
+ this.saveAudioInfoToStorage(wordData.id, audioInfo);
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('缓存单词音频失败:', error);
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 下载音频文件(完整实现)
|
|
|
|
+ async downloadAudioFiles(audioList) {
|
|
|
|
+ if (!Array.isArray(audioList)) return;
|
|
|
|
+
|
|
|
|
+ const downloadTasks = audioList.map(audio => {
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
+ // 检查是否已有缓存
|
|
|
|
+ const cacheInfo = this.getAudioCacheInfo(audio.id);
|
|
|
|
+ if (cacheInfo && cacheInfo.cached) {
|
|
|
|
+ audio.lastCached = cacheInfo.timestamp;
|
|
|
|
+ return resolve();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 开始下载
|
|
|
|
+ uni.downloadFile({
|
|
|
|
+ url: audio.url,
|
|
|
|
+ success: (res) => {
|
|
|
|
+ if (res.statusCode === 200) {
|
|
|
|
+ // 保存到缓存
|
|
|
|
+ uni.setStorageSync(`audio_file_${audio.id}`, res.tempFilePath);
|
|
|
|
+ uni.setStorageSync(`audio_cache_time_${audio.id}`, Date.now());
|
|
|
|
+
|
|
|
|
+ audio.lastCached = Date.now();
|
|
|
|
+ console.log(`音频缓存成功: ${audio.id}`);
|
|
|
|
+ resolve();
|
|
|
|
+ } else {
|
|
|
|
+ reject(new Error(`下载失败,状态码: ${res.statusCode}`));
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ fail: (err) => {
|
|
|
|
+ reject(new Error(`下载失败: ${err.errMsg}`));
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }).catch(err => {
|
|
|
|
+ console.warn(`音频下载失败 ${audio.id}:`, err);
|
|
|
|
+ // 单个失败不影响其他
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ await Promise.all(downloadTasks);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 获取音频缓存信息(完整实现)
|
|
|
|
+ getAudioCacheInfo(audioId) {
|
|
|
|
+ if (!audioId) return null;
|
|
|
|
+
|
|
|
|
+ const cachedPath = uni.getStorageSync(`audio_file_${audioId}`);
|
|
|
|
+ if (!cachedPath) return null;
|
|
|
|
+
|
|
|
|
+ return {
|
|
|
|
+ cached: true,
|
|
|
|
+ path: cachedPath,
|
|
|
|
+ timestamp: uni.getStorageSync(`audio_cache_time_${audioId}`) || 0
|
|
|
|
+ };
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 保存音频信息到Storage(完整实现)
|
|
|
|
+ saveAudioInfoToStorage(wordId, audioInfo) {
|
|
|
|
+ if (!wordId || !audioInfo) return;
|
|
|
|
+
|
|
|
|
+ const cacheKey = `word_audio_${wordId}`;
|
|
|
|
+ const cacheData = {
|
|
|
|
+ ...audioInfo,
|
|
|
|
+ timestamp: Date.now(),
|
|
|
|
+ wordId: wordId
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ uni.setStorageSync(cacheKey, cacheData);
|
|
|
|
+ } catch (e) {
|
|
|
|
+ console.error('存储音频信息失败:', e);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 从Storage获取音频信息(完整实现)
|
|
|
|
+ getAudioInfoFromStorage(wordId) {
|
|
|
|
+ if (!wordId) return null;
|
|
|
|
+
|
|
|
|
+ const cacheKey = `word_audio_${wordId}`;
|
|
|
|
+ try {
|
|
|
|
+ return uni.getStorageSync(cacheKey) || null;
|
|
|
|
+ } catch (e) {
|
|
|
|
+ console.error('读取音频信息失败:', e);
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 播放音频(完整实现)
|
|
|
|
+ playAudio(audioInfo) {
|
|
|
|
+ if (!audioInfo || !audioInfo.url) {
|
|
|
|
+ console.error('无效的音频信息');
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 确保播放器初始化
|
|
|
|
+ this.init();
|
|
|
|
+
|
|
|
|
+ // 停止当前播放
|
|
|
|
+ this.player.stop();
|
|
|
|
+
|
|
|
|
+ // 在App环境下优先使用缓存
|
|
|
|
+ if (this.isApp) {
|
|
|
|
+ const cacheInfo = this.getAudioCacheInfo(audioInfo.id);
|
|
|
|
+ if (cacheInfo && cacheInfo.cached) {
|
|
|
|
+ console.log(`使用缓存播放: ${audioInfo.id}`);
|
|
|
|
+ this.player.src = cacheInfo.path;
|
|
|
|
+ this.player.play();
|
|
|
|
+ return this.player;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 无缓存或非App环境,使用网络播放
|
|
|
|
+ console.log(`使用网络播放: ${audioInfo.id}`);
|
|
|
|
+ this.player.src = audioInfo.url;
|
|
|
|
+ this.player.play();
|
|
|
|
+
|
|
|
|
+ // App环境下后台尝试缓存
|
|
|
|
+ if (this.isApp) {
|
|
|
|
+ this.downloadAudioFiles([audioInfo]).then(() => {
|
|
|
|
+ console.log(`音频后台缓存成功: ${audioInfo.id}`);
|
|
|
|
+ }).catch(err => {
|
|
|
|
+ console.warn(`音频后台缓存失败: ${audioInfo.id}`, err);
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return this.player;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // // 清理指定单词的缓存(完整实现)
|
|
|
|
+ // clearWordCache(wordId) {
|
|
|
|
+ // if (!wordId) return;
|
|
|
|
+
|
|
|
|
+ // // 清理音频信息
|
|
|
|
+ // uni.removeStorageSync(`word_audio_${wordId}`);
|
|
|
|
+
|
|
|
|
+ // // 清理所有相关音频文件
|
|
|
|
+ // const prefix = `word_${wordId}_`;
|
|
|
|
+ // const keys = uni.getStorageInfoSync().keys;
|
|
|
|
+
|
|
|
|
+ // keys.forEach(key => {
|
|
|
|
+ // if (key.startsWith('audio_file_') && key.includes(prefix)) {
|
|
|
|
+ // uni.removeStorageSync(key);
|
|
|
|
+ // }
|
|
|
|
+ // if (key.startsWith('audio_cache_time_') && key.includes(prefix)) {
|
|
|
|
+ // uni.removeStorageSync(key);
|
|
|
|
+ // }
|
|
|
|
+ // });
|
|
|
|
+ // },
|
|
|
|
+
|
|
|
|
+ // 清理所有缓存(完整实现)
|
|
|
|
+ clearAllCache() {
|
|
|
|
+ const keys = uni.getStorageInfoSync().keys;
|
|
|
|
+
|
|
|
|
+ // 清理所有单词音频信息
|
|
|
|
+ keys.filter(key => key.startsWith('word_audio_'))
|
|
|
|
+ .forEach(key => uni.removeStorageSync(key));
|
|
|
|
+
|
|
|
|
+ // 清理所有音频文件
|
|
|
|
+ keys.filter(key => key.startsWith('audio_file_'))
|
|
|
|
+ .forEach(key => uni.removeStorageSync(key));
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // // 获取缓存大小(完整实现)
|
|
|
|
+ // getCacheSize() {
|
|
|
|
+ // const keys = uni.getStorageInfoSync().keys;
|
|
|
|
+ // let size = 0;
|
|
|
|
+
|
|
|
|
+ // keys.filter(key => key.startsWith('audio_file_'))
|
|
|
|
+ // .forEach(key => {
|
|
|
|
+ // try {
|
|
|
|
+ // const filePath = uni.getStorageSync(key);
|
|
|
|
+ // if (filePath && typeof filePath === 'string') {
|
|
|
|
+ // // 注意:uni-app中无法直接获取文件大小,这里返回文件数量
|
|
|
|
+ // size += 1;
|
|
|
|
+ // }
|
|
|
|
+ // } catch (e) {
|
|
|
|
+ // console.error(`获取缓存大小失败: ${key}`, e);
|
|
|
|
+ // }
|
|
|
|
+ // });
|
|
|
|
+
|
|
|
|
+ // return {
|
|
|
|
+ // fileCount: size,
|
|
|
|
+ // // 实际项目中可以添加更精确的大小计算
|
|
|
|
+ // estimatedSize: size * 1024 * 100 // 假设每个文件约100KB
|
|
|
|
+ // };
|
|
|
|
+ // }
|
|
|
|
+};
|