fix audio

This commit is contained in:
Felix
2025-12-06 20:20:25 +08:00
parent 29064351aa
commit 7dd17b1c08
3 changed files with 90 additions and 78 deletions

View File

@@ -129,7 +129,10 @@ interface IPageData {
cachedSentenceIndex: number,
isWordEmptyResult: boolean,
recordPermissionGranted: boolean
loadingMaskVisible: boolean
loadingMaskVisible: boolean,
bottomLocked: boolean,
standardAudioLocalMap: { [key: string]: string }, // 标准语音文件ID映射
assessmentAudioLocalMap: { [key: string]: string } // 评估语音文件ID映射
}
type IPageMethods = {
@@ -228,6 +231,9 @@ Page<IPageData, IPageInstance>({
{ tag: 4, description: '未录入', color: '#f5f5f5' }
],
standardAudioMap: {}, // 标准语音文件ID映射
// 页面级音频本地缓存:避免重复网络获取
standardAudioLocalMap: {},
assessmentAudioLocalMap: {},
isPlaying: false, // 是否正在播放音频
audioDuration: 0, // 音频总时长
currentTime: 0, // 当前播放时间
@@ -267,7 +273,8 @@ Page<IPageData, IPageInstance>({
cachedSentenceIndex: -1,
isMoreMenuClosing: false,
recordPermissionGranted: false,
loadingMaskVisible: false
loadingMaskVisible: false,
bottomLocked: false,
},
onMicHighlight() {
@@ -542,6 +549,7 @@ Page<IPageData, IPageInstance>({
// 长按开始录音
handleRecordStart() {
if (this.data.bottomLocked) return
this.startRecording()
try { this.onMicHighlight() } catch (e) {}
},
@@ -604,7 +612,7 @@ Page<IPageData, IPageInstance>({
// 播放标准语音
playStandardVoice() {
if (this.data.isRecording) return
if (this.data.bottomLocked || this.data.isRecording) return
const { currentSentence, standardAudioMap, isPlaying } = this.data
const audioUrl = standardAudioMap[currentSentence.id]
@@ -622,17 +630,17 @@ Page<IPageData, IPageInstance>({
return
}
// 使用文件下载接口获取音频文件路径
apiManager.downloadFile(audioUrl).then((filePath) => {
// 如果当前正在播放且音源一致,则切换为暂停
// 优先使用页面本地缓存
const cachedLocal = this.data.standardAudioLocalMap[currentSentence.id]
const playWithPath = (filePath: string) => {
if (isPlaying && this.audioContext?.src === filePath) {
this.audioContext.pause()
// 清除播放图标动画并复位
try { this.audioContext.pause() } catch (e) {}
try { this.audioContext.seek(0) } catch (e) {}
if (this.playIconTimer) {
clearInterval(this.playIconTimer)
this.playIconTimer = undefined
}
this.setData({ isPlaying: false, playIconName: 'sound-low' })
this.setData({ isPlaying: false, playIconName: 'sound-low', currentTime: 0, sliderValue: 0 })
return
}
@@ -738,36 +746,39 @@ Page<IPageData, IPageInstance>({
try { this.audioContext.seek(0) } catch (e) {}
this.setData({ currentTime: 0, sliderValue: 0, audioDuration: 0 })
this.audioContext.src = filePath
} else {
try { this.audioContext.seek(0) } catch (e) {}
this.setData({ currentTime: 0, sliderValue: 0 })
}
const canplayHandler = () => {
try {
this.audioContext?.play?.()
this.setData({ isPlaying: true })
} catch (error) {
console.error('标准语音播放失败:', error)
wx.showToast({ title: '音频播放失败', icon: 'none' })
}
try { (this.audioContext as any).offCanplay && (this.audioContext as any).offCanplay(canplayHandler) } catch (e) {}
try {
this.audioContext.play()
this.setData({ isPlaying: true })
} catch (error) {
wx.showToast({ title: '音频播放失败', icon: 'none' })
}
try { (this.audioContext as any).onCanplay(canplayHandler) } catch (e) {
try {
this.audioContext.play()
this.setData({ isPlaying: true })
} catch (error) {
console.error('标准语音播放失败(无canplay):', error)
wx.showToast({ title: '音频播放失败', icon: 'none' })
}
}
}).catch((error) => {
console.error('下载音频文件失败:', error)
wx.showToast({ title: '音频下载失败', icon: 'none' })
})
}
if (cachedLocal) {
playWithPath(cachedLocal)
} else {
apiManager.downloadFile(audioUrl).then((filePath) => {
this.setData({
standardAudioLocalMap: {
...this.data.standardAudioLocalMap,
[currentSentence.id]: filePath
}
})
playWithPath(filePath)
}).catch((error) => {
console.error('下载音频文件失败:', error)
wx.showToast({ title: '音频下载失败', icon: 'none' })
})
}
},
// 新增:播放评分结果音频(使用当前句子的 file_id
playAssessmentVoice() {
if (this.data.isRecording) return
if (this.data.bottomLocked || this.data.isRecording) return
const { currentSentence, isPlaying } = this.data
const fileId = currentSentence?.file_id
@@ -776,17 +787,16 @@ Page<IPageData, IPageInstance>({
return
}
// 使用文件下载接口获取音频文件路径
apiManager.downloadFile(fileId).then((filePath) => {
// 如果当前正在播放且音源一致,则切换为暂停
const cachedLocal = this.data.assessmentAudioLocalMap[fileId]
const playWithPath = (filePath: string) => {
if (isPlaying && this.audioContext?.src === filePath) {
this.audioContext.pause()
// 清除播放图标动画并复位
try { this.audioContext.pause() } catch (e) {}
try { this.audioContext.seek(0) } catch (e) {}
if (this.playIconTimer) {
clearInterval(this.playIconTimer)
this.playIconTimer = undefined
}
this.setData({ isPlaying: false, playIconName: 'sound-low' })
this.setData({ isPlaying: false, playIconName: 'sound-low', currentTime: 0, sliderValue: 0 })
return
}
@@ -876,37 +886,39 @@ Page<IPageData, IPageInstance>({
})
}
// 切换音源并播放
if (this.audioContext.src !== filePath) {
try { this.audioContext.pause() } catch (e) {}
try { this.audioContext.seek(0) } catch (e) {}
this.setData({ currentTime: 0, sliderValue: 0, audioDuration: 0 })
this.audioContext.src = filePath
} else {
try { this.audioContext.seek(0) } catch (e) {}
this.setData({ currentTime: 0, sliderValue: 0 })
}
const canplayHandler = () => {
try {
this.audioContext?.play()
this.setData({ isPlaying: true })
} catch (error) {
console.error('评分语音播放失败:', error)
wx.showToast({ title: '音频播放失败', icon: 'none' })
}
try { (this.audioContext as any).offCanplay && (this.audioContext as any).offCanplay(canplayHandler) } catch (e) {}
try {
this.audioContext.play()
this.setData({ isPlaying: true })
} catch (error) {
wx.showToast({ title: '音频播放失败', icon: 'none' })
}
try { (this.audioContext as any).onCanplay(canplayHandler) } catch (e) {
try {
this.audioContext.play()
this.setData({ isPlaying: true })
} catch (error) {
console.error('评分语音播放失败(无canplay):', error)
wx.showToast({ title: '音频播放失败', icon: 'none' })
}
}
}).catch((error) => {
console.error('下载音频文件失败:', error)
wx.showToast({ title: '音频下载失败', icon: 'none' })
})
}
if (cachedLocal) {
playWithPath(cachedLocal)
} else {
apiManager.downloadFile(fileId).then((filePath) => {
this.setData({
assessmentAudioLocalMap: {
...this.data.assessmentAudioLocalMap,
[fileId]: filePath
}
})
playWithPath(filePath)
}).catch((error) => {
console.error('下载音频文件失败:', error)
wx.showToast({ title: '音频下载失败', icon: 'none' })
})
}
},
async handleWordClick(e: any) {
@@ -1363,7 +1375,10 @@ Page<IPageData, IPageInstance>({
this.setData({
wordAudioPlaying: false,
wordAudioIconName: 'sound',
activeWordAudioType: ''
activeWordAudioType: '',
// 清空页面级音频缓存映射
standardAudioLocalMap: {},
assessmentAudioLocalMap: {}
})
},
@@ -1519,7 +1534,7 @@ Page<IPageData, IPageInstance>({
},
onScoreTap() {
if (this.data.isRecording) return
if (this.data.bottomLocked || this.data.isRecording) return
console.log('Score button tapped')
// 当当前选中例句无评分信息时,不响应点击
if (!this.data.hasScoreInfo) {
@@ -1558,7 +1573,7 @@ Page<IPageData, IPageInstance>({
})
},
onTransTap() {
if (this.data.isRecording) return
if (this.data.bottomLocked || this.data.isRecording) return
console.log('User button tapped')
// Cycle through translation display modes
const currentMode = this.data.transDisplayMode
@@ -1693,7 +1708,7 @@ Page<IPageData, IPageInstance>({
},
onMoreTap() {
if (this.data.isRecording) return
if (this.data.bottomLocked || this.data.isRecording) return
const { isMoreMenuOpen } = this.data
if (!isMoreMenuOpen) {
this.setData({ isMoreMenuOpen: true, isMoreMenuClosing: false })

View File

@@ -43,20 +43,20 @@
<view wx:if="{{isMoreMenuOpen}}" class="more-menu-modal"></view>
<view class="button-row">
<t-icon name="{{isPlaying ? 'pause' : 'play'}}" class="bottom-button {{isRecording ? 'disabled' : ''}}" size="48rpx" bind:tap="playStandardVoice" />
<view class="bottom-button-img-wrap bottom-button {{isRecording ? 'disabled' : ''}}" bind:tap="onTransTap">
<view class="bottom-button-img-wrap bottom-button {{isRecording ? 'disabled' : ''}} {{bottomLocked ? 'disabled' : ''}}" bind:tap="onTransTap">
<t-icon name="translate" class="trans-button left-half {{transDisplayMode === 'en_ipa' ? 'trans-active' : 'trans-deactive'}}" size="48rpx" />
<t-icon name="translate" class="trans-button right-half {{transDisplayMode === 'en_zh' ? 'trans-active' : 'trans-deactive'}}" size="48rpx" />
</view>
<view class="bottom-button mic-wrap" bind:longpress="handleRecordStart" bind:touchend="handleRecordEnd" bind:touchcancel="handleRecordEnd" >
<view class="bottom-button mic-wrap {{bottomLocked ? 'disabled' : ''}}" bind:longpress="handleRecordStart" bind:touchend="handleRecordEnd" bind:touchcancel="handleRecordEnd" >
<t-icon name="microphone-1" color="{{isRecording ? '#FFFFFF' : '#333333'}}"
class="microphone {{isRecording ? 'recording' : 'bottom-button'}} {{recordPermissionGranted ? '' : 'disabled'}}" size="48rpx" />
<view wx:if="{{isRecording}}" class="mic">
<view class="mic-shadow"></view>
</view>
</view>
<t-icon name="fact-check" class="bottom-button {{(hasScoreInfo && !isRecording) ? '' : 'disabled'}}" size="48rpx" bind:tap="onScoreTap" />
<t-icon name="fact-check" class="bottom-button {{(hasScoreInfo && !isRecording && !bottomLocked) ? '' : 'disabled'}}" size="48rpx" bind:tap="onScoreTap" />
<!-- <t-icon name="ellipsis" class="bottom-button {{isMoreMenuOpen ? 'more-open' : ''}}" size="48rpx" bind:tap="onMoreTap" /> -->
<view class="bottom-button {{isRecording ? 'disabled' : ''}}" bindtap="onMoreTap">
<view class="bottom-button {{isRecording ? 'disabled' : ''}} {{bottomLocked ? 'disabled' : ''}}" bindtap="onMoreTap">
<view class="ul {{isMoreMenuOpen ? 'active' : ''}}">
<view class="dot1"></view>
<view class="dot2"></view>

View File

@@ -656,15 +656,6 @@
border: 4rpx solid #e0e0e0;
}
.bottom-button.disabled {
opacity: 0.4;
pointer-events: none;
}
.bottom-button:active {
background: #e0e0e0;
color: #333;
}
.bottom-button-img-wrap {
width: 46rpx;
height: 46rpx;
@@ -1525,3 +1516,9 @@
opacity: 1;
}
}
.bottom-button.disabled,
.mic-wrap.disabled,
.bottom-button-img-wrap.disabled {
pointer-events: none;
opacity: 0.6;
}