|
|
|
@ -224,6 +224,7 @@ |
|
|
|
export default { |
|
|
|
data() { |
|
|
|
return { |
|
|
|
voiceId: '', |
|
|
|
courseId: '', |
|
|
|
showNavbar: true, |
|
|
|
currentPage: 1, |
|
|
|
@ -233,10 +234,17 @@ export default { |
|
|
|
// 音频控制相关数据 |
|
|
|
isPlaying: false, |
|
|
|
currentTime: 0, |
|
|
|
totalTime: 317, // 5:17 = 317秒 |
|
|
|
totalTime: 0, |
|
|
|
isLoop: false, |
|
|
|
playSpeed: 1.0, |
|
|
|
speedOptions: [1.0, 1.25, 1.5, 2.0], |
|
|
|
// 音频数组管理 |
|
|
|
currentPageAudios: [], // 当前页面的音频数组 |
|
|
|
currentAudioIndex: 0, // 当前播放的音频索引 |
|
|
|
audioContext: null, // 音频上下文 |
|
|
|
currentAudio: null, // 当前音频实例 |
|
|
|
// 音频缓存管理 |
|
|
|
audioCache: {}, // 页面音频缓存 {pageIndex: {audios: [], totalDuration: 0}} |
|
|
|
courseIdList: [], |
|
|
|
bookTitle: '', |
|
|
|
courseList: [ |
|
|
|
@ -271,6 +279,114 @@ export default { |
|
|
|
} |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
// 获取当前页面的音频内容 |
|
|
|
async getCurrentPageAudio() { |
|
|
|
// 检查缓存中是否已有当前页面的音频数据 |
|
|
|
const cacheKey = `${this.courseId}_${this.currentPage}`; |
|
|
|
if (this.audioCache[cacheKey]) { |
|
|
|
console.log('从缓存加载音频数据:', cacheKey); |
|
|
|
// 从缓存加载音频数据 |
|
|
|
this.currentPageAudios = this.audioCache[cacheKey].audios; |
|
|
|
this.totalTime = this.audioCache[cacheKey].totalDuration; |
|
|
|
this.currentAudioIndex = 0; |
|
|
|
this.isPlaying = false; |
|
|
|
this.currentTime = 0; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 清空当前页面音频数组 |
|
|
|
this.currentPageAudios = []; |
|
|
|
this.currentAudioIndex = 0; |
|
|
|
this.isPlaying = false; |
|
|
|
this.currentTime = 0; |
|
|
|
this.totalTime = 0; |
|
|
|
|
|
|
|
// 对着当前页面的每一个[]元素进行切割 如果是文本text类型则进行音频请求 |
|
|
|
const currentPageData = this.bookPages[this.currentPage - 1]; |
|
|
|
if (currentPageData) { |
|
|
|
// 收集所有text类型的元素 |
|
|
|
const textItems = currentPageData.filter(item => item.type === 'text'); |
|
|
|
|
|
|
|
if (textItems.length > 0) { |
|
|
|
// 并行发送所有音频请求 |
|
|
|
const audioPromises = textItems.map(async (item, index) => { |
|
|
|
try { |
|
|
|
// 进行音频请求 - 修正字段名:使用content而不是text |
|
|
|
const radioRes = await this.$api.music.textToVoice({ |
|
|
|
text: item.content, |
|
|
|
voiceId: this.voiceId, |
|
|
|
}); |
|
|
|
console.log(`音频请求响应 ${index + 1}:`, radioRes); |
|
|
|
|
|
|
|
if(radioRes.code === 200){ |
|
|
|
// API返回的是Base64编码的WAV音频数据,在uniapp环境中需要特殊处理 |
|
|
|
const base64Data = radioRes.result; |
|
|
|
// 在uniapp中,可以直接使用base64数据作为音频源 |
|
|
|
const audioUrl = `data:audio/wav;base64,${base64Data}`; |
|
|
|
|
|
|
|
// 同时保存到原始数据中以保持兼容性 |
|
|
|
item.audioUrl = audioUrl; |
|
|
|
console.log(`音频URL设置成功 ${index + 1}:`, audioUrl); |
|
|
|
|
|
|
|
return { |
|
|
|
url: audioUrl, |
|
|
|
text: item.content, |
|
|
|
duration: 0, // 音频时长,需要在加载后获取 |
|
|
|
index: index // 保持原始顺序 |
|
|
|
}; |
|
|
|
} else { |
|
|
|
console.error(`音频请求失败 ${index + 1}:`, radioRes); |
|
|
|
return null; |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error(`音频请求异常 ${index + 1}:`, error); |
|
|
|
return null; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 等待所有音频请求完成 |
|
|
|
const audioResults = await Promise.all(audioPromises); |
|
|
|
|
|
|
|
// 按原始顺序添加到音频数组中,过滤掉失败的请求 |
|
|
|
this.currentPageAudios = audioResults |
|
|
|
.filter(result => result !== null) |
|
|
|
.sort((a, b) => a.index - b.index) |
|
|
|
.map(result => ({ |
|
|
|
url: result.url, |
|
|
|
text: result.text, |
|
|
|
duration: result.duration |
|
|
|
})); |
|
|
|
|
|
|
|
console.log('所有音频请求完成,共获取', this.currentPageAudios.length, '个音频'); |
|
|
|
} |
|
|
|
|
|
|
|
// 如果有音频,计算总时长 |
|
|
|
if (this.currentPageAudios.length > 0) { |
|
|
|
await this.calculateTotalDuration(); |
|
|
|
|
|
|
|
// 将音频数据保存到缓存中 |
|
|
|
const cacheKey = `${this.courseId}_${this.currentPage}`; |
|
|
|
this.audioCache[cacheKey] = { |
|
|
|
audios: [...this.currentPageAudios], // 深拷贝音频数组 |
|
|
|
totalDuration: this.totalTime |
|
|
|
}; |
|
|
|
console.log('音频数据已缓存:', cacheKey, this.audioCache[cacheKey]); |
|
|
|
|
|
|
|
// 限制缓存大小 |
|
|
|
this.limitCacheSize(10); |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
// 获取音色列表 拿第一个做默认的音色id |
|
|
|
async getVoiceList() { |
|
|
|
const voiceRes = await this.$api.music.list() |
|
|
|
if(voiceRes.code === 200){ |
|
|
|
this.voiceId = voiceRes.result.id |
|
|
|
console.log('111'); |
|
|
|
|
|
|
|
await this.getCurrentPageAudio() |
|
|
|
} |
|
|
|
}, |
|
|
|
toggleNavbar() { |
|
|
|
this.showNavbar = !this.showNavbar |
|
|
|
}, |
|
|
|
@ -310,35 +426,233 @@ export default { |
|
|
|
} |
|
|
|
this.currentWordMeaning = null |
|
|
|
}, |
|
|
|
// 计算音频总时长 |
|
|
|
async calculateTotalDuration() { |
|
|
|
let totalDuration = 0; |
|
|
|
for (let i = 0; i < this.currentPageAudios.length; i++) { |
|
|
|
const audio = this.currentPageAudios[i]; |
|
|
|
try { |
|
|
|
const duration = await this.getAudioDuration(audio.url); |
|
|
|
audio.duration = duration; |
|
|
|
totalDuration += duration; |
|
|
|
} catch (error) { |
|
|
|
console.error('获取音频时长失败:', error); |
|
|
|
// 如果无法获取时长,估算一个默认值(假设每100字符约5秒) |
|
|
|
const estimatedDuration = Math.max(5, audio.text.length / 20); |
|
|
|
audio.duration = estimatedDuration; |
|
|
|
totalDuration += estimatedDuration; |
|
|
|
} |
|
|
|
} |
|
|
|
this.totalTime = totalDuration; |
|
|
|
console.log('音频总时长:', totalDuration, '秒'); |
|
|
|
}, |
|
|
|
|
|
|
|
// 获取音频时长 |
|
|
|
getAudioDuration(audioUrl) { |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
const audio = uni.createInnerAudioContext(); |
|
|
|
audio.src = audioUrl; |
|
|
|
|
|
|
|
audio.onCanplay(() => { |
|
|
|
resolve(audio.duration || 5); // 如果无法获取时长,默认5秒 |
|
|
|
audio.destroy(); |
|
|
|
}); |
|
|
|
|
|
|
|
audio.onError((error) => { |
|
|
|
console.error('音频加载失败:', error); |
|
|
|
reject(error); |
|
|
|
audio.destroy(); |
|
|
|
}); |
|
|
|
|
|
|
|
// 设置超时 |
|
|
|
setTimeout(() => { |
|
|
|
reject(new Error('获取音频时长超时')); |
|
|
|
audio.destroy(); |
|
|
|
}, 3000); |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
// 音频控制方法 |
|
|
|
togglePlay() { |
|
|
|
this.isPlaying = !this.isPlaying; |
|
|
|
// 这里可以添加实际的音频播放/暂停逻辑 |
|
|
|
if (this.currentPageAudios.length === 0) { |
|
|
|
uni.showToast({ |
|
|
|
title: '当前页面没有音频内容', |
|
|
|
icon: 'none' |
|
|
|
}); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (this.isPlaying) { |
|
|
|
this.pauseAudio(); |
|
|
|
} else { |
|
|
|
this.playAudio(); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 播放音频 |
|
|
|
playAudio() { |
|
|
|
if (this.currentPageAudios.length === 0) return; |
|
|
|
|
|
|
|
// 如果没有当前音频实例或者需要切换音频 |
|
|
|
if (!this.currentAudio || this.currentAudio.src !== this.currentPageAudios[this.currentAudioIndex].url) { |
|
|
|
this.createAudioInstance(); |
|
|
|
} |
|
|
|
|
|
|
|
this.currentAudio.play(); |
|
|
|
this.isPlaying = true; |
|
|
|
}, |
|
|
|
|
|
|
|
// 暂停音频 |
|
|
|
pauseAudio() { |
|
|
|
if (this.currentAudio) { |
|
|
|
this.currentAudio.pause(); |
|
|
|
} |
|
|
|
this.isPlaying = false; |
|
|
|
}, |
|
|
|
|
|
|
|
// 创建音频实例 |
|
|
|
createAudioInstance() { |
|
|
|
// 销毁之前的音频实例 |
|
|
|
if (this.currentAudio) { |
|
|
|
this.currentAudio.destroy(); |
|
|
|
} |
|
|
|
|
|
|
|
const audio = uni.createInnerAudioContext(); |
|
|
|
audio.src = this.currentPageAudios[this.currentAudioIndex].url; |
|
|
|
audio.playbackRate = this.playSpeed; |
|
|
|
|
|
|
|
// 音频事件监听 |
|
|
|
audio.onPlay(() => { |
|
|
|
console.log('音频开始播放'); |
|
|
|
this.isPlaying = true; |
|
|
|
}); |
|
|
|
|
|
|
|
audio.onPause(() => { |
|
|
|
console.log('音频暂停'); |
|
|
|
this.isPlaying = false; |
|
|
|
}); |
|
|
|
|
|
|
|
audio.onTimeUpdate(() => { |
|
|
|
this.updateCurrentTime(); |
|
|
|
}); |
|
|
|
|
|
|
|
audio.onEnded(() => { |
|
|
|
console.log('当前音频播放结束'); |
|
|
|
this.onAudioEnded(); |
|
|
|
}); |
|
|
|
|
|
|
|
audio.onError((error) => { |
|
|
|
console.error('音频播放错误:', error); |
|
|
|
this.isPlaying = false; |
|
|
|
uni.showToast({ |
|
|
|
title: '音频播放失败', |
|
|
|
icon: 'none' |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
this.currentAudio = audio; |
|
|
|
}, |
|
|
|
|
|
|
|
// 更新当前播放时间 |
|
|
|
updateCurrentTime() { |
|
|
|
if (!this.currentAudio) return; |
|
|
|
|
|
|
|
let totalTime = 0; |
|
|
|
// 计算之前音频的总时长 |
|
|
|
for (let i = 0; i < this.currentAudioIndex; i++) { |
|
|
|
totalTime += this.currentPageAudios[i].duration; |
|
|
|
} |
|
|
|
// 加上当前音频的播放时间 |
|
|
|
totalTime += this.currentAudio.currentTime; |
|
|
|
|
|
|
|
this.currentTime = totalTime; |
|
|
|
}, |
|
|
|
|
|
|
|
// 音频播放结束处理 |
|
|
|
onAudioEnded() { |
|
|
|
if (this.currentAudioIndex < this.currentPageAudios.length - 1) { |
|
|
|
// 播放下一个音频 |
|
|
|
this.currentAudioIndex++; |
|
|
|
this.playAudio(); |
|
|
|
} else { |
|
|
|
// 所有音频播放完毕 |
|
|
|
if (this.isLoop) { |
|
|
|
// 循环播放 |
|
|
|
this.currentAudioIndex = 0; |
|
|
|
this.playAudio(); |
|
|
|
} else { |
|
|
|
// 停止播放 |
|
|
|
this.isPlaying = false; |
|
|
|
this.currentTime = this.totalTime; |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
toggleLoop() { |
|
|
|
this.isLoop = !this.isLoop; |
|
|
|
}, |
|
|
|
|
|
|
|
toggleSpeed() { |
|
|
|
const currentIndex = this.speedOptions.indexOf(this.playSpeed); |
|
|
|
const nextIndex = (currentIndex + 1) % this.speedOptions.length; |
|
|
|
this.playSpeed = this.speedOptions[nextIndex]; |
|
|
|
|
|
|
|
// 如果当前有音频在播放,更新播放速度 |
|
|
|
if (this.currentAudio) { |
|
|
|
this.currentAudio.playbackRate = this.playSpeed; |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 上一个音频 |
|
|
|
previousAudio() { |
|
|
|
if (this.currentPageAudios.length === 0) return; |
|
|
|
|
|
|
|
if (this.currentAudioIndex > 0) { |
|
|
|
this.currentAudioIndex--; |
|
|
|
if (this.isPlaying) { |
|
|
|
this.playAudio(); |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 下一个音频 |
|
|
|
nextAudio() { |
|
|
|
if (this.currentPageAudios.length === 0) return; |
|
|
|
|
|
|
|
if (this.currentAudioIndex < this.currentPageAudios.length - 1) { |
|
|
|
this.currentAudioIndex++; |
|
|
|
if (this.isPlaying) { |
|
|
|
this.playAudio(); |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
async previousPage() { |
|
|
|
if (this.currentPage > 1) { |
|
|
|
// 停止当前音频播放 |
|
|
|
this.pauseAudio(); |
|
|
|
|
|
|
|
this.currentPage--; |
|
|
|
// 获取对应页面的数据(如果还没有获取过) |
|
|
|
if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) { |
|
|
|
await this.getBookPages(this.courseIdList[this.currentPage - 1]); |
|
|
|
} |
|
|
|
|
|
|
|
// 重新获取当前页面的音频 |
|
|
|
await this.getCurrentPageAudio(); |
|
|
|
} |
|
|
|
}, |
|
|
|
async nextPage() { |
|
|
|
if (this.currentPage < this.bookPages.length) { |
|
|
|
// 停止当前音频播放 |
|
|
|
this.pauseAudio(); |
|
|
|
|
|
|
|
this.currentPage++; |
|
|
|
// 获取对应页面的数据(如果还没有获取过) |
|
|
|
if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) { |
|
|
|
await this.getBookPages(this.courseIdList[this.currentPage - 1]); |
|
|
|
} |
|
|
|
|
|
|
|
// 重新获取当前页面的音频 |
|
|
|
await this.getCurrentPageAudio(); |
|
|
|
} |
|
|
|
}, |
|
|
|
formatTime(seconds) { |
|
|
|
@ -360,19 +674,31 @@ export default { |
|
|
|
}) |
|
|
|
}, |
|
|
|
async goToPage(page) { |
|
|
|
// 停止当前音频播放 |
|
|
|
this.pauseAudio(); |
|
|
|
|
|
|
|
this.currentPage = page |
|
|
|
console.log('跳转到页面:', page) |
|
|
|
// 获取对应页面的数据(如果还没有获取过) |
|
|
|
if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) { |
|
|
|
await this.getBookPages(this.courseIdList[this.currentPage - 1]); |
|
|
|
} |
|
|
|
|
|
|
|
// 重新获取当前页面的音频 |
|
|
|
await this.getCurrentPageAudio(); |
|
|
|
}, |
|
|
|
async onSwiperChange(e) { |
|
|
|
// 停止当前音频播放 |
|
|
|
this.pauseAudio(); |
|
|
|
|
|
|
|
this.currentPage = e.detail.current + 1 |
|
|
|
// 获取对应页面的数据(如果还没有获取过) |
|
|
|
if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) { |
|
|
|
await this.getBookPages(this.courseIdList[this.currentPage - 1]); |
|
|
|
} |
|
|
|
|
|
|
|
// 重新获取当前页面的音频 |
|
|
|
await this.getCurrentPageAudio(); |
|
|
|
}, |
|
|
|
async getCourseList(id) { |
|
|
|
const res = await this.$api.book.coursePage({ |
|
|
|
@ -423,7 +749,43 @@ export default { |
|
|
|
this.courseId = args.courseId |
|
|
|
// 先获取点进来的课程的页面列表 |
|
|
|
await Promise.all([this.getCourseList(this.courseId), this.getCoursePageList(args.bookId)]) |
|
|
|
await this.getVoiceList() |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
// 页面卸载时清理音频资源 |
|
|
|
onUnload() { |
|
|
|
if (this.currentAudio) { |
|
|
|
this.currentAudio.destroy(); |
|
|
|
this.currentAudio = null; |
|
|
|
} |
|
|
|
this.isPlaying = false; |
|
|
|
// 清理音频缓存 |
|
|
|
this.clearAudioCache(); |
|
|
|
}, |
|
|
|
|
|
|
|
// 页面隐藏时暂停音频 |
|
|
|
onHide() { |
|
|
|
this.pauseAudio(); |
|
|
|
}, |
|
|
|
|
|
|
|
// 清理音频缓存 |
|
|
|
clearAudioCache() { |
|
|
|
this.audioCache = {}; |
|
|
|
console.log('音频缓存已清理'); |
|
|
|
}, |
|
|
|
|
|
|
|
// 限制缓存大小,保留最近访问的页面 |
|
|
|
limitCacheSize(maxSize = 10) { |
|
|
|
const cacheKeys = Object.keys(this.audioCache); |
|
|
|
if (cacheKeys.length > maxSize) { |
|
|
|
// 删除最旧的缓存项 |
|
|
|
const keysToDelete = cacheKeys.slice(0, cacheKeys.length - maxSize); |
|
|
|
keysToDelete.forEach(key => { |
|
|
|
delete this.audioCache[key]; |
|
|
|
}); |
|
|
|
console.log('缓存大小已限制,删除了', keysToDelete.length, '个缓存项'); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
</script> |
|
|
|
|