Просмотр исходного кода

📦 build(deps): 降级Agora SDK版本并优化STT调试日志

- 将agora-rtc-sdk-ng从4.24.0降级至4.20.0
- 将agora-rtm-sdk替换为agora-rtm 2.1.9版本
- 更新相关依赖配置和锁文件

🐛 fix(stt): 增强STT功能调试日志和错误处理

- 为AgoraSTT组件添加详细调试日志标签【调试】
- 优化RTC连接状态监控和错误处理【稳定性】
- 改进转录任务冲突时的自动恢复机制【容错】
- 增强音频轨道创建和发布的事件监听【监控】

🔧 chore(config): 更新RTM包导入路径

- 将agora-rtm-sdk导入路径统一改为agora-rtm
- 更新RTM配置和类型导入【重构】
yourname 4 месяцев назад
Родитель
Сommit
4425181a1e

+ 2 - 2
package.json

@@ -68,8 +68,8 @@
     "@radix-ui/react-tooltip": "^1.2.7",
     "@tanstack/react-query": "^5.83.0",
     "@types/node-cron": "^3.0.11",
-    "agora-rtc-sdk-ng": "^4.24.0",
-    "agora-rtm-sdk": "^2.2.2",
+    "agora-rtc-sdk-ng": "4.20.0",
+    "agora-rtm": "2.1.9",
     "agora-token": "^2.0.5",
     "axios": "^1.11.0",
     "bcrypt": "^6.0.0",

+ 25 - 21
pnpm-lock.yaml

@@ -114,11 +114,11 @@ importers:
         specifier: ^3.0.11
         version: 3.0.11
       agora-rtc-sdk-ng:
-        specifier: ^4.24.0
-        version: 4.24.0(debug@4.4.1)
-      agora-rtm-sdk:
-        specifier: ^2.2.2
-        version: 2.2.2(agora-rtc-sdk-ng@4.24.0(debug@4.4.1))
+        specifier: 4.20.0
+        version: 4.20.0(debug@4.4.1)
+      agora-rtm:
+        specifier: 2.1.9
+        version: 2.1.9(debug@4.4.1)
       agora-token:
         specifier: ^2.0.5
         version: 2.0.5
@@ -1898,16 +1898,14 @@ packages:
     engines: {node: '>=0.4.0'}
     hasBin: true
 
-  agora-rtc-sdk-ng@4.24.0:
-    resolution: {integrity: sha512-2apG/07EtsuX21ncSF77q+dr6/kDgu9B/RpKtstCtaq46l4/Eraoecewi4zXRUCY3Im+8dzTIXx6jUwyPdxdHQ==}
+  agora-rtc-sdk-ng@4.20.0:
+    resolution: {integrity: sha512-jgMn6NXuFKQ0nVkLP2hGn+Asxbx+DSV0gn5PY6X0iF7LC2xqsqEzGhehLASxliS81JjhMNKE72hHEzrQn0qtvQ==}
 
   agora-rte-extension@1.2.4:
     resolution: {integrity: sha512-0ovZz1lbe30QraG1cU+ji7EnQ8aUu+Hf3F+a8xPml3wPOyUQEK6CTdxV9kMecr9t+fIDrGeW7wgJTsM1DQE7Nw==}
 
-  agora-rtm-sdk@2.2.2:
-    resolution: {integrity: sha512-A1vT/JmX4le70SO0QVgqBic6FAvR4RbdswXa8r+nea6Sd0wWGYg9HqA3poBve9mjRyK56AaOL38Z4vxZLRBcFA==}
-    peerDependencies:
-      agora-rtc-sdk-ng: 4.23.0
+  agora-rtm@2.1.9:
+    resolution: {integrity: sha512-+xQQeacNZVCA84DsVwS/OKh8HubioeTaw132DZPpq6H0/JuA7PVqdeDMpCbH0KAqySE9lqeWs/jq9nza1O1aBA==}
 
   agora-token@2.0.5:
     resolution: {integrity: sha512-0NcbzC3iuutlksv3b4bCMKHrW3pko6gdiGEMRo6APDice24kfXAuWyAlG9hRBrrPBVDShwm9/GUz2Scd3zuZQw==}
@@ -2010,6 +2008,9 @@ packages:
     resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==}
     engines: {node: '>= 6.0.0'}
 
+  axios@0.27.2:
+    resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
+
   axios@1.11.0:
     resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==}
 
