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 | 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 | 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 | 118 | \ No newline at end of file | ... | ... |
pages/index/index.js
| ... | ... | @@ -12,7 +12,9 @@ Page({ |
| 12 | 12 | placeholder: '点击此处输入文字', |
| 13 | 13 | text: '', |
| 14 | 14 | typeList, |
| 15 | - selectedIndex: String(selectedIndex) ? selectedIndex : 0 | |
| 15 | + selectedIndex: String(selectedIndex) ? selectedIndex : 0, | |
| 16 | + isAudioPlaying: false, | |
| 17 | + currentAudioText: '' | |
| 16 | 18 | }, |
| 17 | 19 | onLoad(){ |
| 18 | 20 | wx.showShareMenu(); |
| ... | ... | @@ -141,6 +143,20 @@ Page({ |
| 141 | 143 | audio = wx.createInnerAudioContext({useWebAudioImplement: true}); |
| 142 | 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 | 161 | const audioList = app.globalData.getAudioList(); |
| 146 | 162 | let played = audioList.find(v => v.text === text && v.type === type); |
| ... | ... | @@ -153,6 +169,7 @@ Page({ |
| 153 | 169 | // 播放失败要移除缓存内容 |
| 154 | 170 | audio.onError(err => { |
| 155 | 171 | app.globalData.setAudioList('delete', played.path); |
| 172 | + this.setData({ isAudioPlaying: false }); | |
| 156 | 173 | wx.showModal({ |
| 157 | 174 | title: '提示', |
| 158 | 175 | content: '该语音播放过但缓存失效,点击确定重新播放', |
| ... | ... | @@ -189,6 +206,7 @@ Page({ |
| 189 | 206 | // 不合规结束 |
| 190 | 207 | if(result.suggest !== 'pass'){ |
| 191 | 208 | wx.hideLoading(); |
| 209 | + this.setData({ isAudioPlaying: false }); | |
| 192 | 210 | return wx.showToast({ |
| 193 | 211 | title: '内容不合规', |
| 194 | 212 | icon: 'error', |
| ... | ... | @@ -221,6 +239,7 @@ Page({ |
| 221 | 239 | fail(err) { |
| 222 | 240 | console.log(err); |
| 223 | 241 | wx.hideLoading(); |
| 242 | + this.setData({ isAudioPlaying: false }); | |
| 224 | 243 | wx.showToast({ |
| 225 | 244 | title: '下载失败', |
| 226 | 245 | icon: 'none' |
| ... | ... | @@ -229,6 +248,7 @@ Page({ |
| 229 | 248 | }); |
| 230 | 249 | } catch (error) { |
| 231 | 250 | wx.hideLoading(); |
| 251 | + this.setData({ isAudioPlaying: false }); | |
| 232 | 252 | // TTS接口调用失败的错误已经在app.js中处理了 |
| 233 | 253 | } |
| 234 | 254 | }, |
| ... | ... | @@ -243,6 +263,15 @@ Page({ |
| 243 | 263 | audio.destroy(); |
| 244 | 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 | 276 | selectIndex(e){ |
| 248 | 277 | const { index } = e.target.dataset; | ... | ... |
pages/index/index.json
pages/index/index.wxml
| ... | ... | @@ -28,5 +28,13 @@ |
| 28 | 28 | > {{item.desc}} </button> |
| 29 | 29 | </view> |
| 30 | 30 | </view> |
| 31 | + | |
| 32 | +<!-- 音频播放蒙层 --> | |
| 33 | +<audio-overlay | |
| 34 | + is-playing="{{isAudioPlaying}}" | |
| 35 | + audio-text="{{currentAudioText}}" | |
| 36 | + bind:stop="onAudioStop" | |
| 37 | +></audio-overlay> | |
| 38 | + | |
| 31 | 39 | <!--ad class="ad-1" unit-id="adunit-34f63a98428c1660"></ad> |
| 32 | 40 | <ad class="ad-2" unit-id="adunit-80a27ecf9a35c553"></ad--> |
| 33 | 41 | \ No newline at end of file | ... | ... |
pages/result/result.js
| ... | ... | @@ -16,7 +16,9 @@ let formateDate = function (audioList) { |
| 16 | 16 | |
| 17 | 17 | Page({ |
| 18 | 18 | data: { |
| 19 | - audioList: formateDate(app.globalData.getAudioList()) | |
| 19 | + audioList: formateDate(app.globalData.getAudioList()), | |
| 20 | + isAudioPlaying: false, | |
| 21 | + currentAudioText: '' | |
| 20 | 22 | }, |
| 21 | 23 | onLoad(options){ |
| 22 | 24 | this.setData({ |
| ... | ... | @@ -33,8 +35,24 @@ Page({ |
| 33 | 35 | audio = wx.createInnerAudioContext({useWebAudioImplement: true}); |
| 34 | 36 | audio.autoplay = true; |
| 35 | 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 | 54 | audio.onError(err => { |
| 55 | + this.setData({ isAudioPlaying: false }); | |
| 38 | 56 | wx.showModal({ |
| 39 | 57 | title: '提示', |
| 40 | 58 | content: '该语音缓存失效,点击确定删除', |
| ... | ... | @@ -80,5 +98,14 @@ Page({ |
| 80 | 98 | audio.destroy(); |
| 81 | 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 | 112 | \ No newline at end of file | ... | ... |
pages/result/result.json
pages/result/result.wxml
| ... | ... | @@ -16,4 +16,11 @@ |
| 16 | 16 | <button class="button" type="primary" size="mini" data-path="{{item.path}}" bindtap="play">播放</button> |
| 17 | 17 | <button class="button" type="primary" size="mini" data-path="{{item.path}}" bindtap="download">下载</button> |
| 18 | 18 | </view> |
| 19 | -</view> | |
| 20 | 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 | 28 | \ No newline at end of file | ... | ... |