diff --git a/subPages/home/AudioControls.vue b/subPages/home/AudioControls.vue
index f60c24f..fa8ed28 100644
--- a/subPages/home/AudioControls.vue
+++ b/subPages/home/AudioControls.vue
@@ -1,3366 +1,3104 @@
-
-
-
-
-
-
-
-
-
- 第{{currentPage}}页音频加载中,请稍等...
-
-
-
-
-
-
-
- 正在加载更多音频...
-
-
-
- {{ formatTime(currentTime) }}
-
-
-
- {{ formatTime(totalTime) }}
-
-
-
-
-
- 循环
-
-
-
- 上一页
+
+
+
+
-
-
-
-
-
-
- 下一页
+
+
+
+
+ 第{{ currentPage }}页音频加载中,请稍等...
-
-
-
- {{ playbackRateSupported ? playSpeed + 'x' : '不支持' }}
-
+
+
+
+
+
+
+ 正在加载更多音频...
+
+
+
+ {{ formatTime(currentTime) }}
+
+
+
+ {{ formatTime(totalTime) }}
+
+
+
+
+
+ 循环
+
+
+
+ 上一页
+
+
+
+
+
+
+
+ 下一页
+
+
+
+
+ {{ playbackRateSupported ? playSpeed + 'x' : '不支持' }}
+
+
+
-
-
\ No newline at end of file
diff --git a/subPages/home/book.vue b/subPages/home/book.vue
index 473d00b..35d445a 100644
--- a/subPages/home/book.vue
+++ b/subPages/home/book.vue
@@ -1,63 +1,49 @@
-
-
-
-
-
-
-
-
-
-
-
-
- {{ currentPageTitle }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ currentPageTitle }}
-
-
-
-
-
-
-
-
-
- {{ currentPageTitle }}
-
-
- {{ pageTitles[index] }}
-
- 升级会员解锁
-
-
-
-
-
-
- 划线重点
-
-
-
-
+
+
+
+
+
+
+
+
+ {{ currentPageTitle }}
+
+
+ {{ pageTitles[index] }}
+
+ 升级会员解锁
+
+
+
+
+
+
+
+ 划线重点
+
+
+
+
-
-
-
- {{ item.content }}
-
- {{ item.content }}
-
-
-
-
-
-
-
-
-
-
-
-
- 视频加载中...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ -->
+
+
+ {{
+ segment.text }}
+
+
+
+
+
+
+
+
+
+
+ {{ item.content }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 视频加载中...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/subPages/home/components/MeaningPopup.vue b/subPages/home/components/MeaningPopup.vue
index 98a075a..9ac4414 100644
--- a/subPages/home/components/MeaningPopup.vue
+++ b/subPages/home/components/MeaningPopup.vue
@@ -1,225 +1,220 @@
-
-
+
\ No newline at end of file
diff --git a/utils/audioManager.js b/utils/audioManager.js
new file mode 100644
index 0000000..44d718a
--- /dev/null
+++ b/utils/audioManager.js
@@ -0,0 +1,524 @@
+/**
+ * 统一音频管理器
+ * 实现单实例音频管理,避免多个音频同时播放造成的冲突
+ */
+class AudioManager {
+ constructor() {
+ // 当前音频实例
+ this.currentAudio = null;
+ // 音频类型:'sentence' | 'word'
+ this.currentAudioType = null;
+ // 音频状态
+ this.isPlaying = false;
+ // 复用的音频实例池
+ this.audioInstance = null;
+ // 事件监听器
+ this.listeners = {
+ play: [],
+ pause: [],
+ ended: [],
+ error: [],
+ timeupdate: [],
+ canplay: []
+ };
+ // 播放速度支持检测
+ this.playbackRateSupported = true;
+
+ // 统一音频配置
+ this.globalPlaybackRate = 1.0; // 全局播放速度
+ this.globalVoiceId = ''; // 全局音色ID
+ this.speedOptions = [0.5, 0.8, 1.0, 1.25, 1.5, 2.0]; // 支持的播放速度选项
+ }
+
+ /**
+ * 创建HTML5 Audio实例并包装为uni-app兼容接口
+ */
+ createHTML5Audio() {
+ const audio = new Audio();
+
+ // 包装为uni-app兼容的接口
+ const wrappedAudio = {
+ // 原生HTML5 Audio实例
+ _nativeAudio: audio,
+
+ // 基本属性
+ get src() { return audio.src; },
+ set src(value) { audio.src = value; },
+
+ get duration() { return audio.duration || 0; },
+ get currentTime() { return audio.currentTime || 0; },
+
+ get paused() { return audio.paused; },
+
+ // 支持倍速的关键属性
+ get playbackRate() { return audio.playbackRate; },
+ set playbackRate(value) {
+ try {
+ audio.playbackRate = value;
+ console.log(`🎵 HTML5 Audio倍速设置成功: ${value}x`);
+ } catch (error) {
+ console.error('❌ HTML5 Audio倍速设置失败:', error);
+ }
+ },
+
+ // 基本方法
+ play() {
+ return audio.play().catch(error => {
+ console.error('HTML5 Audio播放失败:', error);
+ });
+ },
+
+ pause() {
+ audio.pause();
+ },
+
+ stop() {
+ audio.pause();
+ audio.currentTime = 0;
+ },
+
+ seek(time) {
+ audio.currentTime = time;
+ },
+
+ destroy() {
+ audio.pause();
+ audio.src = '';
+ audio.load();
+ },
+
+ // 事件绑定方法
+ onCanplay(callback) {
+ audio.addEventListener('canplay', callback);
+ },
+
+ onPlay(callback) {
+ audio.addEventListener('play', callback);
+ },
+
+ onPause(callback) {
+ audio.addEventListener('pause', callback);
+ },
+
+ onEnded(callback) {
+ audio.addEventListener('ended', callback);
+ },
+
+ onTimeUpdate(callback) {
+ audio.addEventListener('timeupdate', callback);
+ },
+
+ onError(callback) {
+ // 包装错误事件,过滤掉非关键错误
+ const wrappedCallback = (error) => {
+ // 只在有src且音频正在播放时才传递错误事件
+ if (audio.src && audio.src.trim() !== '' && !audio.paused) {
+ callback(error);
+ } else {
+ console.log('🔇 HTML5 Audio错误(已忽略):', {
+ hasSrc: !!audio.src,
+ paused: audio.paused,
+ errorType: error.type || 'unknown'
+ });
+ }
+ };
+ audio.addEventListener('error', wrappedCallback);
+ },
+
+ // 移除事件监听
+ offCanplay(callback) {
+ audio.removeEventListener('canplay', callback);
+ },
+
+ offPlay(callback) {
+ audio.removeEventListener('play', callback);
+ },
+
+ offPause(callback) {
+ audio.removeEventListener('pause', callback);
+ },
+
+ offEnded(callback) {
+ audio.removeEventListener('ended', callback);
+ },
+
+ offTimeUpdate(callback) {
+ audio.removeEventListener('timeupdate', callback);
+ },
+
+ offError(callback) {
+ audio.removeEventListener('error', callback);
+ }
+ };
+
+ return wrappedAudio;
+ }
+
+ /**
+ * 创建音频实例
+ * 优先使用HTML5 Audio(支持倍速),降级到uni.createInnerAudioContext
+ * 复用已存在的音频实例以提高性能
+ */
+ createAudioInstance() {
+ // 如果已有音频实例,直接复用
+ if (this.audioInstance) {
+ console.log('🔄 复用现有音频实例');
+ return this.audioInstance;
+ }
+
+ // 在H5环境下优先使用HTML5 Audio
+ // #ifdef H5
+ try {
+ this.audioInstance = this.createHTML5Audio();
+ console.log('🎵 创建新的HTML5 Audio实例');
+ } catch (error) {
+ console.warn('⚠️ HTML5 Audio创建失败,降级到uni音频:', error);
+ audio = uni.createInnerAudioContext();
+ this.playbackRateSupported = false;
+ }
+ // #endif
+
+ // 在非H5环境下使用uni音频
+ // #ifndef H5
+ this.audioInstance = uni.createInnerAudioContext();
+ this.playbackRateSupported = false;
+ console.log('🎵 创建新的uni音频实例');
+ // #endif
+
+ return this.audioInstance;
+ }
+
+ /**
+ * 停止当前音频
+ */
+ stopCurrentAudio() {
+ if (this.currentAudio) {
+ console.log(`🛑 停止当前音频 (类型: ${this.currentAudioType})`);
+
+ try {
+ this.currentAudio.pause();
+ // 不销毁音频实例,保留以供复用
+ // this.currentAudio.destroy();
+ } catch (error) {
+ console.error('⚠️ 停止音频时出错:', error);
+ }
+
+ this.currentAudio = null;
+ this.currentAudioType = null;
+ this.isPlaying = false;
+
+ // 触发暂停事件
+ this.emit('pause');
+ }
+ }
+
+ /**
+ * 播放音频
+ * @param {string} audioUrl - 音频URL
+ * @param {string} audioType - 音频类型 ('sentence' | 'word')
+ * @param {Object} options - 播放选项
+ */
+ async playAudio(audioUrl, audioType = 'word', options = {}) {
+ try {
+ console.log(`🎵 开始播放${audioType}音频:`, audioUrl);
+
+ // 停止当前播放的音频
+ this.stopCurrentAudio();
+
+ // 创建新的音频实例
+ const audio = this.createAudioInstance();
+ audio.src = audioUrl;
+
+ // 设置播放选项,优先使用全局播放速度
+ const playbackRate = options.playbackRate || this.globalPlaybackRate;
+ if (playbackRate && this.playbackRateSupported) {
+ audio.playbackRate = playbackRate;
+ console.log(`🎵 设置音频播放速度: ${playbackRate}x`);
+ }
+
+ // 绑定事件监听器
+ this.bindAudioEvents(audio, audioType);
+
+ // 保存当前音频实例
+ this.currentAudio = audio;
+ this.currentAudioType = audioType;
+
+ // 延迟播放,确保音频实例完全准备好
+ setTimeout(() => {
+ if (this.currentAudio === audio) {
+ try {
+ audio.play();
+ console.log(`✅ ${audioType}音频开始播放`);
+ } catch (playError) {
+ console.error('❌ 播放命令失败:', playError);
+ this.emit('error', playError);
+ }
+ }
+ }, 100);
+
+ return audio;
+ } catch (error) {
+ console.error('❌ 播放音频异常:', error);
+ this.emit('error', error);
+ throw error;
+ }
+ }
+
+ /**
+ * 绑定音频事件监听器
+ */
+ bindAudioEvents(audio, audioType) {
+ // 播放开始
+ audio.onPlay(() => {
+ console.log(`🎵 ${audioType}音频播放开始`);
+ this.isPlaying = true;
+ this.emit('play', { audioType, audio });
+ });
+
+ // 播放暂停
+ audio.onPause(() => {
+ console.log(`⏸️ ${audioType}音频播放暂停`);
+ this.isPlaying = false;
+ this.emit('pause', { audioType, audio });
+ });
+
+ // 播放结束
+ audio.onEnded(() => {
+ console.log(`🏁 ${audioType}音频播放结束`);
+ this.isPlaying = false;
+
+ // 不销毁音频实例,保留以供复用
+ // 只清理当前播放状态
+ if (this.currentAudio === audio) {
+ this.currentAudio = null;
+ this.currentAudioType = null;
+ }
+
+ this.emit('ended', { audioType, audio });
+ });
+
+ // 播放错误
+ audio.onError((error) => {
+ console.error(`❌ ${audioType}音频播放失败:`, error);
+ this.isPlaying = false;
+
+ // 发生错误时才销毁音频实例
+ try {
+ audio.destroy();
+ } catch (destroyError) {
+ console.error('⚠️ 销毁音频实例时出错:', destroyError);
+ }
+
+ if (this.currentAudio === audio) {
+ this.currentAudio = null;
+ this.currentAudioType = null;
+ }
+
+ // 清理复用的音频实例引用
+ if (this.audioInstance === audio) {
+ this.audioInstance = null;
+ }
+
+ this.emit('error', { error, audioType, audio });
+ });
+
+ // 时间更新
+ audio.onTimeUpdate(() => {
+ this.emit('timeupdate', {
+ currentTime: audio.currentTime,
+ duration: audio.duration,
+ audioType,
+ audio
+ });
+ });
+
+ // 可以播放
+ audio.onCanplay(() => {
+ this.emit('canplay', { audioType, audio });
+ });
+ }
+
+ /**
+ * 暂停当前音频
+ */
+ pause() {
+ if (this.currentAudio && this.isPlaying) {
+ this.currentAudio.pause();
+ }
+ }
+
+ /**
+ * 恢复播放
+ */
+ resume() {
+ if (this.currentAudio && !this.isPlaying) {
+ this.currentAudio.play();
+ }
+ }
+
+ /**
+ * 设置播放速度
+ */
+ setPlaybackRate(rate) {
+ if (this.currentAudio && this.playbackRateSupported) {
+ this.currentAudio.playbackRate = rate;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 设置全局播放速度
+ * @param {number} rate - 播放速度 (0.5 - 2.0)
+ */
+ setGlobalPlaybackRate(rate) {
+ if (rate < 0.5 || rate > 2.0) {
+ console.warn('⚠️ 播放速度超出支持范围 (0.5-2.0):', rate);
+ return false;
+ }
+
+ this.globalPlaybackRate = rate;
+ console.log(`🎵 设置全局播放速度: ${rate}x`);
+
+ // 如果当前有音频在播放,立即应用新的播放速度
+ if (this.currentAudio && this.playbackRateSupported) {
+ try {
+ this.currentAudio.playbackRate = rate;
+ console.log(`✅ 当前音频播放速度已更新: ${rate}x`);
+ } catch (error) {
+ console.error('❌ 更新当前音频播放速度失败:', error);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 获取全局播放速度
+ */
+ getGlobalPlaybackRate() {
+ return this.globalPlaybackRate;
+ }
+
+ /**
+ * 设置全局音色ID
+ * @param {string|number} voiceId - 音色ID
+ */
+ setGlobalVoiceId(voiceId) {
+ this.globalVoiceId = String(voiceId);
+ console.log(`🎵 设置全局音色ID: ${this.globalVoiceId}`);
+ }
+
+ /**
+ * 获取全局音色ID
+ */
+ getGlobalVoiceId() {
+ return this.globalVoiceId;
+ }
+
+ /**
+ * 获取支持的播放速度选项
+ */
+ getSpeedOptions() {
+ return [...this.speedOptions];
+ }
+
+ /**
+ * 切换到下一个播放速度
+ */
+ togglePlaybackRate() {
+ const currentIndex = this.speedOptions.indexOf(this.globalPlaybackRate);
+ const nextIndex = (currentIndex + 1) % this.speedOptions.length;
+ const nextRate = this.speedOptions[nextIndex];
+
+ this.setGlobalPlaybackRate(nextRate);
+ return nextRate;
+ }
+
+ /**
+ * 获取当前播放状态
+ */
+ getPlaybackState() {
+ return {
+ isPlaying: this.isPlaying,
+ currentAudioType: this.currentAudioType,
+ hasAudio: !!this.currentAudio,
+ playbackRateSupported: this.playbackRateSupported,
+ currentTime: this.currentAudio ? this.currentAudio.currentTime : 0,
+ duration: this.currentAudio ? this.currentAudio.duration : 0
+ };
+ }
+
+ /**
+ * 添加事件监听器
+ */
+ on(event, callback) {
+ if (this.listeners[event]) {
+ // 检查是否已经绑定过相同的回调函数,避免重复绑定
+ if (this.listeners[event].indexOf(callback) === -1) {
+ this.listeners[event].push(callback);
+ console.log(`🎵 添加事件监听器: ${event}, 当前监听器数量: ${this.listeners[event].length}`);
+ } else {
+ console.warn(`⚠️ 事件监听器已存在,跳过重复绑定: ${event}`);
+ }
+ }
+ }
+
+ /**
+ * 移除事件监听器
+ */
+ off(event, callback) {
+ if (this.listeners[event]) {
+ const index = this.listeners[event].indexOf(callback);
+ if (index > -1) {
+ this.listeners[event].splice(index, 1);
+ }
+ }
+ }
+
+ /**
+ * 触发事件
+ */
+ emit(event, data) {
+ if (this.listeners[event]) {
+ this.listeners[event].forEach(callback => {
+ try {
+ callback(data);
+ } catch (error) {
+ console.error(`事件监听器执行错误 (${event}):`, error);
+ }
+ });
+ }
+ }
+
+ /**
+ * 销毁音频管理器
+ */
+ destroy() {
+ this.stopCurrentAudio();
+
+ // 销毁复用的音频实例
+ // if (this.audioInstance) {
+ // try {
+ // this.audioInstance.destroy();
+ // } catch (error) {
+ // console.error('⚠️ 销毁音频实例时出错:', error);
+ // }
+ // this.audioInstance = null;
+ // }
+
+ this.listeners = {
+ play: [],
+ pause: [],
+ ended: [],
+ error: [],
+ timeupdate: [],
+ canplay: []
+ };
+ }
+}
+
+// 创建全局单例
+const audioManager = new AudioManager();
+
+export default audioManager;
\ No newline at end of file