[lisa-feat]feat: 调整标准语音播放

This commit is contained in:
chenlisha02
2025-10-15 12:27:43 +08:00
parent 4465afc7ba
commit 09edeaa755
7 changed files with 161 additions and 20 deletions

View File

@@ -7,6 +7,7 @@
"enablePullDownRefresh": false,
"onReachBottomDistance": 50,
"usingComponents": {
"t-slider": "tdesign-miniprogram/slider/slider",
"t-icon": "tdesign-miniprogram/icon/icon"
}
}

View File

@@ -28,6 +28,9 @@ interface IPageData {
}>
standardAudioMap: { [key: string]: string } // 标准语音文件ID映射
isPlaying: boolean // 是否正在播放音频
audioDuration: number // 音频总时长
currentTime: number // 当前播放时间
sliderValue: number // 进度条值0-100
}
type IPageMethods = {
@@ -43,6 +46,9 @@ type IPageMethods = {
handleImagePreview: () => void
getStandardVoice: (sentenceId: string) => Promise<void>
playStandardVoice: () => void
resetAudioState: () => void
handleSliderChange: (e: any) => void
formatTime: (seconds: number) => string
}
interface IPageInstance extends IPageMethods {
@@ -72,7 +78,10 @@ Page<IPageData, IPageInstance>({
isScoreExpanded: false, // 评分区域是否展开
wordScores: [], // 单词评分数组
standardAudioMap: {}, // 标准语音文件ID映射
isPlaying: false // 是否正在播放音频
isPlaying: false, // 是否正在播放音频
audioDuration: 0, // 音频总时长
currentTime: 0, // 当前播放时间
sliderValue: 0 // 进度条值0-100
},
// 切换评分区域展开状态
@@ -100,10 +109,26 @@ Page<IPageData, IPageInstance>({
this.setData({ circleProgressStyle: style })
},
// 重置音频播放状态
resetAudioState() {
if (this.audioContext) {
this.audioContext.stop()
}
this.setData({
isPlaying: false,
currentTime: 0,
sliderValue: 0,
audioDuration: 0
})
},
// 切换到上一个例句
prevSentence() {
const { currentIndex, sentences } = this.data
if (currentIndex <= 0) return;
// 重置音频播放状态
this.resetAudioState()
if (currentIndex > 0) {
const currentSentence = sentences[currentIndex - 1]
const assessmentResult = currentSentence?.details?.assessment?.result
@@ -146,6 +171,9 @@ Page<IPageData, IPageInstance>({
nextSentence() {
const { currentIndex, sentences } = this.data
if (currentIndex >= sentences.length - 1) return;
// 重置音频播放状态
this.resetAudioState()
if (currentIndex < sentences.length - 1) {
const index = currentIndex + 1
const currentSentence = sentences[index]
@@ -265,9 +293,15 @@ Page<IPageData, IPageInstance>({
// 如果已经获取过该例句的标准语音,直接返回
if (this.data.standardAudioMap[sentenceId]) return
const result = await apiManager.getStandardVoice(sentenceId)
if (result.id) {
// 获取文件
const id = await apiManager.getStandardVoice(sentenceId)
if (id) {
const fileUrl = `${FILE_BASE_URL}/${id}`
this.setData({
standardAudioMap: {
...this.data.standardAudioMap,
[sentenceId]: fileUrl
}
})
}
} catch (err) {
console.error('获取标准语音失败:', err)
@@ -292,7 +326,7 @@ Page<IPageData, IPageInstance>({
}
if (isPlaying) {
this.audioContext?.stop()
this.audioContext?.pause()
this.setData({ isPlaying: false })
return
}
@@ -300,22 +334,69 @@ Page<IPageData, IPageInstance>({
if (!this.audioContext) {
this.audioContext = wx.createInnerAudioContext()
this.audioContext.onEnded(() => {
this.setData({ isPlaying: false })
this.setData({
isPlaying: false,
currentTime: 0,
sliderValue: 0
})
})
this.audioContext.onError(() => {
this.setData({ isPlaying: false })
this.setData({
isPlaying: false,
currentTime: 0,
sliderValue: 0
})
wx.showToast({
title: '播放失败',
icon: 'none'
})
})
this.audioContext.onTimeUpdate(() => {
const currentTime = this.audioContext!.currentTime
const duration = this.audioContext!.duration
const sliderValue = (currentTime / duration) * 100
this.setData({
currentTime,
audioDuration: duration,
sliderValue
})
})
}
if (this.audioContext.src !== audioUrl) {
this.audioContext.src = audioUrl
this.setData({
currentTime: 0,
sliderValue: 0
})
}
this.audioContext.src = audioUrl
this.audioContext.play()
this.setData({ isPlaying: true })
},
// 处理进度条拖动
handleSliderChange(e: any) {
const value = e.detail.value
const duration = this.audioContext?.duration || 0
const currentTime = (value / 100) * duration
if (this.audioContext) {
this.audioContext.seek(currentTime)
this.setData({
currentTime,
sliderValue: value
})
}
},
// 格式化时间
formatTime(seconds: number) {
const minutes = Math.floor(seconds / 60)
const remainingSeconds = Math.floor(seconds % 60)
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`
},
onLoad(options: Record<string, string>) {
//如果有图片ID调用接口获取文本和评分信息
if (options.index) {
@@ -386,6 +467,13 @@ Page<IPageData, IPageInstance>({
}
},
onUnload() {
// 销毁音频实例
if (this.audioContext) {
this.audioContext.destroy()
}
},
onReady() {
// 监听录音结束事件
recorderManager.onStop((res) => {

View File

@@ -32,11 +32,20 @@
<view class="microphone-container">
<t-icon name="microphone-1" class="microphone {{isRecording ? 'recording' : ''}}" size="100rpx" bind:longpress="handleRecordStart" bind:touchend="handleRecordEnd" bind:touchcancel="handleRecordEnd" />
<text wx:if="{{isRecording}}" class="countdown">{{remainingTime}}s</text>
<!-- 播放标准语音按钮 -->
<t-button class="play-btn" theme="primary" size="small" variant="outline" bind:tap="playStandardVoice">
<t-icon name="{{isPlaying ? 'stop' : 'play'}}" size="32rpx" />
{{isPlaying ? '停止' : '播放'}}标准语音
</t-button>
<!-- 音频播放控制区域 -->
<view class="audio-control">
<view class="audio-title">标准音频</view>
<view class="audio-player">
<t-button class="play-btn" theme="primary" size="large" variant="outline" bind:tap="playStandardVoice">
<t-icon name="{{isPlaying ? 'pause' : 'play'}}" size="48rpx" />
</t-button>
<view class="audio-progress">
<text class="time-text">{{formatTime(currentTime)}}</text>
<t-slider class="progress-slider" value="{{sliderValue}}" bind:change="handleSliderChange" showValue="{{false}}" />
<text class="time-text">{{formatTime(audioDuration)}}</text>
</view>
</view>
</view>
<!-- 评分详解按钮 -->
<t-button wx:if="{{hasScoreInfo}}" class="score-detail-btn" theme="primary" size="small" variant="outline" bind:tap="handleScoreDetailClick">
评分详解

View File

@@ -111,16 +111,61 @@
display: flex;
flex-direction: column;
align-items: center;
margin-top: 40rpx;
position: relative;
gap: 24rpx;
}
.audio-control {
display: flex;
align-items: center;
justify-content: start;
width: 100%;
padding: 24rpx 32rpx;
background: #ffffff;
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.audio-title {
font-size: 28rpx;
color: #333333;
font-weight: 500;
}
.audio-player {
display: flex;
align-items: center;
gap: 24rpx;
}
.play-btn {
display: flex;
align-items: center;
gap: 8rpx;
min-width: 200rpx;
justify-content: center;
width: 96rpx;
height: 96rpx;
padding: 0;
border-radius: 50%;
flex-shrink: 0;
}
.audio-progress {
flex: 1;
display: flex;
align-items: center;
gap: 16rpx;
}
.time-text {
font-size: 24rpx;
color: #666;
min-width: 70rpx;
text-align: center;
}
.progress-slider {
flex: 1;
margin: 0 8rpx;
}
.score-detail-btn {

View File

@@ -234,7 +234,6 @@ Page({
displayContent: validDescriptions,
errorTip: ''
})
console.log('--lisa-displayContent', this.data.displayContent)
} else {
this.setData({
errorTip: '暂无描述信息'

View File

@@ -130,7 +130,6 @@ Page({
hasMore: newData.length < result.total,
isLoading: false,
});
console.log('---lisa-groupedHistory', this.data.groupedHistory)
} catch (error) {
console.error('加载每日摘要失败:', error);
this.setData({ isLoading: false });

View File

@@ -1377,10 +1377,10 @@ class ApiManager {
}
}
async getStandardVoice(text_id: string | number): Promise<{id: number | string}> {
async getStandardVoice(text_id: string | number): Promise<number | string> {
try {
console.log('开始获取标准音频');
const response = await this.request<{id: number | string}>(`/api/v1/standard/${text_id}`);
const response = await this.request<number | string>(`/api/v1/image_text/standard/${text_id}`);
console.log('获取标准音频成功:', response.data);
return response.data;
} catch (error) {