Преглед изворни кода

语音播报功能和后端对接

zmj пре 4 дана
родитељ
комит
6bba47b3bd
4 измењених фајлова са 32 додато и 3 уклоњено
  1. 3 2
      src/api/screen.js
  2. 1 1
      src/components/BroadcastOverlay.vue
  3. 21 0
      src/components/IdlePlayer.vue
  4. 7 0
      src/stores/screen.js

+ 3 - 2
src/api/screen.js

@@ -117,7 +117,8 @@ export async function getBroadcastContent() {
 // ============================================
 
 // 开发测试开关:启用后跳过真实接口,始终返回 Mock 播报数据
-const ENABLE_BROADCAST_MOCK = true
+// 正式环境应设置为 false,使用真实后端接口
+const ENABLE_BROADCAST_MOCK = false
 
 /**
  * 获取当前播报状态
@@ -137,7 +138,7 @@ export async function getCurrentBroadcast() {
         content: '欢迎使用迎宾巡逻安防机器人,当前正在测试播报插播功能。播报结束后,系统将自动恢复原来的待机播放状态。',
         contentType: 'notice',
         level: 'normal',
-        audioUrl: '/test-audio/broadcast-test.mp3',
+        audioUrl: 'http://192.168.0.30:8080/profile/audio/tts/315e916026ab49e8aeeb7e25a2713c29.wav',
         audioDuration: 8,
         playMode: 'once',
         startTime: new Date().toISOString(),

+ 1 - 1
src/components/BroadcastOverlay.vue

@@ -13,7 +13,7 @@
         <div class="bar-text">{{ broadcast?.content }}</div>
       </div>
 
-      <!-- 右侧音频波纹 -->
+      <!-- 右侧音频波纹(播放中时动画) -->
       <div class="bar-wave">
         <span></span>
         <span></span>

+ 21 - 0
src/components/IdlePlayer.vue

@@ -198,6 +198,8 @@ const broadcastAudio = ref(null)
 const lastPlayedBroadcastKey = ref('')
 // 播报开始时缓存一份 snapshot,结束时 ack 用(避免 markBroadcastFinishedLocally 清空后读取不到)
 const lastBroadcastSnapshot = ref(null)
+// 标记播报处理状态,避免 onerror 和 onended 重复触发
+const isBroadcastProcessed = ref(false)
 
 const actualMode = computed(() => screenStore.actualIdleMode)
 
@@ -483,6 +485,9 @@ watch(actualMode, (newMode) => {
 const playBroadcastAudio = (audioUrl) => {
   if (!audioUrl) return
 
+  // 重置处理标志
+  isBroadcastProcessed.value = false
+
   // 停止之前的音频
   stopBroadcastAudio()
 
@@ -520,6 +525,8 @@ const playBroadcastAudio = (audioUrl) => {
  */
 const stopBroadcastAudio = () => {
   if (broadcastAudio.value) {
+    broadcastAudio.value.onended = null
+    broadcastAudio.value.onerror = null
     broadcastAudio.value.pause()
     broadcastAudio.value.src = ''
     broadcastAudio.value = null
@@ -530,6 +537,13 @@ const stopBroadcastAudio = () => {
  * 播报音频播放结束
  */
 const onBroadcastEnded = () => {
+  // 防止 onerror 和 onended 同时触发导致的重复处理
+  if (isBroadcastProcessed.value) {
+    console.log('[Broadcast] 播报已处理完毕,忽略重复的 onended')
+    return
+  }
+  isBroadcastProcessed.value = true
+
   console.log('[Broadcast] onBroadcastEnded 触发,准备恢复素材', {
     actualMode: actualMode.value,
     mediaType: currentMedia.value?.type,
@@ -555,6 +569,13 @@ const onBroadcastEnded = () => {
  * 播报音频播放失败
  */
 const onBroadcastError = () => {
+  // 防止 onerror 和 onended 同时触发导致的重复处理
+  if (isBroadcastProcessed.value) {
+    console.log('[Broadcast] 播报已处理完毕,忽略重复的 onerror')
+    return
+  }
+  isBroadcastProcessed.value = true
+
   console.warn('[Broadcast] onBroadcastError 触发,准备恢复素材', {
     actualMode: actualMode.value,
     mediaType: currentMedia.value?.type,

+ 7 - 0
src/stores/screen.js

@@ -303,6 +303,13 @@ export const useScreenStore = defineStore('screen', () => {
       const res = await api.getCurrentBroadcast()
       const data = res && res.data !== undefined ? res.data : res
       const key = getBroadcastKey(data)
+
+      // 音频正在播放时,不清空已完成记录,避免状态被轮询错误清空
+      if (isBroadcastAudioPlaying.value) {
+        console.log('[Broadcast] 音频正在播放,忽略后端的 broadcasting=false 状态')
+        return
+      }
+
       if (data?.broadcasting === true && key && finishedBroadcastKeys.value.has(key)) {
         console.log('[Broadcast] 已播放完成的播报,本地忽略:', key)
         return