@@ -3257,9 +3258,6 @@ packages:
   package-json-from-dist@1.0.1:
     resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
 
-  pako@2.1.0:
-    resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
-
   parent-module@1.0.1:
     resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
     engines: {node: '>=6'}
@@ -5662,15 +5660,14 @@ snapshots:
 
   acorn@8.15.0: {}
 
-  agora-rtc-sdk-ng@4.24.0(debug@4.4.1):
+  agora-rtc-sdk-ng@4.20.0(debug@4.4.1):
     dependencies:
       '@agora-js/media': 4.24.0(debug@4.4.1)
       '@agora-js/report': 4.24.0(debug@4.4.1)
       '@agora-js/shared': 4.24.0(debug@4.4.1)
       agora-rte-extension: 1.2.4
-      axios: 1.11.0(debug@4.4.1)
+      axios: 0.27.2(debug@4.4.1)
       formdata-polyfill: 4.0.10
-      pako: 2.1.0
       ua-parser-js: 0.7.41
       webrtc-adapter: 8.2.0
     transitivePeerDependencies:
@@ -5678,9 +5675,11 @@ snapshots:
 
   agora-rte-extension@1.2.4: {}
 
-  agora-rtm-sdk@2.2.2(agora-rtc-sdk-ng@4.24.0(debug@4.4.1)):
+  agora-rtm@2.1.9(debug@4.4.1):
     dependencies:
-      agora-rtc-sdk-ng: 4.24.0(debug@4.4.1)
+      agora-rtc-sdk-ng: 4.20.0(debug@4.4.1)
+    transitivePeerDependencies:
+      - debug
 
   agora-token@2.0.5:
     dependencies:
@@ -5801,6 +5800,13 @@ snapshots:
   aws-ssl-profiles@1.1.2:
     optional: true
 
+  axios@0.27.2(debug@4.4.1):
+    dependencies:
+      follow-redirects: 1.15.9(debug@4.4.1)
+      form-data: 4.0.4
+    transitivePeerDependencies:
+      - debug
+
   axios@1.11.0(debug@4.4.1):
     dependencies:
       follow-redirects: 1.15.9(debug@4.4.1)
@@ -7136,8 +7142,6 @@ snapshots:
 
   package-json-from-dist@1.0.1: {}
 
-  pako@2.1.0: {}
-
   parent-module@1.0.1:
     dependencies:
       callsites: 3.1.0

+ 3 - 1
src/client/admin/components/agora-stt/AgoraSTTProvider.tsx

@@ -40,13 +40,15 @@ export const AgoraSTTProvider: React.FC<AgoraSTTProviderProps> = ({ children })
   // 监听转录结果事件
   useEffect(() => {
     const handleTextStreamReceived = (textstream: ITextstream) => {
-      console.debug('textstream', textstream)
+      console.debug('[AgoraSTT:Provider] textstreamReceived event:', textstream)
       setTranscriptionResults(prev => [...prev, textstream])
     }
 
+    console.debug('[AgoraSTT:Provider] Setting up textstreamReceived event listener')
     rtcManagerRef.current.on('textstreamReceived', handleTextStreamReceived)
 
     return () => {
+      console.debug('[AgoraSTT:Provider] Removing textstreamReceived event listener')
       rtcManagerRef.current.off('textstreamReceived', handleTextStreamReceived)
     }
   }, [])

+ 46 - 5
src/client/admin/components/agora-stt/common/request.ts

@@ -26,18 +26,31 @@ export async function apiGetAgoraToken(config: { uid: string | number; channel:
     }
 
     const { uid, channel } = config
+    console.debug('[AgoraSTT:API] Getting Agora token for:', { type: 'rtc', channel, userId: uid.toString() })
+
     const response = await agoraClient.token.$get({
       query: { type: 'rtc', channel, userId: uid.toString() }
     })
 
     if (!response.ok) {
+      console.error('[AgoraSTT:API] Token获取失败,状态码:', response.status)
       throw new Error('Token获取失败')
     }
 
     const data = await response.json()
+    console.debug('[AgoraSTT:API] Token获取成功,包含字段:', Object.keys(data))
+
+    // 检查Token的过期时间信息
+    if (data.expiresIn) {
+      console.debug('[AgoraSTT:API] Token过期时间:', data.expiresIn, '秒')
+    }
+    if (data.expiresAt) {
+      console.debug('[AgoraSTT:API] Token过期时间戳:', data.expiresAt)
+    }
+
     return data.token
   } catch (error) {
-    console.error('获取Agora Token失败:', error)
+    console.error('[AgoraSTT:API] 获取Agora Token失败:', error)
     throw error
   }
 }
