From eccc9e475bebf9298cc4b3331c6d9ffa1b3fc688 Mon Sep 17 00:00:00 2001 From: hflllll Date: Mon, 13 Oct 2025 17:26:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E9=9F=B3=E9=A2=91=E6=8E=A7=E5=88=B6):=20?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E9=9F=B3=E9=A2=91=E6=92=AD=E6=94=BE=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=92=8C=E5=8D=95=E8=AF=8D=E9=87=8A=E4=B9=89=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 在CustomTabbar和book页面添加isWordAudioPlaying状态管理,防止单词音频与句子音频同时播放 2. 优化MeaningPopup图片显示模式为aspectFit 3. 在book页面添加当前页面标题显示 4. 重构handleTextClick和playWordAudio方法,添加详细日志和错误处理 5. 改进音频播放冲突处理,确保单词音频优先 6. 增强AudioControls组件,添加单词音频状态检查和停止功能 7. 优化音频匹配逻辑,支持多种匹配方式并添加实时TTS生成功能 8. 简化倍速播放检测逻辑,默认启用并优化设置流程 9. 添加页面卸载和隐藏时的资源清理逻辑 --- subPages/home/AudioControls.vue | 762 +++++++++++++++++++++++------- subPages/home/book.vue | 373 +++++++++++++-- subPages/home/components/CustomTabbar.vue | 5 + subPages/home/components/MeaningPopup.vue | 2 +- 4 files changed, 930 insertions(+), 212 deletions(-) diff --git a/subPages/home/AudioControls.vue b/subPages/home/AudioControls.vue index 412f355..f1d52ab 100644 --- a/subPages/home/AudioControls.vue +++ b/subPages/home/AudioControls.vue @@ -110,6 +110,10 @@ export default { pagePay: { type: Array, default: () => [] + }, + isWordAudioPlaying: { + type: Boolean, + default: false } }, data() { @@ -152,6 +156,8 @@ export default { shouldCancelRequest: false, // 是否应该取消当前请求 // 本地音色ID(避免直接修改prop) localVoiceId: '', // 本地音色ID,从prop初始化 + // 倍速检查相关 + lastSpeedCheckTime: -1, // 上次检查倍速的时间点 } }, computed: { @@ -433,9 +439,9 @@ export default { }); } - // 每个请求之间间隔400ms,避免请求过于频繁 + // 每个请求之间间隔200ms,避免请求过于频繁 if (i < segments.length - 1) { - await new Promise(resolve => setTimeout(resolve, 400)); + await new Promise(resolve => setTimeout(resolve, 200)); } } @@ -450,6 +456,34 @@ export default { // 获取当前页面的音频内容 async getCurrentPageAudio(autoPlay = false) { + // 🎯 确保音色ID已加载完成后再获取音频 + if (!this.localVoiceId || this.localVoiceId === '' || this.localVoiceId === null || this.localVoiceId === undefined) { + console.log('⚠️ 音色ID未加载,无法获取音频'); + console.log(' localVoiceId:', this.localVoiceId); + console.log(' 类型:', typeof this.localVoiceId); + + // 设置加载失败状态 + this.isAudioLoading = false; + this.audioLoadFailed = true; + this.hasAudioData = false; + + // 通知父组件音频状态变化 + this.$emit('audio-state-change', { + hasAudioData: false, + isLoading: false, + currentHighlightIndex: -1 + }); + + uni.showToast({ + title: '音色未加载,请稍后重试', + icon: 'none', + duration: 2000 + }); + return; + } + + console.log('✅ 音色ID验证通过:', this.localVoiceId); + // 检查是否正在页面切换中,如果是则不加载音频 if (this.isPageChanging) { console.log('页面切换中,跳过音频加载'); @@ -1005,6 +1039,9 @@ export default { if (this.currentPageAudios.length === 0) return; + // 🎯 全局音频管理:停止单词音频播放,确保只有一个音频播放 + this.stopWordAudio(); + // 如果没有当前音频实例或者需要切换音频 if (!this.currentAudio || this.currentAudio.src !== this.currentPageAudios[this.currentAudioIndex].url) { this.createAudioInstance(); @@ -1021,6 +1058,10 @@ export default { this.currentAudio.play(); // isPlaying状态会在onPlay事件中自动设置 this.updateHighlightIndex(); + // 确保倍速设置正确 + setTimeout(() => { + this.applyPlaybackRate(this.currentAudio); + }, 100); } }, @@ -1036,13 +1077,124 @@ export default { this.emitHighlightChange(-1); }, + // 文本标准化函数 - 移除多余空格、标点符号等 + normalizeText(text) { + if (!text || typeof text !== 'string') return ''; + + return text + .trim() + .replace(/\s+/g, ' ') // 将多个空格替换为单个空格 + .replace(/[,。!?;:""''()【】《》]/g, '') // 移除中文标点 + .replace(/[,.!?;:"'()\[\]<>]/g, '') // 移除英文标点 + .toLowerCase(); // 转为小写(对英文有效) + }, + + // 备用方案:使用 TTS API 实时生成并播放音频 + async playTextWithTTS(textContent) { + try { + console.log('🔊 使用 TTS 实时生成音频:', textContent); + + // 停止当前播放的音频 + if (this.currentAudio) { + this.currentAudio.pause(); + this.currentAudio.destroy(); + this.currentAudio = null; + } + + // 显示加载提示 + uni.showLoading({ + title: '正在生成音频...' + }); + + // 调用 TTS API + const audioRes = await this.$api.music.textToVoice({ + text: textContent, + voiceType: this.voiceId || 1 // 使用当前语音类型,默认为1 + }); + + uni.hideLoading(); + + console.log('🔊 TTS API 响应:', audioRes); + + if (audioRes && audioRes.result && audioRes.result.url) { + const audioUrl = audioRes.result.url; + + // 创建并播放音频 + const audio = uni.createInnerAudioContext(); + audio.src = audioUrl; + + audio.onPlay(() => { + console.log('🔊 TTS 音频开始播放'); + this.isPlaying = true; + }); + + audio.onEnded(() => { + console.log('🔊 TTS 音频播放完成'); + this.isPlaying = false; + audio.destroy(); + if (this.currentAudio === audio) { + this.currentAudio = null; + } + }); + + audio.onError((error) => { + console.error('🔊 TTS 音频播放失败:', error); + this.isPlaying = false; + audio.destroy(); + if (this.currentAudio === audio) { + this.currentAudio = null; + } + uni.showToast({ + title: '音频播放失败', + icon: 'none' + }); + }); + + // 保存当前音频实例并播放 + this.currentAudio = audio; + audio.play(); + + console.log('✅ TTS 音频播放成功'); + return true; + } else { + console.error('❌ TTS API 请求失败:', audioRes); + uni.showToast({ + title: '音频生成失败', + icon: 'none' + }); + return false; + } + } catch (error) { + uni.hideLoading(); + console.error('❌ TTS 音频生成异常:', error); + uni.showToast({ + title: '音频生成失败', + icon: 'none' + }); + return false; + } + }, + // 播放指定的音频段落(通过文本内容匹配) playSpecificAudio(textContent) { - console.log('尝试播放指定音频段落:', textContent); + console.log('🎯 尝试播放指定音频段落:', textContent); + console.log('📊 当前页面音频数组长度:', this.currentPageAudios.length); + console.log('📋 音频加载状态:', this.isAudioLoading); + + // 检查单词音频播放状态 + console.log('🎵 检查单词音频播放状态:', this.isWordAudioPlaying); + if (this.isWordAudioPlaying) { + console.log('⚠️ 单词音频正在播放,阻止句子音频播放'); + uni.showToast({ + title: '请等待单词音频播放完成', + icon: 'none' + }); + return false; + } // 检查textContent是否有效 if (!textContent || typeof textContent !== 'string') { - console.log('无效的文本内容:', textContent); + console.error('❌ 无效的文本内容:', textContent); uni.showToast({ title: '无效的文本内容', icon: 'none' @@ -1050,37 +1202,170 @@ export default { return false; } - // 在当前页面音频数组中查找匹配的音频 - let audioIndex = this.currentPageAudios.findIndex(audio => - audio.text && audio.text.trim() === textContent.trim() - ); + // 检查音频数据是否已加载 + if (this.currentPageAudios.length === 0) { + console.warn('⚠️ 当前页面音频数据为空,可能还在加载中'); + uni.showToast({ + title: '音频正在加载中,请稍后再试', + icon: 'none' + }); + return false; + } + + // 标准化目标文本 + const normalizedTarget = this.normalizeText(textContent); + console.log('🔍 标准化后的目标文本:', normalizedTarget); - // 如果直接匹配失败,尝试匹配分段音频 - if (audioIndex === -1) { - console.log('直接匹配失败,尝试匹配分段音频'); + // 打印所有音频文本用于调试 + console.log('📝 当前页面所有音频文本:'); + this.currentPageAudios.forEach((audio, index) => { + console.log(` [${index}] 原始文本: "${audio.text}"`); + console.log(` [${index}] 标准化文本: "${this.normalizeText(audio.text)}"`); + if (audio.originalText) { + console.log(` [${index}] 原始完整文本: "${audio.originalText}"`); + } + }); + + let audioIndex = -1; + + // 第一步:精确匹配(标准化后) + audioIndex = this.currentPageAudios.findIndex(audio => { + if (!audio.text) return false; + const normalizedAudio = this.normalizeText(audio.text); + return normalizedAudio === normalizedTarget; + }); + + if (audioIndex !== -1) { + console.log('✅ 精确匹配成功,索引:', audioIndex); + } else { + console.log('❌ 精确匹配失败,尝试模糊匹配'); - // 查找包含该文本的分段音频 + // 第二步:包含匹配 audioIndex = this.currentPageAudios.findIndex(audio => { if (!audio.text) return false; + const normalizedAudio = this.normalizeText(audio.text); - // 检查是否为分段音频,且原始文本包含目标文本 - if (audio.isSegmented && audio.originalText) { - return audio.originalText.trim() === textContent.trim(); - } - - // 检查目标文本是否包含在当前音频文本中,或者当前音频文本包含在目标文本中 - const audioText = audio.text.trim(); - const targetText = textContent.trim(); - return audioText.includes(targetText) || targetText.includes(audioText); + // 双向包含检查 + return normalizedAudio.includes(normalizedTarget) || normalizedTarget.includes(normalizedAudio); }); if (audioIndex !== -1) { - console.log('找到分段音频匹配,索引:', audioIndex); + console.log('✅ 包含匹配成功,索引:', audioIndex); + } else { + console.log('❌ 包含匹配失败,尝试分段音频匹配'); + + // 第三步:分段音频匹配 + audioIndex = this.currentPageAudios.findIndex(audio => { + if (!audio.text) return false; + + // 检查是否为分段音频,且原始文本匹配 + if (audio.isSegmented && audio.originalText) { + const normalizedOriginal = this.normalizeText(audio.originalText); + return normalizedOriginal === normalizedTarget || + normalizedOriginal.includes(normalizedTarget) || + normalizedTarget.includes(normalizedOriginal); + } + + return false; + }); + + if (audioIndex !== -1) { + console.log('✅ 分段音频匹配成功,索引:', audioIndex); + } else { + console.log('❌ 所有匹配方式都失败'); + + // 第四步:句子分割匹配(针对长句子) + console.log('❌ 尝试句子分割匹配...'); + + // 将目标句子按标点符号分割 + const targetSentences = normalizedTarget.split(/[,。!?;:,!?;:]/).filter(s => s.trim().length > 0); + console.log('🔪 目标句子分割结果:', targetSentences); + + if (targetSentences.length > 1) { + // 尝试匹配分割后的句子片段 + for (let i = 0; i < targetSentences.length; i++) { + const sentence = targetSentences[i].trim(); + if (sentence.length < 3) continue; // 跳过太短的片段 + + audioIndex = this.currentPageAudios.findIndex(audio => { + if (!audio.text) return false; + const normalizedAudio = this.normalizeText(audio.text); + return normalizedAudio.includes(sentence) || sentence.includes(normalizedAudio); + }); + + if (audioIndex !== -1) { + console.log(`✅ 句子片段匹配成功,片段: "${sentence}", 索引: ${audioIndex}`); + break; + } + } + } + + if (audioIndex === -1) { + console.log('❌ 句子分割匹配失败,尝试关键词匹配...'); + + // 第五步:关键词匹配(提取关键词进行匹配) + const keywords = normalizedTarget.split(/\s+/).filter(word => word.length > 2); + console.log('🔑 提取的关键词:', keywords); + + let bestKeywordMatch = -1; + let bestKeywordCount = 0; + + this.currentPageAudios.forEach((audio, index) => { + if (!audio.text) return; + const normalizedAudio = this.normalizeText(audio.text); + + // 计算匹配的关键词数量 + const matchedKeywords = keywords.filter(keyword => normalizedAudio.includes(keyword)); + const matchCount = matchedKeywords.length; + + if (matchCount > bestKeywordCount && matchCount >= Math.min(2, keywords.length)) { + bestKeywordCount = matchCount; + bestKeywordMatch = index; + console.log(` [${index}] 关键词匹配: ${matchCount}/${keywords.length}, 匹配词: [${matchedKeywords.join(', ')}]`); + } + }); + + if (bestKeywordMatch !== -1) { + audioIndex = bestKeywordMatch; + console.log(`✅ 关键词匹配成功,索引: ${audioIndex}, 匹配数: ${bestKeywordCount}/${keywords.length}`); + } else { + console.log('❌ 关键词匹配失败,尝试相似度匹配...'); + + // 第六步:相似度匹配(最后的尝试) + let bestMatch = -1; + let bestSimilarity = 0; + + this.currentPageAudios.forEach((audio, index) => { + if (!audio.text) return; + + const normalizedAudio = this.normalizeText(audio.text); + + // 计算简单的相似度(共同字符数 / 较长文本长度) + const commonChars = [...normalizedTarget].filter(char => normalizedAudio.includes(char)).length; + const maxLength = Math.max(normalizedTarget.length, normalizedAudio.length); + const similarity = maxLength > 0 ? commonChars / maxLength : 0; + + console.log(` [${index}] 相似度: ${similarity.toFixed(2)}, 文本: "${audio.text}"`); + + if (similarity > bestSimilarity && similarity > 0.5) { // 降低相似度阈值到50% + bestSimilarity = similarity; + bestMatch = index; + } + }); + + if (bestMatch !== -1) { + audioIndex = bestMatch; + console.log(`✅ 相似度匹配成功,索引: ${audioIndex}, 相似度: ${bestSimilarity.toFixed(2)}`); + } + } + } + } } } if (audioIndex !== -1) { - console.log('找到匹配的音频,索引:', audioIndex); + console.log('🎵 找到匹配的音频,开始播放,索引:', audioIndex); + console.log('🎵 匹配的音频文本:', this.currentPageAudios[audioIndex].text); // 停止当前播放的音频 if (this.currentAudio) { @@ -1108,18 +1393,19 @@ export default { setTimeout(() => { if (this.currentAudio) { this.currentAudio.play(); - console.log('开始播放指定音频段落'); + console.log('🎵 开始播放指定音频段落'); + } else { + console.error('❌ 音频实例创建失败'); } }, 100); return true; // 成功找到并播放 } else { - console.log('未找到匹配的音频段落:', textContent); - uni.showToast({ - title: '未找到对应的音频', - icon: 'none' - }); - return false; // 未找到匹配的音频 + console.error('❌ 未找到匹配的音频段落:', textContent); + console.log('💡 尝试使用备用方案:实时生成音频'); + + // 备用方案:使用 textToVoice API 实时生成音频 + return this.playTextWithTTS(textContent); } }, @@ -1141,8 +1427,14 @@ export default { console.log('使用微信原生音頻API'); audio = wx.createInnerAudioContext(); } else { - console.log('使用uni-app音頻API'); - audio = uni.createInnerAudioContext(); + // 在H5环境下,尝试使用原生HTML5 Audio API来支持倍速 + if (typeof window !== 'undefined' && window.Audio) { + console.log('使用原生HTML5 Audio API以支持倍速'); + audio = this.createHTML5Audio(); + } else { + console.log('使用uni-app音頻API'); + audio = uni.createInnerAudioContext(); + } } const audioUrl = this.currentPageAudios[this.currentAudioIndex].url; audio.src = audioUrl; @@ -1154,43 +1446,47 @@ export default { // 在音頻可以播放時檢測playbackRate支持 audio.onCanplay(() => { - console.log('🎵 音頻可以播放,開始檢測playbackRate支持'); - this.checkPlaybackRateSupport(audio); + console.log('🎵 音頻可以播放事件觸發,開始檢測playbackRate支持'); + console.log('🔍 音頻實例基本信息:', { + src: audio.src, + duration: audio.duration, + readyState: audio.readyState || '未知', + constructor: audio.constructor.name + }); - // 檢測完成後,設置用戶期望的播放速度 - setTimeout(() => { - if (this.playbackRateSupported) { - console.log('設置播放速度:', this.playSpeed); - audio.playbackRate = this.playSpeed; - - // 驗證設置結果 - setTimeout(() => { - console.log('最終播放速度:', audio.playbackRate); - if (Math.abs(audio.playbackRate - this.playSpeed) > 0.01) { - console.log('⚠️ 播放速度設置可能未生效'); - } else { - console.log('✅ 播放速度設置成功'); - } - }, 50); - } else { - console.log('❌ 當前環境不支持播放速度控制'); - } - }, 50); + this.checkPlaybackRateSupport(audio); }); // 音频事件监听 audio.onPlay(() => { - console.log('音频开始播放'); + console.log('🎵 音频开始播放'); this.isPlaying = true; + + // 在播放开始时立即设置倍速 + this.applyPlaybackRate(audio); }); audio.onPause(() => { - console.log('音频暂停'); + console.log('⏸️ 音频暂停'); + console.log('📊 暫停時倍速狀態:', { + 當前播放速度: audio.playbackRate + 'x', + 期望播放速度: this.playSpeed + 'x' + }); this.isPlaying = false; }); audio.onTimeUpdate(() => { this.updateCurrentTime(); + + // 定期检查倍速设置是否正确(每5秒检查一次) + if (Math.floor(audio.currentTime) % 5 === 0 && Math.floor(audio.currentTime) !== this.lastSpeedCheckTime) { + this.lastSpeedCheckTime = Math.floor(audio.currentTime); + const rateDifference = Math.abs(audio.playbackRate - this.playSpeed); + if (rateDifference > 0.01) { + console.log('⚠️ 检测到倍速偏差,重新应用倍速设置'); + this.applyPlaybackRate(audio); + } + } }); audio.onEnded(() => { @@ -1317,14 +1613,18 @@ export default { }, toggleSpeed() { - // 檢查是否支持播放速度控制 + console.log('🎛️ 用户点击倍速切换按钮...'); + + // 简化检测:只在极少数情况下阻止倍速切换 + console.log('🔍 检查倍速支持状态:', { + playbackRateSupported: this.playbackRateSupported, + 当前状态: this.playbackRateSupported ? '✅ 支持' : '⚠️ 受限' + }); + + // 只有在明确禁用的情况下才阻止(比如Android 4.x) if (!this.playbackRateSupported) { - uni.showToast({ - title: '當前設備不支持播放速度控制', - icon: 'none', - duration: 2000 - }); - return; + console.log('⚠️ 倍速功能受限,但仍尝试切换'); + // 不再直接返回,而是继续尝试 } const currentIndex = this.speedOptions.indexOf(this.playSpeed); @@ -1332,30 +1632,64 @@ export default { const oldSpeed = this.playSpeed; this.playSpeed = this.speedOptions[nextIndex]; - console.log(`播放速度切換: ${oldSpeed}x -> ${this.playSpeed}x`); + console.log('⚡ 倍速切换详情:', { + 可用选项: this.speedOptions, + 当前索引: currentIndex, + 下一个索引: nextIndex, + 旧速度: oldSpeed + 'x', + 新速度: this.playSpeed + 'x', + 切换时间: new Date().toLocaleTimeString() + }); // 如果当前有音频在播放,更新播放速度 + console.log('🎵 检查音频实例状态:', { + 音频实例存在: !!this.currentAudio, + 正在播放: this.isPlaying, + 音频src: this.currentAudio ? this.currentAudio.src : '无' + }); + if (this.currentAudio) { const wasPlaying = this.isPlaying; const currentTime = this.currentAudio.currentTime; - // 設置新的播放速度 - this.currentAudio.playbackRate = this.playSpeed; + console.log('🔧 准备更新音频播放速度:', { + 播放状态: wasPlaying ? '正在播放' : '已暂停', + 当前时间: currentTime + 's', + 目标速度: this.playSpeed + 'x' + }); + + // 使用统一的倍速设置方法 + this.applyPlaybackRate(this.currentAudio); - // 如果正在播放,需要重啟播放才能使播放速度生效 + // 如果正在播放,需要重启播放才能使播放速度生效 if (wasPlaying) { + console.log('🔄 重启播放以应用新速度...'); this.currentAudio.pause(); setTimeout(() => { - this.currentAudio.seek(currentTime); + // 不使用seek方法,直接重新播放 this.currentAudio.play(); - }, 50); + console.log('▶️ 播放已重启,新速度已生效'); + }, 100); } - console.log('音頻實例播放速度已更新為:', this.currentAudio.playbackRate); + console.log('📊 最终音频状态:', { + 播放速度: this.currentAudio.playbackRate + 'x', + 播放时间: this.currentAudio.currentTime + 's', + 播放状态: this.isPlaying ? '播放中' : '已暂停' + }); - // 顯示速度變更提示 + // 显示速度变更提示 uni.showToast({ - title: `播放速度: ${this.playSpeed}x`, + title: `🎵 播放速度: ${this.playSpeed}x`, + icon: 'none', + duration: 1000 + }); + + console.log('🎉 倍速切换完成!新速度:', this.playSpeed + 'x'); + } else { + console.log('⚠️ 没有音频实例,仅更新速度设置'); + uni.showToast({ + title: `⚡ 速度设为: ${this.playSpeed}x`, icon: 'none', duration: 1000 }); @@ -1506,123 +1840,161 @@ export default { } }, - // 初始檢測播放速度支持(不依賴音頻實例) + // 初始检测播放速度支持(简化版本,默认启用) checkInitialPlaybackRateSupport() { + console.log('🚀 开始初始倍速播放支持检测...'); try { const systemInfo = uni.getSystemInfoSync(); - console.log('初始檢測 - 系統信息:', systemInfo); - - // 檢查基礎庫版本 - playbackRate需要2.11.0及以上 - const SDKVersion = systemInfo.SDKVersion || '0.0.0'; - const versionArray = SDKVersion.split('.').map(v => parseInt(v)); - const isVersionSupported = versionArray[0] > 2 || - (versionArray[0] === 2 && versionArray[1] > 11) || - (versionArray[0] === 2 && versionArray[1] === 11 && versionArray[2] >= 0); - - if (!isVersionSupported) { - this.playbackRateSupported = false; - console.log(`初始檢測 - 基礎庫版本過低 (${SDKVersion}),需要2.11.0及以上才支持播放速度控制`); - return; - } + console.log('📱 系统信息:', { + platform: systemInfo.platform, + system: systemInfo.system, + SDKVersion: systemInfo.SDKVersion, + brand: systemInfo.brand, + model: systemInfo.model + }); + + // 简化检测逻辑:默认启用倍速功能 + // 只在极少数明确不支持的情况下禁用 + this.playbackRateSupported = true; - // Android 6以下版本不支持 + // 仅对非常老的Android版本进行限制(Android 4.x及以下) if (systemInfo.platform === 'android') { const androidVersion = systemInfo.system.match(/Android (\d+)/); - if (androidVersion && parseInt(androidVersion[1]) < 6) { + if (androidVersion && parseInt(androidVersion[1]) < 5) { this.playbackRateSupported = false; - console.log('初始檢測 - Android版本過低,需要Android 6及以上才支持播放速度控制'); + console.log(`⚠️ Android版本过低 (${androidVersion[1]}),禁用倍速功能`); + uni.showToast({ + title: `Android ${androidVersion[1]} 不支持倍速`, + icon: 'none', + duration: 2000 + }); return; } } - // 檢查微信原生API是否可用 - if (typeof wx === 'undefined' || !wx.createInnerAudioContext) { - console.log('初始檢測 - 微信原生API不可用,可能影響playbackRate支持'); - } else { - console.log('初始檢測 - 微信原生API可用'); - } + console.log('✅ 倍速功能已启用!'); + console.log('📊 可用倍速选项:', this.speedOptions); + console.log('⚡ 当前播放速度:', this.playSpeed + 'x'); - // 如果通過基本檢測,暫時設為支持,等音頻實例創建後再詳細檢測 - this.playbackRateSupported = true; - console.log('初始檢測 - 基本條件滿足,等待音頻實例檢測'); + // 显示成功提示 + uni.showToast({ + title: '✅ 倍速功能可用', + icon: 'none', + duration: 1500 + }); } catch (error) { - console.error('初始檢測播放速度支持時出錯:', error); - this.playbackRateSupported = false; + console.error('💥 检测播放速度支持时出错:', error); + // 即使出错也默认启用 + this.playbackRateSupported = true; + console.log('⚠️ 检测出错,但仍启用倍速功能'); + } + }, + + // 应用播放速度设置 + applyPlaybackRate(audio) { + if (!audio) return; + + console.log('⚙️ 开始应用播放速度设置...'); + console.log('📊 当前状态检查:', { + playbackRateSupported: this.playbackRateSupported, + 期望速度: this.playSpeed + 'x', + 音频当前速度: audio.playbackRate + 'x', + 音频播放状态: this.isPlaying ? '播放中' : '未播放' + }); + + if (this.playbackRateSupported) { + try { + // 多次尝试设置倍速,确保生效 + const maxAttempts = 3; + let attempt = 0; + + const trySetRate = () => { + attempt++; + audio.playbackRate = this.playSpeed; + + setTimeout(() => { + const actualRate = audio.playbackRate; + const rateDifference = Math.abs(actualRate - this.playSpeed); + + console.log(`🔍 倍速设置尝试 ${attempt}/${maxAttempts}:`, { + 期望速度: this.playSpeed, + 实际速度: actualRate, + 差值: rateDifference, + 设置成功: rateDifference < 0.01 + }); + + if (rateDifference >= 0.01 && attempt < maxAttempts) { + console.log(`⚠️ 倍速设置未完全生效,进行第 ${attempt + 1} 次尝试...`); + setTimeout(trySetRate, 100); + } else if (rateDifference < 0.01) { + console.log('✅ 倍速设置成功!'); + } else { + console.log('⚠️ 倍速设置可能不完全支持,但已尽力尝试'); + } + }, 50); + }; + + trySetRate(); + + } catch (error) { + console.log('💥 设置播放速度时出错:', error); + } + } else { + console.log('❌ 当前环境不支持播放速度控制'); } }, - // 檢查播放速度控制支持 + // 检查播放速度控制支持(简化版本) checkPlaybackRateSupport(audio) { + console.log('🎵 开始音频实例倍速支持检测...'); try { - // 檢查基礎庫版本和平台支持 - const systemInfo = uni.getSystemInfoSync(); - console.log('系統信息:', systemInfo); - console.log('平台:', systemInfo.platform); - console.log('基礎庫版本:', systemInfo.SDKVersion); - console.log('系統版本:', systemInfo.system); - - // 根據uni-app文檔,微信小程序需要基礎庫2.11.0+才支持playbackRate - const SDKVersion = systemInfo.SDKVersion || '0.0.0'; - const versionArray = SDKVersion.split('.').map(v => parseInt(v)); - const isVersionSupported = versionArray[0] > 2 || - (versionArray[0] === 2 && versionArray[1] > 11) || - (versionArray[0] === 2 && versionArray[1] === 11 && versionArray[2] >= 0); - - console.log('基礎庫版本檢查:', { - version: SDKVersion, - parsed: versionArray, - supported: isVersionSupported - }); - - if (!isVersionSupported) { - this.playbackRateSupported = false; - console.log(`❌ 基礎庫版本不支持 (${SDKVersion}),微信小程序需要2.11.0+才支持playbackRate`); + // 如果初始检测已经禁用,直接返回 + if (!this.playbackRateSupported) { + console.log('⚠️ 初始检测已禁用倍速功能,跳过音频实例检测'); return; } - // Android平台需要6.0+版本支持 - if (systemInfo.platform === 'android') { - const androidVersion = systemInfo.system.match(/Android (\d+)/); - console.log('Android版本檢查:', androidVersion); - if (androidVersion && parseInt(androidVersion[1]) < 6) { - this.playbackRateSupported = false; - console.log(`❌ Android版本不支持 (${androidVersion[1]}),需要Android 6+才支持playbackRate`); - return; - } - } - - // 檢查音頻實例是否支持playbackRate - console.log('🔍 音頻實例檢查:'); - console.log('- playbackRate初始值:', audio.playbackRate); - console.log('- playbackRate類型:', typeof audio.playbackRate); - - // 嘗試設置並檢測是否真正支持 - const testRate = 1.25; - const originalRate = audio.playbackRate || 1.0; + console.log('🎧 音频实例信息:', { + 音频对象存在: !!audio, + 音频对象类型: typeof audio, + 音频src: audio ? audio.src : '无' + }); - try { - audio.playbackRate = testRate; - console.log('- 設置測試速度:', testRate); - console.log('- 設置後的值:', audio.playbackRate); + // 简化检测:直接尝试设置playbackRate + if (audio && typeof audio.playbackRate !== 'undefined') { + console.log('✅ 音频实例支持playbackRate属性'); - // 檢查設置是否生效 - if (Math.abs(audio.playbackRate - testRate) < 0.01) { - this.playbackRateSupported = true; - console.log('✅ playbackRate功能正常'); - } else { - this.playbackRateSupported = false; - console.log('❌ playbackRate設置無效,可能不被支持'); + // 尝试设置当前播放速度 + try { + const currentSpeed = this.playSpeed || 1.0; + audio.playbackRate = currentSpeed; + console.log(`🔧 设置播放速度为 ${currentSpeed}x`); + + // 简单验证 + setTimeout(() => { + const actualRate = audio.playbackRate; + console.log('🔍 播放速度验证:', { + 期望值: currentSpeed, + 实际值: actualRate, + 设置成功: Math.abs(actualRate - currentSpeed) < 0.1 + }); + }, 50); + + } catch (error) { + console.log('⚠️ 设置播放速度时出现错误,但继续保持倍速功能启用:', error.message); } - - } catch (error) { - this.playbackRateSupported = false; - console.log('❌ playbackRate設置出錯:', error); + } else { + console.log('⚠️ 音频实例不支持playbackRate属性,但保持倍速功能启用'); } + // 保持倍速功能启用状态 + console.log('✅ 音频实例检测完成,倍速功能保持启用'); + } catch (error) { - console.error('檢查播放速度支持時出錯:', error); - this.playbackRateSupported = false; + console.error('💥 检查播放速度支持时出错:', error); + // 即使出错也保持启用状态 + console.log('⚠️ 检测出错,但保持倍速功能启用'); } }, @@ -1680,12 +2052,64 @@ export default { // 清理音频资源 destroyAudio() { + console.log('🧹 AudioControls: 开始清理音频资源'); + + // 1. 停止并销毁当前音频实例 if (this.currentAudio) { - this.currentAudio.destroy(); + console.log('🧹 销毁当前音频实例'); + try { + // 先暂停再销毁 + if (this.isPlaying) { + this.currentAudio.pause(); + } + this.currentAudio.destroy(); + } catch (error) { + console.error('销毁音频实例失败:', error); + } this.currentAudio = null; } + + // 2. 重置所有播放状态 this.isPlaying = false; + this.currentTime = 0; + this.sliderValue = 0; + this.currentHighlightIndex = -1; + + // 3. 清理音频缓存 this.clearAudioCache(); + + // 4. 取消正在进行的请求 + this.shouldCancelRequest = true; + + // 5. 重置加载状态 + this.isAudioLoading = false; + this.isVoiceChanging = false; + this.audioLoadFailed = false; + + console.log('✅ AudioControls: 音频资源清理完成'); + }, + + // 停止单词音频播放(全局音频管理) + stopWordAudio() { + try { + // 通过父组件访问book页面的单词音频 + const pages = getCurrentPages(); + const currentPage = pages[pages.length - 1]; + + if (currentPage && currentPage.$vm) { + const bookVm = currentPage.$vm; + if (bookVm.currentWordAudio) { + console.log('🔄 AudioControls: 停止单词音频播放'); + bookVm.currentWordAudio.pause(); + bookVm.currentWordAudio.destroy(); + bookVm.currentWordAudio = null; + bookVm.isWordAudioPlaying = false; + console.log('✅ AudioControls: 单词音频已停止'); + } + } + } catch (error) { + console.log('⚠️ AudioControls: 停止单词音频时出现异常:', error); + } }, // 课程切换时的完整数据清理(保留音色设置) @@ -1990,7 +2414,7 @@ export default { console.log(`预加载进度: ${this.preloadProgress}%`); // 延迟一下,避免请求过于频繁 - await new Promise(resolve => setTimeout(resolve, 600)); + await new Promise(resolve => setTimeout(resolve, 300)); } catch (error) { console.error(`预加载第${pageInfo.pageIndex + 1}页音频失败:`, error); @@ -2108,9 +2532,9 @@ export default { console.error(`第${pageIndex + 1}页第${i + 1}个文本项处理异常:`, error); } - // 每个文本项处理之间间隔600ms,避免请求过于频繁 + // 每个文本项处理之间间隔300ms,避免请求过于频繁 if (i < textItems.length - 1) { - await new Promise(resolve => setTimeout(resolve, 600)); + await new Promise(resolve => setTimeout(resolve, 300)); } } @@ -2186,8 +2610,18 @@ export default { } }, mounted() { + console.log('🎛️ AudioControls組件初始化開始'); + console.log('⚙️ 初始倍速配置:', { + 默認播放速度: this.playSpeed + 'x', + 可選速度選項: this.speedOptions.map(s => s + 'x'), + 初始支持狀態: this.playbackRateSupported + }); + // 初始檢測播放速度支持 + console.log('🔍 開始檢測播放速度支持...'); this.checkInitialPlaybackRateSupport(); + + console.log('✅ AudioControls組件初始化完成'); }, // 自动播放预加载的音频 diff --git a/subPages/home/book.vue b/subPages/home/book.vue index 474d245..2a2c840 100644 --- a/subPages/home/book.vue +++ b/subPages/home/book.vue @@ -30,7 +30,7 @@ > - + {{ currentPageTitle }} {{ pageTitles[index] }} @@ -47,13 +47,14 @@ - + {{ token.text }} @@ -72,7 +73,7 @@ - + {{ item.content }} - {{ item.content }} + {{ item.content }} @@ -128,6 +129,7 @@ :is-member="isMember" :current-page-requires-member="currentPageRequiresMember" :page-pay="pagePay" + :is-word-audio-playing="isWordAudioPlaying" @toggle-course-popup="toggleCoursePopup" @toggle-sound="toggleSound" @go-to-page="goToPage" @@ -191,6 +193,7 @@ export default { currentHighlightIndex: -1, // 当前高亮的文本索引 wordAudioCache: {}, // 單詞語音緩存 currentWordAudio: null, // 當前播放的單詞音頻實例 + isWordAudioPlaying: false, // 是否有单词音频正在播放 // 音频状态相关 isAudioLoading: false, // 音频是否正在加载 @@ -361,25 +364,42 @@ export default { // 处理文本点击事件 handleTextClick(textContent, item, pageIndex) { - // console.log('点击文本:', textContent); - // console.log('textContent类型:', typeof textContent); - // console.log('textContent是否为undefined:', textContent === undefined); - // console.log('完整item对象:', item); - // console.log('item.content:', item ? item.content : 'item为空'); - // console.log('当前页面索引:', this.currentPage); - // console.log('点击的页面索引:', pageIndex); - // console.log('当前页面数据:', this.bookPages[this.currentPage - 1]); - // console.log('页面数据长度:', this.bookPages[this.currentPage - 1] ? this.bookPages[this.currentPage - 1].length : '页面不存在'); + console.log('🎯 ===== 文本点击事件开始 ====='); + console.log('📝 点击文本:', textContent); + console.log('📄 textContent类型:', typeof textContent); + console.log('❓ textContent是否为undefined:', textContent === undefined); + console.log('📦 完整item对象:', item); + console.log('📝 item.content:', item ? item.content : 'item为空'); + console.log('📖 当前页面索引:', this.currentPage); + console.log('👆 点击的页面索引:', pageIndex); + console.log('📊 当前页面类型:', this.currentPageType); + console.log('📄 是否为文本页面:', this.isTextPage); + console.log('📋 当前页面数据:', this.bookPages[this.currentPage - 1]); + console.log('📏 页面数据长度:', this.bookPages[this.currentPage - 1] ? this.bookPages[this.currentPage - 1].length : '页面不存在'); + + // 检查音频播放状态 + console.log('🎵 ===== 音频状态检查 ====='); + console.log(' isWordAudioPlaying:', this.isWordAudioPlaying); + console.log(' currentWordAudio存在:', !!this.currentWordAudio); + console.log(' currentWordMeaning存在:', !!this.currentWordMeaning); + + if (this.isWordAudioPlaying) { + console.log('⚠️ 检测到单词音频正在播放状态,这可能会阻止句子音频播放'); + console.log('🔄 尝试重置音频播放状态...'); + this.isWordAudioPlaying = false; + console.log('✅ 音频播放状态已重置'); + } // 检查是否点击的是当前页面 if (pageIndex !== undefined && pageIndex !== this.currentPage - 1) { - console.log('点击的不是当前页面,忽略点击事件'); + console.warn('⚠️ 点击的不是当前页面,忽略点击事件'); + console.log(` 期望页面: ${this.currentPage - 1}, 点击页面: ${pageIndex}`); return; } // 验证参数有效性 if (!item) { - console.error('handleTextClick: item参数为空'); + console.error('❌ handleTextClick: item参数为空'); uni.showToast({ title: '数据错误,请刷新页面', icon: 'none' @@ -390,12 +410,16 @@ export default { // 如果textContent为undefined,尝试从item中获取 if (!textContent && item && item.content) { textContent = item.content; - console.log('从item中获取到content:', textContent); + console.log('🔄 从item中获取到content:', textContent); } // 最终验证textContent if (!textContent || typeof textContent !== 'string' || textContent.trim() === '') { - console.error('handleTextClick: 无效的文本内容', textContent); + console.error('❌ handleTextClick: 无效的文本内容', textContent); + console.log(' textContent:', textContent); + console.log(' 类型:', typeof textContent); + console.log(' 是否为空字符串:', textContent === ''); + console.log(' trim后是否为空:', textContent && textContent.trim() === ''); uni.showToast({ title: '文本内容无效', icon: 'none' @@ -403,9 +427,27 @@ export default { return; } + console.log('✅ 文本内容验证通过:', textContent); + console.log(' 文本长度:', textContent.length); + console.log(' 文本预览:', textContent.substring(0, 50) + (textContent.length > 50 ? '...' : '')); + // 检查是否有音频控制组件的引用 - if (!this.$refs.customTabbar || !this.$refs.customTabbar.$refs.audioControls) { - console.log('音频控制组件未找到'); + console.log('🔍 检查音频控制组件引用...'); + console.log(' customTabbar存在:', !!this.$refs.customTabbar); + + if (!this.$refs.customTabbar) { + console.error('❌ customTabbar引用不存在'); + uni.showToast({ + title: '音频控制组件未准备好', + icon: 'none' + }); + return; + } + + console.log(' audioControls存在:', !!this.$refs.customTabbar.$refs.audioControls); + + if (!this.$refs.customTabbar.$refs.audioControls) { + console.error('❌ audioControls引用不存在'); uni.showToast({ title: '音频控制组件未准备好', icon: 'none' @@ -415,8 +457,13 @@ export default { // 检查当前页面是否为文本页面或卡片页面 // 卡片页面(type为'1')现在也支持整句音频播放 + console.log('🔍 检查页面类型支持...'); + console.log(' isTextPage:', this.isTextPage); + console.log(' currentPageType:', this.currentPageType); + if (!this.isTextPage && this.currentPageType !== '1') { - console.log('当前页面不是文本页面或卡片页面'); + console.warn('⚠️ 当前页面不是文本页面或卡片页面'); + console.log(` isTextPage: ${this.isTextPage}, currentPageType: ${this.currentPageType}`); uni.showToast({ title: '当前页面不支持音频播放', icon: 'none' @@ -424,14 +471,35 @@ export default { return; } + console.log('✅ 页面类型检查通过,准备播放音频'); + + // 获取音频控制组件实例 + const audioControls = this.$refs.customTabbar.$refs.audioControls; + console.log('🎵 音频控制组件状态:'); + console.log(' currentPageAudios存在:', !!audioControls.currentPageAudios); + console.log(' currentPageAudios长度:', audioControls.currentPageAudios ? audioControls.currentPageAudios.length : 0); + console.log(' isAudioLoading:', audioControls.isAudioLoading); + console.log(' currentAudioIndex:', audioControls.currentAudioIndex); + console.log(' isPlaying:', audioControls.isPlaying); + // 调用AudioControls组件的播放指定音频方法 - const success = this.$refs.customTabbar.$refs.audioControls.playSpecificAudio(textContent); + console.log('🚀 调用 playSpecificAudio...'); + const success = audioControls.playSpecificAudio(textContent); + + console.log('🎵 playSpecificAudio 返回结果:', success); if (success) { - console.log('成功播放指定音频段落'); + console.log('✅ 成功播放指定音频段落'); } else { - console.log('播放指定音频段落失败'); + console.error('❌ 播放指定音频段落失败'); + console.log('💡 失败可能原因:'); + console.log(' 1. 文本内容与音频数据不匹配'); + console.log(' 2. 音频数据尚未加载完成'); + console.log(' 3. 音频文件路径错误或文件损坏'); + console.log(' 4. 网络连接问题'); } + + console.log('🎯 ===== 文本点击事件结束 ====='); }, onHighlightChange(highlightData) { @@ -498,7 +566,35 @@ export default { } }, closeMeaningPopup() { - this.currentWordMeaning = null + console.log('🔄 ===== 关闭重点单词弹窗 ====='); + console.log('📱 清理弹窗数据...'); + this.currentWordMeaning = null; + + // 重置音频播放状态,确保后续句子点击能正常播放 + console.log('🎵 检查音频播放状态...'); + console.log(' 当前 isWordAudioPlaying:', this.isWordAudioPlaying); + console.log(' 当前 currentWordAudio:', !!this.currentWordAudio); + + if (this.isWordAudioPlaying) { + console.log('🛑 重置音频播放状态'); + this.isWordAudioPlaying = false; + + // 如果有正在播放的音频,停止它 + if (this.currentWordAudio) { + console.log('🛑 停止当前播放的单词音频'); + try { + this.currentWordAudio.pause(); + this.currentWordAudio.destroy(); + console.log('✅ 单词音频已停止并销毁'); + } catch (error) { + console.log('⚠️ 停止单词音频时出现异常:', error); + } + this.currentWordAudio = null; + } + } + + console.log('✅ 弹窗关闭完成,音频状态已重置'); + console.log('🏁 ===== 关闭重点单词弹窗结束 ====='); }, // 将英文句子分割成单词数组 @@ -600,50 +696,133 @@ export default { async playWordAudio(word) { try { - console.log('開始播放單詞語音:', word); + console.log('🎵 ===== 开始播放单词语音 ====='); + console.log('📝 目标单词:', word); + console.log('🎧 当前音频实例状态:', this.currentWordAudio ? '存在' : '不存在'); + console.log('🔊 voiceId:', this.voiceId, '类型:', typeof this.voiceId); + + // 检查是否有音频正在播放,如果有则停止 + if (this.isWordAudioPlaying) { + console.log('⚠️ 有音频正在播放,将停止当前播放'); + this.isWordAudioPlaying = false; + } + + // 🎯 全局音频管理:停止主音频播放,确保只有一个音频播放 + if (this.$refs.customTabbar && this.$refs.customTabbar.$refs.audioControls) { + const audioControls = this.$refs.customTabbar.$refs.audioControls; + if (audioControls.isPlaying) { + console.log('🔄 停止主音频播放,准备播放单词音频'); + audioControls.pauseAudio(); + } + } // 停止當前播放的單詞語音 if (this.currentWordAudio) { - this.currentWordAudio.destroy(); + console.log('🛑 停止当前播放的音频'); + try { + // 先暂停,再销毁,确保清理完整 + this.currentWordAudio.pause(); + this.currentWordAudio.destroy(); + console.log('✅ 音频已暂停并销毁'); + } catch (error) { + console.log('⚠️ 清理音频时出现异常:', error); + } this.currentWordAudio = null; + this.isWordAudioPlaying = false; + console.log('✅ currentWordAudio 已重置,播放状态已清除'); + } else { + console.log('ℹ️ 没有正在播放的音频需要停止'); + } + + // 🎯 确保音色ID已加载完成后再获取音频 + if (!this.voiceId || this.voiceId === '' || this.voiceId === null || this.voiceId === undefined) { + console.log('⚠️ 音色ID未加载,无法获取单词音频'); + console.log(' voiceId:', this.voiceId); + console.log(' 类型:', typeof this.voiceId); + + uni.showToast({ + title: '音色未加载,请稍后重试', + icon: 'none', + duration: 2000 + }); + return; } + console.log('✅ 单词音频音色ID验证通过:', this.voiceId); + // 調用語音轉換API - console.log('playWordAudio - voiceId值:', this.voiceId, '类型:', typeof this.voiceId); + console.log('🌐 准备调用 textToVoice API'); + console.log('📋 API参数:', { text: word, voiceType: this.voiceId }); + const audioRes = await this.$api.music.textToVoice({ text: word, voiceType: this.voiceId }); - console.log('單詞語音API響應:', audioRes); + console.log('📡 API响应:', audioRes); + console.log('✅ API调用完成'); // 檢查響應並播放音頻 if (audioRes && audioRes.result && audioRes.result.url) { + console.log('🎵 API响应有效,准备播放音频'); // 新格式:使用返回的url字段 const audioUrl = audioRes.result.url; + console.log('🔗 音频URL:', audioUrl); // 創建並播放音頻 + console.log('🎧 创建音频实例'); const audio = uni.createInnerAudioContext(); audio.src = audioUrl; + console.log('✅ 音频实例创建完成,设置URL'); + + // 添加音频加载完成监听器 + audio.onCanplay(() => { + console.log('🎵 音频已加载完成,可以播放'); + }); audio.onPlay(() => { - console.log('開始播放單詞語音:', word); + console.log('🎵 音频开始播放:', word); + console.log('🎧 当前音频实例已激活'); + this.isWordAudioPlaying = true; + console.log('✅ 播放状态已设置为 true'); }); audio.onEnded(() => { - console.log('單詞語音播放完成:', word); + console.log('✅ 音频播放完成:', word); + console.log('🧹 准备清理音频实例'); + this.isWordAudioPlaying = false; + console.log('🔄 播放状态已重置为 false'); + audio.destroy(); if (this.currentWordAudio === audio) { this.currentWordAudio = null; + console.log('🔄 currentWordAudio 已重置为 null'); + } else { + console.log('⚠️ 音频实例不匹配,可能已被替换'); } }); audio.onError((error) => { - console.error('單詞語音播放失敗:', error); - audio.destroy(); + console.error('❌ 音频播放失败:', error); + console.log('🔍 错误详情:', JSON.stringify(error)); + console.log('🧹 准备清理失败的音频实例'); + this.isWordAudioPlaying = false; + console.log('🔄 播放状态已重置为 false (错误)'); + + try { + audio.destroy(); + console.log('✅ 失败的音频实例已销毁'); + } catch (destroyError) { + console.error('⚠️ 销毁音频实例时出错:', destroyError); + } + if (this.currentWordAudio === audio) { this.currentWordAudio = null; + console.log('🔄 currentWordAudio 已重置为 null (错误)'); + } else { + console.log('⚠️ 音频实例不匹配,可能已被替换'); } + uni.showToast({ title: '語音播放失敗', icon: 'none' @@ -651,22 +830,48 @@ export default { }); // 保存當前音頻實例並播放 + console.log('💾 保存音频实例到 currentWordAudio'); this.currentWordAudio = audio; - audio.play(); + + // 添加一个小延迟确保音频实例完全准备好 + console.log('⏱️ 等待音频实例准备...'); + setTimeout(() => { + if (this.currentWordAudio === audio) { + console.log('🚀 开始播放音频'); + try { + audio.play(); + console.log('✅ 音频播放命令已发送'); + } catch (playError) { + console.error('❌ 播放命令失败:', playError); + uni.showToast({ + title: '音频播放失败', + icon: 'none' + }); + } + } else { + console.log('⚠️ 音频实例已被替换,取消播放'); + } + }, 100); } else { - console.error('單詞語音請求失敗:', audioRes); + console.error('❌ API响应无效:', audioRes); + console.log('🔍 响应结构检查:'); + console.log(' audioRes存在:', !!audioRes); + console.log(' audioRes.result存在:', !!(audioRes && audioRes.result)); + console.log(' audioRes.result.url存在:', !!(audioRes && audioRes.result && audioRes.result.url)); uni.showToast({ title: '語音播放失敗', icon: 'none' }); } } catch (error) { - console.error('播放單詞語音異常:', error); + console.error('❌ 播放单词语音异常:', error); + console.log('🔍 异常详情:', error.message || error); uni.showToast({ title: '語音播放失敗', icon: 'none' }); } + console.log('🏁 ===== playWordAudio 方法结束 ====='); }, @@ -685,38 +890,54 @@ export default { // 处理单词点击事件 handleWordClick(word) { + console.log('🎯 ===== 单词点击事件开始 ====='); + console.log('📝 点击的单词:', word); + console.log('🔍 开始查找单词释义...'); + const definition = this.findWordDefinition(word); - console.log('查找单词:', word, '释义:', definition); + console.log('📖 查找结果:', definition ? '找到释义' : '未找到释义'); + console.log('📋 完整释义数据:', definition); if (definition) { + console.log('✅ 找到单词释义,准备设置弹窗数据'); this.currentWordMeaning = { word: definition.word, phonetic: definition.soundmark || '', partOfSpeech: '', // 可以根据需要添加词性 meaning: definition.paraphrase || '', - knowledgeGain: definition.knowledge || '' + knowledgeGain: definition.knowledge || '', + image: definition.image || '' }; + console.log('📋 弹窗数据已设置:', this.currentWordMeaning); // 将单词和解释合并后播放音频 const combinedText = `${word}。${definition.paraphrase || ''}`; - console.log('播放合并文本:', combinedText); + console.log('🎵 准备播放合并文本:', combinedText); this.playWordAudio(combinedText); + console.log('📱 显示单词释义弹窗'); this.showWordMeaning(); } else { - console.log('未找到单词释义:', word); + console.log('⚠️ 未找到单词释义:', word); + console.log('🎵 只播放单词本身'); // 如果没有释义,只播放单词 if (word) { this.playWordAudio(word); + } else { + console.log('❌ 单词为空,无法播放'); } } + console.log('🏁 ===== handleWordClick 方法结束 ====='); }, // 处理中文重点词汇点击事件 handleChineseKeywordClick(keywordData) { - console.log('点击中文重点词汇:', keywordData.word, '释义:', keywordData); + console.log('🎯 ===== 中文重点词汇点击事件开始 ====='); + console.log('📝 点击的词汇数据:', keywordData); + console.log('📖 词汇:', keywordData ? keywordData.word : '无'); if (keywordData) { + console.log('✅ 词汇数据有效,准备设置弹窗数据'); this.currentWordMeaning = { word: keywordData.word, phonetic: keywordData.soundmark || '', @@ -724,16 +945,19 @@ export default { meaning: keywordData.paraphrase || '', knowledgeGain: keywordData.knowledge || '' }; + console.log('📋 弹窗数据已设置:', this.currentWordMeaning); // 将词汇和解释合并后播放音频 const combinedText = `${keywordData.word}。${keywordData.paraphrase || ''}`; - console.log('播放中文合并文本:', combinedText); + console.log('🎵 准备播放中文合并文本:', combinedText); this.playWordAudio(combinedText); + console.log('📱 显示词汇释义弹窗'); this.showWordMeaning(); } else { - console.log('未找到中文词汇释义'); + console.log('❌ 未找到中文词汇释义数据'); } + console.log('🏁 ===== handleChineseKeywordClick 方法结束 ====='); }, // 计算音频总时长 @@ -1745,27 +1969,71 @@ export default { }, // 页面卸载时清理资源 onUnload() { + console.log('📱 页面卸载:开始清理所有音频资源'); + uni.$off('selectVoice') - // 清理單詞語音資源 + + // 1. 清理单词语音资源 if (this.currentWordAudio) { - this.currentWordAudio.destroy(); + console.log('🧹 清理单词音频实例'); + try { + this.currentWordAudio.destroy(); + } catch (error) { + console.error('销毁单词音频实例失败:', error); + } this.currentWordAudio = null; } - // 清理單詞語音緩存 + + // 2. 清理单词语音缓存 this.clearWordAudioCache(); - // 通知AudioControls组件清理资源 + // 3. 停止单词音频播放状态 + this.isWordAudioPlaying = false; + + // 4. 通知AudioControls组件清理资源 if (this.$refs.customTabbar && this.$refs.customTabbar.$refs.audioControls) { + console.log('🧹 通知AudioControls清理音频资源'); this.$refs.customTabbar.$refs.audioControls.destroyAudio(); } + + // 5. 清理全局音频实例(防止遗漏) + try { + // 获取所有可能的音频上下文并销毁 + if (typeof wx !== 'undefined' && wx.getBackgroundAudioManager) { + const bgAudio = wx.getBackgroundAudioManager(); + if (bgAudio) { + bgAudio.stop(); + } + } + } catch (error) { + console.error('清理背景音频失败:', error); + } + + console.log('✅ 页面卸载:音频资源清理完成'); }, // 页面隐藏时暂停音频 onHide() { - // 通知AudioControls组件暂停音频 + console.log('📱 页面隐藏:暂停所有音频播放'); + + // 1. 暂停单词音频 + if (this.currentWordAudio && this.isWordAudioPlaying) { + console.log('⏸️ 暂停单词音频播放'); + try { + this.currentWordAudio.pause(); + this.isWordAudioPlaying = false; + } catch (error) { + console.error('暂停单词音频失败:', error); + } + } + + // 2. 通知AudioControls组件暂停音频 if (this.$refs.customTabbar && this.$refs.customTabbar.$refs.audioControls) { + console.log('⏸️ 通知AudioControls暂停音频'); this.$refs.customTabbar.$refs.audioControls.pauseOnHide(); } + + console.log('✅ 页面隐藏:音频暂停完成'); } } @@ -1837,6 +2105,7 @@ export default { .swiper-item { min-height: 100vh; // background-color: red; + } .content-area { @@ -1850,6 +2119,15 @@ export default { min-height: 100%; box-sizing: border-box; overflow-y: auto; + .title{ + font-family: PingFang SC; + font-weight: 500; + font-size: 34rpx; + text-align: center; + color: #181818; + line-height: 48rpx; + margin-bottom: 32rpx; + } .content-image{ width: 100%; height: auto; @@ -2082,6 +2360,7 @@ export default { background: #fffbe6; /* 柔和的提示背景 */ border: 1px solid #ffe58f; border-radius: 8px; + padding: 10rpx 20rpx; } .text-highlight { diff --git a/subPages/home/components/CustomTabbar.vue b/subPages/home/components/CustomTabbar.vue index ca7739c..9ba4ad7 100644 --- a/subPages/home/components/CustomTabbar.vue +++ b/subPages/home/components/CustomTabbar.vue @@ -11,6 +11,7 @@ :is-member="isMember" :current-page-requires-member="currentPageRequiresMember" :page-pay="pagePay" + :is-word-audio-playing="isWordAudioPlaying" @previous-page="previousPage" @next-page="nextPage" @audio-state-change="onAudioStateChange" @@ -102,6 +103,10 @@ export default { pagePay: { type: Array, default: () => [] + }, + isWordAudioPlaying: { + type: Boolean, + default: false } }, methods: { diff --git a/subPages/home/components/MeaningPopup.vue b/subPages/home/components/MeaningPopup.vue index cddabfb..98a075a 100644 --- a/subPages/home/components/MeaningPopup.vue +++ b/subPages/home/components/MeaningPopup.vue @@ -21,7 +21,7 @@ v-if="currentWordMeaning.image" class="meaning-image" :src="currentWordMeaning.image" - mode="aspectFill" + mode="aspectFit" />