Commit 0a02df0176e3d0deed6790ab51c623fe6439b344

Authored by xwenliang
1 parent 0e03ee37

feat: playing ui

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
  1 +{
  2 + "component": true,
  3 + "usingComponents": {}
  4 +}
0 5 \ No newline at end of file
... ...
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
1   -{}
2 1 \ No newline at end of file
  2 +{
  3 + "usingComponents": {
  4 + "audio-overlay": "../../components/audio-overlay/audio-overlay"
  5 + }
  6 +}
3 7 \ No newline at end of file
... ...
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
1   -{}
2 1 \ No newline at end of file
  2 +{
  3 + "usingComponents": {
  4 + "audio-overlay": "../../components/audio-overlay/audio-overlay"
  5 + }
  6 +}
3 7 \ No newline at end of file
... ...
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
... ...