@@ -136,6 +149,8 @@ export const apiSTTStartTranscription = async (options: {
   token: string
 }): Promise<{ taskId: string }> => {
   try {
+    console.debug('[AgoraSTT:API] Starting transcription:', options)
+
     // 获取配置
     if (!agoraConfig) {
       await fetchAgoraConfig()
@@ -143,10 +158,13 @@ export const apiSTTStartTranscription = async (options: {
 
     const { channel, languages, token, uid } = options
     const url = `${gatewayAddress}/v1/projects/${agoraConfig!.appId}/rtsc/speech-to-text/tasks?builderToken=${token}`
+    console.debug('[AgoraSTT:API] STT start URL:', url)
+
     let subBotToken = null
     let pubBotToken = null
 
     // 获取Bot Token
+    console.debug('[AgoraSTT:API] Getting bot tokens')
     const tokenData = await Promise.all([
       apiGetAgoraToken({
         uid: SUB_BOT_UID,
@@ -159,6 +177,7 @@ export const apiSTTStartTranscription = async (options: {
     ])
     subBotToken = tokenData[0]
     pubBotToken = tokenData[1]
+    console.debug('[AgoraSTT:API] Bot tokens acquired')
 
     const body: any = {
       languages: languages.map((item) => item.source),
@@ -179,6 +198,9 @@ export const apiSTTStartTranscription = async (options: {
         languages: languages.filter((item) => item.target.length),
       }
     }
+
+    console.debug('[AgoraSTT:API] Request body:', body)
+
     const res = await fetch(url, {
       method: "POST",
       headers: {
@@ -190,13 +212,23 @@ export const apiSTTStartTranscription = async (options: {
       },
       body: JSON.stringify(body),
     })
+
+    console.debug('[AgoraSTT:API] Response status:', res.status)
     const responseData = await res.json()
+    console.debug('[AgoraSTT:API] Response data:', responseData)
+
     if (res.status !== 200) {
-      throw new Error((responseData as any)?.message || "start transcription failed")
+      const errorMessage = (responseData as any)?.message || "start transcription failed"
+      const error = new Error(errorMessage)
+      // 将完整的响应数据附加到错误对象上,便于后续处理
+      ;(error as any).responseData = responseData
+      throw error
     }
+
+    console.debug('[AgoraSTT:API] Transcription started successfully, taskId:', responseData.taskId)
     return responseData as { taskId: string }
   } catch (error) {
-    console.error('STT转录启动失败:', error)
+    console.error('[AgoraSTT:API] STT转录启动失败:', error)
     throw error
   }
 }
@@ -238,6 +270,8 @@ export const apiSTTQueryTranscription = async (options: {
   channel: string
 }) => {
   try {
+    console.debug('[AgoraSTT:API] Querying transcription:', options)
+
     // 获取配置
     if (!agoraConfig) {
       await fetchAgoraConfig()
@@ -245,6 +279,8 @@ export const apiSTTQueryTranscription = async (options: {
 
     const { taskId, token, uid, channel } = options
     const url = `${gatewayAddress}/v1/projects/${agoraConfig!.appId}/rtsc/speech-to-text/tasks/${taskId}?builderToken=${token}`
+    console.debug('[AgoraSTT:API] STT query URL:', url)
+
     const res = await fetch(url, {
       method: "GET",
       headers: {
@@ -255,9 +291,14 @@ export const apiSTTQueryTranscription = async (options: {
         }),
       },
     })
-    return await res.json()
+
+    console.debug('[AgoraSTT:API] Query response status:', res.status)
+    const result = await res.json()
+    console.debug('[AgoraSTT:API] Query response data:', result)
+
+    return result
   } catch (error) {
-    console.error('STT转录查询失败:', error)
+    console.error('[AgoraSTT:API] STT转录查询失败:', error)
     throw error
   }
 }

+ 4 - 2
src/client/admin/components/agora-stt/manager/parser/parser.ts

@@ -9,12 +9,14 @@ export class Parser extends AGEventEmitter<ParserEvents> {
   }
 
   praseData(data: any) {
+    console.debug('[AgoraSTT:Parser] Received data to parse:', data)
     // @ts-ignore
     const textstream = protoRoot.Agora.SpeechToText.lookup("Text").decode(data) as ITextstream
     if (!textstream) {
-      return console.warn("Prase data failed.")
+      console.warn('[AgoraSTT:Parser] Parse data failed')
+      return
     }
-    console.log("[test] textstream praseData", textstream)
+    console.debug('[AgoraSTT:Parser] Successfully parsed textstream:', textstream)
     this.emit("textstreamReceived", textstream)
   }
 }

+ 67 - 8
src/client/admin/components/agora-stt/manager/rtc/rtc.ts

@@ -24,37 +24,66 @@ export class RtcManager extends AGEventEmitter<RtcEvents> {
   }
 
   async join({ channel, userId }: { channel: string; userId: number | string }) {
+    console.debug('[AgoraSTT:RTC] Joining channel:', { channel, userId })
     if (!this._joined) {
       // 一次性获取Token和配置
+      console.debug('[AgoraSTT:RTC] Getting RTC token')
       const response = await agoraClient.token.$get({
         query: { type: 'rtc', channel, userId: userId.toString() }
       })
 
       if (!response.ok) {
+        console.error('[AgoraSTT:RTC] Failed to get RTC token:', response.status)
         throw new Error('配置获取失败')
       }
 
       const data = await response.json()
+      console.debug('[AgoraSTT:RTC] RTC token received, joining channel')
       await this.client?.join(data.appId, channel, data.token, userId)
       this._joined = true
+      console.debug('[AgoraSTT:RTC] Successfully joined RTC channel')
+    } else {
+      console.debug('[AgoraSTT:RTC] Already joined, skipping join')
     }
   }
 
   async createTracks() {
-    // 对于STT功能,只需要音频轨道
-    const audioTrack = await AgoraRTC.createMicrophoneAudioTrack({
-      AGC: false,
-    })
-    this.localTracks.audioTrack = audioTrack
-    this.localTracks.videoTrack = undefined // 不需要视频轨道
-    this.emit("localUserChanged", this.localTracks)
+    console.debug('[AgoraSTT:RTC] Creating audio tracks')
+    try {
+      // 对于STT功能,只需要音频轨道
+      const audioTrack = await AgoraRTC.createMicrophoneAudioTrack({
+        AGC: false,
+      })
+
+      // 添加音频轨道事件监听器
+      audioTrack.on('track-ended', () => {
+        console.debug('[AgoraSTT:RTC] Audio track ended')
+      })
+
+      // 检查音频轨道是否正常
+      console.debug('[AgoraSTT:RTC] Audio track created:', {
+        enabled: audioTrack.enabled,
+        muted: audioTrack.muted
+      })
+
+      this.localTracks.audioTrack = audioTrack
+      this.localTracks.videoTrack = undefined // 不需要视频轨道
+      console.debug('[AgoraSTT:RTC] Audio tracks created successfully')
+      this.emit("localUserChanged", this.localTracks)
+    } catch (error) {
+      console.error('[AgoraSTT:RTC] Failed to create audio tracks:', error)
+      throw error
+    }
   }
 
   async publish() {
+    console.debug('[AgoraSTT:RTC] Publishing audio tracks')
     if (this.localTracks.audioTrack) {
       await this.client.publish([this.localTracks.audioTrack])
+      console.debug('[AgoraSTT:RTC] Audio tracks published successfully')
     } else {
       const msg = "audioTrack is undefined"
+      console.error('[AgoraSTT:RTC] Cannot publish: audioTrack is undefined')
       throw new Error(msg)
     }
   }
@@ -85,10 +114,15 @@ export class RtcManager extends AGEventEmitter<RtcEvents> {
 
   // -------------- private methods --------------
   _listenRtcEvents() {
+    console.debug('[AgoraSTT:RTC] Setting up RTC event listeners')
+
     this.client.on("network-quality", (quality) => {
+      console.debug('[AgoraSTT:RTC] network-quality event:', quality)
       this.emit("networkQuality", quality)
     })
+
     this.client.on("user-published", async (user, mediaType) => {
+      console.debug('[AgoraSTT:RTC] user-published event:', { userId: user.uid, mediaType })
       await this.client.subscribe(user, mediaType)
       if (mediaType === "audio") {
         this._playAudio(user.audioTrack)
@@ -99,7 +133,9 @@ export class RtcManager extends AGEventEmitter<RtcEvents> {
         videoTrack: user.videoTrack,
       })
     })
+
     this.client.on("user-unpublished", async (user, mediaType) => {
+      console.debug('[AgoraSTT:RTC] user-unpublished event:', { userId: user.uid, mediaType })
       await this.client.unsubscribe(user, mediaType)
       this.emit("remoteUserChanged", {
         userId: user.uid,
@@ -107,10 +143,32 @@ export class RtcManager extends AGEventEmitter<RtcEvents> {
         videoTrack: user.videoTrack,
       })
     })
+
     this.client.on("stream-message", (_uid: UID, stream: any) => {
-      console.log('stream', stream)
+      console.debug('[AgoraSTT:RTC] stream-message received:', { uid: _uid, stream })
       parser.praseData(stream)
     })
+
+    // 添加其他可能有用的事件监听器
+    this.client.on("connection-state-change", (curState, revState, reason) => {
+      console.debug('[AgoraSTT:RTC] connection-state-change:', { curState, revState, reason })
+
+      // 记录连接断开的原因
+      if (curState === 'DISCONNECTED') {
+        console.error('[AgoraSTT:RTC] RTC连接断开,原因:', reason)
+        console.error('[AgoraSTT:RTC] 这可能导致stream-message事件无法接收')
+      }
+    })
+
+    this.client.on("user-joined", (user) => {
+      console.debug('[AgoraSTT:RTC] user-joined:', user.uid)
+    })
+
+    this.client.on("user-left", (user) => {
+      console.debug('[AgoraSTT:RTC] user-left:', user.uid)
+    })
+
+    console.debug('[AgoraSTT:RTC] RTC event listeners setup completed')
   }
 
   _playAudio(audioTrack: IMicrophoneAudioTrack | IRemoteAudioTrack | undefined) {
@@ -121,6 +179,7 @@ export class RtcManager extends AGEventEmitter<RtcEvents> {
 
   _listenParserStreamEvent() {
     parser.on("textstreamReceived", (textstream) => {
+      console.debug('[AgoraSTT:RTC] Forwarding textstreamReceived event:', textstream)
       this.emit("textstreamReceived", textstream)
     })
   }

+ 1 - 1
src/client/admin/components/agora-stt/manager/rtm/constant.ts

@@ -1,4 +1,4 @@
-import { RTMConfig } from "agora-rtm-sdk"
+import { RTMConfig } from "agora-rtm"
 
 export const DEFAULT_RTM_CONFIG: RTMConfig = {
   logLevel: "debug",

+ 36 - 5
src/client/admin/components/agora-stt/manager/rtm/rtm.ts

@@ -1,4 +1,4 @@
-import AgoraRTM, { ChannelType, RTMClient, RTMConfig, MetadataItem } from "agora-rtm-sdk"
+import AgoraRTM, { ChannelType, RTMClient, RTMConfig, MetadataItem } from "agora-rtm"
 import { mapToArray, isString, getDefaultLanguageSelect } from "../../common"
 import { AGEventEmitter } from "../events"
 import {
@@ -31,7 +31,9 @@ export class RtmManager extends AGEventEmitter<RtmEvents> {
   }
 
   async join({ channel, userId, userName }: { channel: string; userId: string; userName: string }) {
+    console.debug('[AgoraSTT:RTM] Joining RTM channel:', { channel, userId, userName })
     if (this.joined) {
+      console.debug('[AgoraSTT:RTM] Already joined, skipping')
       return
     }
     this.userId = userId
@@ -39,25 +41,31 @@ export class RtmManager extends AGEventEmitter<RtmEvents> {
     this.channel = channel
 
     // 获取RTM配置和Token
+    console.debug('[AgoraSTT:RTM] Getting RTM configuration')
     const { agoraClient } = await import("@/client/api")
     const response = await agoraClient.token.$get({
       query: { type: 'rtm', channel, userId }
     })
 
     if (!response.ok) {
+      console.error('[AgoraSTT:RTM] RTM配置获取失败')
       throw new Error('RTM配置获取失败')
     }
 
     const data = await response.json()
+    console.debug('[AgoraSTT:RTM] RTM配置获取成功')
 
     if (!this.client) {
+      this.rtmConfig.token = data.token
       this.client = new RTM(data.appId, userId, this.rtmConfig)
     }
     this._listenRtmEvents()
     // 直接使用API返回的token,避免重复调用
-    await this.client.login({ token: data.token })
+    console.debug('[AgoraSTT:RTM] Logging in to RTM')
+    await this.client.login()
     this.joined = true
     // subscribe message channel
+    console.debug('[AgoraSTT:RTM] Subscribing to channel')
     await this.client.subscribe(channel, {
       withPresence: true,
       withMetadata: true,
@@ -67,7 +75,8 @@ export class RtmManager extends AGEventEmitter<RtmEvents> {
     // update user info
     await this._updateUserInfo()
     // set lock
-    this._setLock()
+    await this._setLock()
+    console.debug('[AgoraSTT:RTM] RTM join completed successfully')
   }
 
   async updateSttData(data: ISttData) {
@@ -111,12 +120,28 @@ export class RtmManager extends AGEventEmitter<RtmEvents> {
   }
 
   async acquireLock() {
+    console.debug('[AgoraSTT:RTM] Acquiring lock for channel:', this.channel)
     // if not accquire lock, will throw error
-    return await this.client?.lock.acquireLock(this.channel, CHANNEL_TYPE, LOCK_STT)
+    try {
+      const result = await this.client?.lock.acquireLock(this.channel, CHANNEL_TYPE, LOCK_STT)
+      console.debug('[AgoraSTT:RTM] Lock acquired successfully')
+      return result
+    } catch (error) {
+      console.error('[AgoraSTT:RTM] Error acquiring lock:', error)
+      throw error
+    }
   }
 
   async releaseLock() {
-    return await this.client?.lock.releaseLock(this.channel, CHANNEL_TYPE, LOCK_STT)
+    console.debug('[AgoraSTT:RTM] Releasing lock for channel:', this.channel)
+    try {
+      const result = await this.client?.lock.releaseLock(this.channel, CHANNEL_TYPE, LOCK_STT)
+      console.debug('[AgoraSTT:RTM] Lock released successfully')
+      return result
+    } catch (error) {
+      console.error('[AgoraSTT:RTM] Error releasing lock:', error)
+      throw error
+    }
   }
 
   // --------------------- private methods ---------------------
@@ -322,9 +347,15 @@ export class RtmManager extends AGEventEmitter<RtmEvents> {
   }
 
   private async _setLock() {
+    console.debug('[AgoraSTT:RTM] Setting up lock for channel:', this.channel)
     const { lockDetails = [] } = (await this.client?.lock.getLock(this.channel, CHANNEL_TYPE)) || {}
+    console.debug('[AgoraSTT:RTM] Current lock details:', lockDetails)
     if (!lockDetails.find((v) => v.lockName === LOCK_STT)) {
+      console.debug('[AgoraSTT:RTM] Setting lock:', LOCK_STT)
       await this.client?.lock.setLock(this.channel, CHANNEL_TYPE, LOCK_STT)
+      console.debug('[AgoraSTT:RTM] Lock set successfully')
+    } else {
+      console.debug('[AgoraSTT:RTM] Lock already exists, skipping')
     }
   }
 }

+ 139 - 2
src/client/admin/components/agora-stt/manager/stt/stt.ts

@@ -48,10 +48,12 @@ export class SttManager extends AGEventEmitter<STTEvents> {
     channel: string
     userName: string
   }) {
+    console.debug('[AgoraSTT:STT] Initializing with:', { userId, channel, userName })
     this.userId = userId
     this.channel = channel
 
     // 加入RTM频道(实时消息)
+    console.debug('[AgoraSTT:STT] Joining RTM channel')
     await this.rtmManager.join({
       userId: userId + "",
       userName,
@@ -59,37 +61,57 @@ export class SttManager extends AGEventEmitter<STTEvents> {
     })
 
     // 加入RTC频道(音频传输)
+    console.debug('[AgoraSTT:STT] Joining RTC channel')
     await this.rtcManager.join({
       channel,
       userId
     })
 
     this._init = true
+    console.debug('[AgoraSTT:STT] Initialization completed')
   }
 
   async startTranscription(startOptions: STTManagerStartOptions) {
+    console.debug('[AgoraSTT:STT] Starting transcription with options:', startOptions)
     if (!this.hasInit) {
       throw new Error("please init first")
     }
     const { languages } = startOptions
     if (!languages.length) {
+      console.debug('[AgoraSTT:STT] No languages provided, skipping transcription start')
       return
     }
     // aquire lock
+    console.debug('[AgoraSTT:STT] Acquiring RTM lock')
     await this.rtmManager.acquireLock()
     try {
+      // 先停止可能存在的旧转录任务
+      if (this.option?.taskId) {
+        console.debug('[AgoraSTT:STT] Stopping existing transcription task:', this.option.taskId)
+        try {
+          await this.stopTranscription()
+          console.debug('[AgoraSTT:STT] Existing transcription stopped successfully')
+        } catch (stopError) {
+          console.warn('[AgoraSTT:STT] Failed to stop existing transcription:', stopError)
+        }
+      }
+
       // aquire token
+      console.debug('[AgoraSTT:STT] Acquiring STT token')
       const data = await apiSTTAcquireToken({
         channel: this.channel,
         uid: this.userId,
       })
       const token = data.tokenName
+      console.debug('[AgoraSTT:STT] STT token acquired:', token)
 
       // 创建音频轨道并发布到RTC频道
+      console.debug('[AgoraSTT:STT] Creating and publishing audio tracks')
       await this.rtcManager.createTracks()
       await this.rtcManager.publish()
 
       // api start
+      console.debug('[AgoraSTT:STT] Starting STT transcription via API')
       const res = await apiSTTStartTranscription({
         uid: this.userId,
         channel: this.channel,
@@ -97,11 +119,13 @@ export class SttManager extends AGEventEmitter<STTEvents> {
         token,
       })
       const { taskId } = res
+      console.debug('[AgoraSTT:STT] STT transcription started, taskId:', taskId)
       this.setOption({
         token,
         taskId,
       })
       // set rtm metadata
+      console.debug('[AgoraSTT:STT] Updating RTM metadata')
       await Promise.all([
         this.rtmManager.updateLanguages(languages),
         this.rtmManager.updateSttData({
@@ -112,7 +136,67 @@ export class SttManager extends AGEventEmitter<STTEvents> {
           duration: EXPERIENCE_DURATION,
         }),
       ])
+      console.debug('[AgoraSTT:STT] Transcription start completed successfully')
+
+      // 启动转录状态监控
+      this._startTranscriptionMonitor()
     } catch (err) {
+      console.error('[AgoraSTT:STT] Error starting transcription:', err)
+
+      // 如果是409错误(任务已存在),尝试查询任务状态
+      if (err instanceof Error && err.message && err.message.includes('The task has been started')) {
+        console.debug('[AgoraSTT:STT] Task already exists, querying status')
+        console.debug('[AgoraSTT:STT] Error message:', err.message)
+
+        try {
+          // 尝试多种可能的taskId格式
+          let taskIdMatch = err.message.match(/taskId is: ([a-f0-9]+)/)
+          if (!taskIdMatch) {
+            taskIdMatch = err.message.match(/taskId: ([a-f0-9]+)/)
+          }
+          if (!taskIdMatch) {
+            taskIdMatch = err.message.match(/taskId=([a-f0-9]+)/)
+          }
+
+          // 尝试从错误对象的响应数据中提取tokenName
+          let existingToken = ''
+          const anyErr = err as any
+          if (anyErr.responseData && anyErr.responseData.tokenName) {
+            existingToken = anyErr.responseData.tokenName
+            console.debug('[AgoraSTT:STT] Using token from response data:', existingToken)
+          } else {
+            // 如果响应数据中没有tokenName,尝试从错误消息中提取
+            let tokenMatch = err.message.match(/tokenName: ([^,]+)/)
+            if (!tokenMatch) {
+              tokenMatch = err.message.match(/tokenName=([^,]+)/)
+            }
+            existingToken = tokenMatch && tokenMatch[1] ? tokenMatch[1] : this.option?.token || ''
+            console.debug('[AgoraSTT:STT] Using token:', existingToken ? 'extracted from error message' : 'from existing option')
+          }
+
+          if (taskIdMatch && taskIdMatch[1]) {
+            const existingTaskId = taskIdMatch[1]
+
+            console.debug('[AgoraSTT:STT] Found existing taskId:', existingTaskId)
+
+            // 使用现有的taskId和token继续
+            this.setOption({
+              token: existingToken,
+              taskId: existingTaskId,
+            })
+
+            // 启动转录状态监控
+            this._startTranscriptionMonitor()
+            console.debug('[AgoraSTT:STT] Using existing transcription task')
+            return
+          } else {
+            console.warn('[AgoraSTT:STT] Could not extract taskId from error message')
+          }
+        } catch (queryError) {
+          console.error('[AgoraSTT:STT] Error handling existing task:', queryError)
+        }
+      }
+
       await this.rtmManager.releaseLock()
       throw err
     }
@@ -120,12 +204,14 @@ export class SttManager extends AGEventEmitter<STTEvents> {
   }
 
   async stopTranscription() {
+    console.debug('[AgoraSTT:STT] Stopping transcription')
     if (!this.hasInit) {
       throw new Error("please init first")
     }
     const { taskId, token } = this.option || {}
     if (!taskId) {
-      throw new Error("taskId is not found")
+      console.debug('[AgoraSTT:STT] No taskId found, nothing to stop')
+      return
     }
     if (!token) {
       throw new Error("token is not found")
@@ -134,21 +220,30 @@ export class SttManager extends AGEventEmitter<STTEvents> {
     await this.rtmManager.acquireLock()
     try {
       // api stop
+      console.debug('[AgoraSTT:STT] Stopping transcription via API')
       await apiSTTStopTranscription({
         taskId,
         token,
         uid: this.userId,
         channel: this.channel,
       })
+      console.debug('[AgoraSTT:STT] Transcription stopped via API')
 
       // 停止音频轨道
+      console.debug('[AgoraSTT:STT] Unpublishing audio tracks')
       await this.rtcManager.unpublish()
 
       // set rtm metadata
+      console.debug('[AgoraSTT:STT] Updating RTM metadata to end status')
       await this.rtmManager.updateSttData({
         status: "end",
       })
+
+      // 清除选项
+      this.removeOption()
+      console.debug('[AgoraSTT:STT] Transcription stopped successfully')
     } catch (err) {
+      console.error('[AgoraSTT:STT] Error stopping transcription:', err)
       await this.rtmManager.releaseLock()
       throw err
     }
@@ -156,6 +251,7 @@ export class SttManager extends AGEventEmitter<STTEvents> {
   }
 
   async queryTranscription() {
+    console.debug('[AgoraSTT:STT] Querying transcription status')
     const { taskId, token } = this.option || {}
     if (!taskId) {
       throw new Error("taskId is not found")
@@ -164,12 +260,14 @@ export class SttManager extends AGEventEmitter<STTEvents> {
       throw new Error("token is not found")
     }
     // api query
-    return await apiSTTQueryTranscription({
+    const result = await apiSTTQueryTranscription({
       taskId,
       token,
       uid: this.userId,
       channel: this.channel,
     })
+    console.debug('[AgoraSTT:STT] Transcription query result:', result)
+    return result
   }
 
   async updateTranscription(options: { data: any; updateMaskList: string[] }) {
@@ -219,4 +317,43 @@ export class SttManager extends AGEventEmitter<STTEvents> {
   }
 
   // ------------- private -------------
+
+  private _startTranscriptionMonitor() {
+    console.debug('[AgoraSTT:STT] Starting transcription monitor')
+
+    // 定期查询转录状态
+    const monitorInterval = setInterval(async () => {
+      if (!this.hasInit || !this.option?.taskId) {
+        console.debug('[AgoraSTT:STT] Monitor stopping: no initialization or taskId')
+        clearInterval(monitorInterval)
+        return
+      }
+
+      try {
+        const status = await this.queryTranscription()
+        console.debug('[AgoraSTT:STT] Transcription monitor status:', status)
+
+        // 如果转录已停止,清除监控
+        if (status.status === 'STOPPED' || status.status === 'FAILED') {
+          console.debug('[AgoraSTT:STT] Transcription stopped, clearing monitor')
+          clearInterval(monitorInterval)
+        } else if (status.status === 'STARTED') {
+          console.debug('[AgoraSTT:STT] Transcription is running normally')
+        }
+      } catch (error) {
+        console.error('[AgoraSTT:STT] Transcription monitor error:', error)
+        // 如果查询失败,可能是任务已过期,停止监控
+        if (error instanceof Error && error.message && error.message.includes('not found')) {
+          console.debug('[AgoraSTT:STT] Task not found, stopping monitor')
+          clearInterval(monitorInterval)
+        }
+      }
+    }, 5000) // 每5秒查询一次
+
+    // 10分钟后自动停止监控
+    setTimeout(() => {
+      clearInterval(monitorInterval)
+      console.debug('[AgoraSTT:STT] Transcription monitor timeout')
+    }, 10 * 60 * 1000)
+  }
 }