Commit 0a02df0176e3d0deed6790ab51c623fe6439b344
1 parent
0e03ee37
feat: playing ui
Showing
10 changed files
with
340 additions
and
5 deletions
components/audio-overlay/audio-overlay.js
0 → 100644
| 1 | +// 音频播放蒙层组件 | ||
| 2 | +Component({ | ||
| 3 | + properties: { | ||
| 4 | + // 是否显示蒙层 | ||
| 5 | + isPlaying: { | ||
| 6 | + type: Boolean, | ||
| 7 | + value: false | ||
| 8 | + }, | ||
| 9 | + // 播放的文本内容 | ||
| 10 | + audioText: { | ||
| 11 | + type: String, | ||
| 12 | + value: '' | ||
| 13 | + } | ||
| 14 | + }, | ||
| 15 | + | ||
| 16 | + data: { | ||
| 17 | + progress: 0, // 播放进度百分比 | ||
| 18 | + currentTime: '00:00', // 当前播放时间 | ||
| 19 | + duration: '00:00', // 总时长 | ||
| 20 | + timer: null // 定时器 | ||
| 21 | + }, | ||
| 22 | + | ||
| 23 | + lifetimes: { | ||
| 24 | + detached() { | ||
| 25 | + // 组件销毁时清理定时器 | ||
| 26 | + this.clearTimer(); | ||
| 27 | + } | ||
| 28 | + }, | ||
| 29 | + | ||
| 30 | + methods: { | ||
| 31 | + // 初始化音频监听 | ||
| 32 | + initAudio(audioContext) { | ||
| 33 | + if (!audioContext) return; | ||
| 34 | + | ||
| 35 | + this.audioContext = audioContext; | ||
| 36 | + | ||
| 37 | + // 监听音频播放进度 | ||
| 38 | + audioContext.onTimeUpdate(() => { | ||
| 39 | + const currentTime = audioContext.currentTime || 0; | ||
| 40 | + const duration = audioContext.duration || 0; | ||
| 41 | + | ||
| 42 | + this.setData({ | ||
| 43 | + progress: duration > 0 ? (currentTime / duration) * 100 : 0, | ||
| 44 | + currentTime: this.formatTime(currentTime), | ||
| 45 | + duration: this.formatTime(duration) | ||
| 46 | + }); | ||
| 47 | + }); | ||
| 48 | + | ||
| 49 | + // 监听播放结束 | ||
| 50 | + audioContext.onEnded(() => { | ||
| 51 | + this.stopPlaying(); | ||
| 52 | + }); | ||
| 53 | + | ||
| 54 | + // 监听播放错误 | ||
| 55 | + audioContext.onError(() => { | ||
| 56 | + this.stopPlaying(); | ||
| 57 | + }); | ||
| 58 | + | ||
| 59 | + // 监听播放停止 | ||
| 60 | + audioContext.onStop(() => { | ||
| 61 | + this.stopPlaying(); | ||
| 62 | + }); | ||
| 63 | + }, | ||
| 64 | + | ||
| 65 | + // 格式化时间显示 | ||
| 66 | + formatTime(seconds) { | ||
| 67 | + if (!seconds || isNaN(seconds)) return '00:00'; | ||
| 68 | + | ||
| 69 | + const mins = Math.floor(seconds / 60); | ||
| 70 | + const secs = Math.floor(seconds % 60); | ||
| 71 | + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; | ||
| 72 | + }, | ||
| 73 | + | ||
| 74 | + // 停止播放 | ||
| 75 | + stopPlaying() { | ||
| 76 | + this.setData({ | ||
| 77 | + progress: 0, | ||
| 78 | + currentTime: '00:00', | ||
| 79 | + duration: '00:00' | ||
| 80 | + }); | ||
| 81 | + | ||
| 82 | + this.clearTimer(); | ||
| 83 | + | ||
| 84 | + // 触发父组件的停止事件 | ||
| 85 | + this.triggerEvent('stop'); | ||
| 86 | + }, | ||
| 87 | + | ||
| 88 | + // 清理定时器 | ||
| 89 | + clearTimer() { | ||
| 90 | + if (this.data.timer) { | ||
| 91 | + clearInterval(this.data.timer); | ||
| 92 | + this.setData({ | ||
| 93 | + timer: null | ||
| 94 | + }); | ||
| 95 | + } | ||
| 96 | + }, | ||
| 97 | + | ||
| 98 | + // 点击蒙层背景 | ||
| 99 | + onOverlayTap() { | ||
| 100 | + // 点击背景关闭蒙层并停止播放 | ||
| 101 | + this.stopPlaying(); | ||
| 102 | + }, | ||
| 103 | + | ||
| 104 | + // 点击停止按钮 | ||
| 105 | + onStopTap() { | ||
| 106 | + this.stopPlaying(); | ||
| 107 | + }, | ||
| 108 | + | ||
| 109 | + // 阻止事件冒泡 | ||
| 110 | + stopPropagation() { | ||
| 111 | + // 阻止点击内容区域时触发背景点击事件 | ||
| 112 | + } | ||
| 113 | + } | ||
| 114 | +}); | ||
| 0 | \ No newline at end of file | 115 | \ No newline at end of file |
components/audio-overlay/audio-overlay.json
0 → 100644
components/audio-overlay/audio-overlay.wxml
0 → 100644
| 1 | +<!-- 音频播放蒙层组件 --> | ||
| 2 | +<view class="audio-overlay" wx:if="{{isPlaying}}" bindtap="onOverlayTap"> | ||
| 3 | + <view class="overlay-content" catchtap="stopPropagation"> | ||
| 4 | + <view class="audio-icon"> | ||
| 5 | + <view class="sound-wave"> | ||
| 6 | + <view class="wave"></view> | ||
| 7 | + <view class="wave"></view> | ||
| 8 | + <view class="wave"></view> | ||
| 9 | + <view class="wave"></view> | ||
| 10 | + </view> | ||
| 11 | + </view> | ||
| 12 | + <view class="audio-text">{{audioText}}</view> | ||
| 13 | + <view class="progress-container"> | ||
| 14 | + <view class="progress-bar"> | ||
| 15 | + <view class="progress-fill" style="width: {{progress}}%"></view> | ||
| 16 | + </view> | ||
| 17 | + <view class="time-info">{{currentTime}} / {{duration}}</view> | ||
| 18 | + </view> | ||
| 19 | + <button class="stop-btn" bindtap="onStopTap">停止播放</button> | ||
| 20 | + </view> | ||
| 21 | +</view> | ||
| 0 | \ No newline at end of file | 22 | \ No newline at end of file |
components/audio-overlay/audio-overlay.wxss
0 → 100644
| 1 | +/* 音频播放蒙层样式 */ | ||
| 2 | +.audio-overlay { | ||
| 3 | + position: fixed; | ||
| 4 | + top: 0; | ||
| 5 | + left: 0; | ||
| 6 | + width: 100vw; | ||
| 7 | + height: 100vh; | ||
| 8 | + background: rgba(0, 0, 0, 0.7); | ||
| 9 | + display: flex; | ||
| 10 | + align-items: center; | ||
| 11 | + justify-content: center; | ||
| 12 | + z-index: 9999; | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +.overlay-content { | ||
| 16 | + background: white; | ||
| 17 | + border-radius: 16rpx; | ||
| 18 | + padding: 60rpx 40rpx; | ||
| 19 | + margin: 40rpx; | ||
| 20 | + text-align: center; | ||
| 21 | + min-width: 500rpx; | ||
| 22 | + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2); | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +.audio-icon { | ||
| 26 | + margin-bottom: 40rpx; | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +.sound-wave { | ||
| 30 | + display: flex; | ||
| 31 | + align-items: center; | ||
| 32 | + justify-content: center; | ||
| 33 | + gap: 8rpx; | ||
| 34 | + height: 80rpx; | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +.wave { | ||
| 38 | + width: 6rpx; | ||
| 39 | + background: #1296db; | ||
| 40 | + border-radius: 3rpx; | ||
| 41 | + animation: wave 1.5s ease-in-out infinite; | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +.wave:nth-child(1) { | ||
| 45 | + height: 20rpx; | ||
| 46 | + animation-delay: 0s; | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +.wave:nth-child(2) { | ||
| 50 | + height: 40rpx; | ||
| 51 | + animation-delay: 0.1s; | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +.wave:nth-child(3) { | ||
| 55 | + height: 60rpx; | ||
| 56 | + animation-delay: 0.2s; | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +.wave:nth-child(4) { | ||
| 60 | + height: 40rpx; | ||
| 61 | + animation-delay: 0.3s; | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +@keyframes wave { | ||
| 65 | + 0%, 100% { | ||
| 66 | + transform: scaleY(1); | ||
| 67 | + } | ||
| 68 | + 50% { | ||
| 69 | + transform: scaleY(1.5); | ||
| 70 | + } | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +.audio-text { | ||
| 74 | + font-size: 32rpx; | ||
| 75 | + color: #333; | ||
| 76 | + margin-bottom: 40rpx; | ||
| 77 | + line-height: 1.4; | ||
| 78 | + max-height: 200rpx; | ||
| 79 | + overflow: hidden; | ||
| 80 | + text-overflow: ellipsis; | ||
| 81 | + display: -webkit-box; | ||
| 82 | + -webkit-line-clamp: 3; | ||
| 83 | + -webkit-box-orient: vertical; | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +.progress-container { | ||
| 87 | + margin-bottom: 40rpx; | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +.progress-bar { | ||
| 91 | + width: 100%; | ||
| 92 | + height: 8rpx; | ||
| 93 | + background: #f0f0f0; | ||
| 94 | + border-radius: 4rpx; | ||
| 95 | + overflow: hidden; | ||
| 96 | + margin-bottom: 16rpx; | ||
| 97 | +} | ||
| 98 | + | ||
| 99 | +.progress-fill { | ||
| 100 | + height: 100%; | ||
| 101 | + background: #1296db; | ||
| 102 | + transition: width 0.3s ease; | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | +.time-info { | ||
| 106 | + font-size: 24rpx; | ||
| 107 | + color: #666; | ||
| 108 | +} | ||
| 109 | + | ||
| 110 | +.stop-btn { | ||
| 111 | + background: #ff4757; | ||
| 112 | + color: white; | ||
| 113 | + border: none; | ||
| 114 | + border-radius: 50rpx; | ||
| 115 | + padding: 20rpx 60rpx; | ||
| 116 | + font-size: 30rpx; | ||
| 117 | +} | ||
| 0 | \ No newline at end of file | 118 | \ No newline at end of file |
pages/index/index.js
| @@ -12,7 +12,9 @@ Page({ | @@ -12,7 +12,9 @@ Page({ | ||
| 12 | placeholder: '点击此处输入文字', | 12 | placeholder: '点击此处输入文字', |
| 13 | text: '', | 13 | text: '', |
| 14 | typeList, | 14 | typeList, |
| 15 | - selectedIndex: String(selectedIndex) ? selectedIndex : 0 | 15 | + selectedIndex: String(selectedIndex) ? selectedIndex : 0, |
| 16 | + isAudioPlaying: false, | ||
| 17 | + currentAudioText: '' | ||
| 16 | }, | 18 | }, |
| 17 | onLoad(){ | 19 | onLoad(){ |
| 18 | wx.showShareMenu(); | 20 | wx.showShareMenu(); |
| @@ -141,6 +143,20 @@ Page({ | @@ -141,6 +143,20 @@ Page({ | ||
| 141 | audio = wx.createInnerAudioContext({useWebAudioImplement: true}); | 143 | audio = wx.createInnerAudioContext({useWebAudioImplement: true}); |
| 142 | audio.autoplay = true; | 144 | audio.autoplay = true; |
| 143 | 145 | ||
| 146 | + // 显示播放蒙层 | ||
| 147 | + this.setData({ | ||
| 148 | + isAudioPlaying: true, | ||
| 149 | + currentAudioText: text | ||
| 150 | + }); | ||
| 151 | + | ||
| 152 | + // 初始化蒙层组件的音频监听 | ||
| 153 | + setTimeout(() => { | ||
| 154 | + const overlayComponent = this.selectComponent('audio-overlay'); | ||
| 155 | + if (overlayComponent) { | ||
| 156 | + overlayComponent.initAudio(audio); | ||
| 157 | + } | ||
| 158 | + }, 100); | ||
| 159 | + | ||
| 144 | // 检查是否播放过 | 160 | // 检查是否播放过 |
| 145 | const audioList = app.globalData.getAudioList(); | 161 | const audioList = app.globalData.getAudioList(); |
| 146 | let played = audioList.find(v => v.text === text && v.type === type); | 162 | let played = audioList.find(v => v.text === text && v.type === type); |
| @@ -153,6 +169,7 @@ Page({ | @@ -153,6 +169,7 @@ Page({ | ||
| 153 | // 播放失败要移除缓存内容 | 169 | // 播放失败要移除缓存内容 |
| 154 | audio.onError(err => { | 170 | audio.onError(err => { |
| 155 | app.globalData.setAudioList('delete', played.path); | 171 | app.globalData.setAudioList('delete', played.path); |
| 172 | + this.setData({ isAudioPlaying: false }); | ||
| 156 | wx.showModal({ | 173 | wx.showModal({ |
| 157 | title: '提示', | 174 | title: '提示', |
| 158 | content: '该语音播放过但缓存失效,点击确定重新播放', | 175 | content: '该语音播放过但缓存失效,点击确定重新播放', |
| @@ -189,6 +206,7 @@ Page({ | @@ -189,6 +206,7 @@ Page({ | ||
| 189 | // 不合规结束 | 206 | // 不合规结束 |
| 190 | if(result.suggest !== 'pass'){ | 207 | if(result.suggest !== 'pass'){ |
| 191 | wx.hideLoading(); | 208 | wx.hideLoading(); |
| 209 | + this.setData({ isAudioPlaying: false }); | ||
| 192 | return wx.showToast({ | 210 | return wx.showToast({ |
| 193 | title: '内容不合规', | 211 | title: '内容不合规', |
| 194 | icon: 'error', | 212 | icon: 'error', |
| @@ -221,6 +239,7 @@ Page({ | @@ -221,6 +239,7 @@ Page({ | ||
| 221 | fail(err) { | 239 | fail(err) { |
| 222 | console.log(err); | 240 | console.log(err); |
| 223 | wx.hideLoading(); | 241 | wx.hideLoading(); |
| 242 | + this.setData({ isAudioPlaying: false }); | ||
| 224 | wx.showToast({ | 243 | wx.showToast({ |
| 225 | title: '下载失败', | 244 | title: '下载失败', |
| 226 | icon: 'none' | 245 | icon: 'none' |
| @@ -229,6 +248,7 @@ Page({ | @@ -229,6 +248,7 @@ Page({ | ||
| 229 | }); | 248 | }); |
| 230 | } catch (error) { | 249 | } catch (error) { |
| 231 | wx.hideLoading(); | 250 | wx.hideLoading(); |
| 251 | + this.setData({ isAudioPlaying: false }); | ||
| 232 | // TTS接口调用失败的错误已经在app.js中处理了 | 252 | // TTS接口调用失败的错误已经在app.js中处理了 |
| 233 | } | 253 | } |
| 234 | }, | 254 | }, |
| @@ -243,6 +263,15 @@ Page({ | @@ -243,6 +263,15 @@ Page({ | ||
| 243 | audio.destroy(); | 263 | audio.destroy(); |
| 244 | audio = null; | 264 | audio = null; |
| 245 | } | 265 | } |
| 266 | + this.setData({ | ||
| 267 | + isAudioPlaying: false, | ||
| 268 | + currentAudioText: '' | ||
| 269 | + }); | ||
| 270 | + }, | ||
| 271 | + | ||
| 272 | + // 音频停止播放的回调 | ||
| 273 | + onAudioStop() { | ||
| 274 | + this.resetAudio(); | ||
| 246 | }, | 275 | }, |
| 247 | selectIndex(e){ | 276 | selectIndex(e){ |
| 248 | const { index } = e.target.dataset; | 277 | const { index } = e.target.dataset; |
pages/index/index.json
pages/index/index.wxml
| @@ -28,5 +28,13 @@ | @@ -28,5 +28,13 @@ | ||
| 28 | > {{item.desc}} </button> | 28 | > {{item.desc}} </button> |
| 29 | </view> | 29 | </view> |
| 30 | </view> | 30 | </view> |
| 31 | + | ||
| 32 | +<!-- 音频播放蒙层 --> | ||
| 33 | +<audio-overlay | ||
| 34 | + is-playing="{{isAudioPlaying}}" | ||
| 35 | + audio-text="{{currentAudioText}}" | ||
| 36 | + bind:stop="onAudioStop" | ||
| 37 | +></audio-overlay> | ||
| 38 | + | ||
| 31 | <!--ad class="ad-1" unit-id="adunit-34f63a98428c1660"></ad> | 39 | <!--ad class="ad-1" unit-id="adunit-34f63a98428c1660"></ad> |
| 32 | <ad class="ad-2" unit-id="adunit-80a27ecf9a35c553"></ad--> | 40 | <ad class="ad-2" unit-id="adunit-80a27ecf9a35c553"></ad--> |
| 33 | \ No newline at end of file | 41 | \ No newline at end of file |
pages/result/result.js
| @@ -16,7 +16,9 @@ let formateDate = function (audioList) { | @@ -16,7 +16,9 @@ let formateDate = function (audioList) { | ||
| 16 | 16 | ||
| 17 | Page({ | 17 | Page({ |
| 18 | data: { | 18 | data: { |
| 19 | - audioList: formateDate(app.globalData.getAudioList()) | 19 | + audioList: formateDate(app.globalData.getAudioList()), |
| 20 | + isAudioPlaying: false, | ||
| 21 | + currentAudioText: '' | ||
| 20 | }, | 22 | }, |
| 21 | onLoad(options){ | 23 | onLoad(options){ |
| 22 | this.setData({ | 24 | this.setData({ |
| @@ -33,8 +35,24 @@ Page({ | @@ -33,8 +35,24 @@ Page({ | ||
| 33 | audio = wx.createInnerAudioContext({useWebAudioImplement: true}); | 35 | audio = wx.createInnerAudioContext({useWebAudioImplement: true}); |
| 34 | audio.autoplay = true; | 36 | audio.autoplay = true; |
| 35 | audio.src = target.path; | 37 | audio.src = target.path; |
| 38 | + | ||
| 39 | + // 显示播放蒙层 | ||
| 40 | + this.setData({ | ||
| 41 | + isAudioPlaying: true, | ||
| 42 | + currentAudioText: target.text | ||
| 43 | + }); | ||
| 44 | + | ||
| 45 | + // 初始化蒙层组件的音频监听 | ||
| 46 | + setTimeout(() => { | ||
| 47 | + const overlayComponent = this.selectComponent('audio-overlay'); | ||
| 48 | + if (overlayComponent) { | ||
| 49 | + overlayComponent.initAudio(audio); | ||
| 50 | + } | ||
| 51 | + }, 100); | ||
| 52 | + | ||
| 36 | // 播放失败要移除缓存内容 | 53 | // 播放失败要移除缓存内容 |
| 37 | audio.onError(err => { | 54 | audio.onError(err => { |
| 55 | + this.setData({ isAudioPlaying: false }); | ||
| 38 | wx.showModal({ | 56 | wx.showModal({ |
| 39 | title: '提示', | 57 | title: '提示', |
| 40 | content: '该语音缓存失效,点击确定删除', | 58 | content: '该语音缓存失效,点击确定删除', |
| @@ -80,5 +98,14 @@ Page({ | @@ -80,5 +98,14 @@ Page({ | ||
| 80 | audio.destroy(); | 98 | audio.destroy(); |
| 81 | audio = null; | 99 | audio = null; |
| 82 | } | 100 | } |
| 101 | + this.setData({ | ||
| 102 | + isAudioPlaying: false, | ||
| 103 | + currentAudioText: '' | ||
| 104 | + }); | ||
| 105 | + }, | ||
| 106 | + | ||
| 107 | + // 音频停止播放的回调 | ||
| 108 | + onAudioStop() { | ||
| 109 | + this.resetAudio(); | ||
| 83 | } | 110 | } |
| 84 | }); | 111 | }); |
| 85 | \ No newline at end of file | 112 | \ No newline at end of file |
pages/result/result.json
pages/result/result.wxml
| @@ -16,4 +16,11 @@ | @@ -16,4 +16,11 @@ | ||
| 16 | <button class="button" type="primary" size="mini" data-path="{{item.path}}" bindtap="play">播放</button> | 16 | <button class="button" type="primary" size="mini" data-path="{{item.path}}" bindtap="play">播放</button> |
| 17 | <button class="button" type="primary" size="mini" data-path="{{item.path}}" bindtap="download">下载</button> | 17 | <button class="button" type="primary" size="mini" data-path="{{item.path}}" bindtap="download">下载</button> |
| 18 | </view> | 18 | </view> |
| 19 | -</view> | ||
| 20 | \ No newline at end of file | 19 | \ No newline at end of file |
| 20 | +</view> | ||
| 21 | + | ||
| 22 | +<!-- 音频播放蒙层 --> | ||
| 23 | +<audio-overlay | ||
| 24 | + is-playing="{{isAudioPlaying}}" | ||
| 25 | + audio-text="{{currentAudioText}}" | ||
| 26 | + bind:stop="onAudioStop" | ||
| 27 | +></audio-overlay> | ||
| 21 | \ No newline at end of file | 28 | \ No newline at end of file |