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