useAgoraSTTManager.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import { useState, useCallback, useEffect } from 'react';
  2. import { useAgoraSTT } from '../AgoraSTTProvider';
  3. import { toast } from 'sonner';
  4. export interface TranscriptionResult {
  5. text: string;
  6. isFinal: boolean;
  7. timestamp: number;
  8. confidence?: number;
  9. }
  10. export interface AgoraSTTState {
  11. isConnected: boolean;
  12. isRecording: boolean;
  13. isTranscribing: boolean;
  14. isConnecting: boolean;
  15. error: string | null;
  16. transcriptionResults: TranscriptionResult[];
  17. currentTranscription: string;
  18. microphonePermission: 'granted' | 'denied' | 'prompt';
  19. }
  20. export interface UseAgoraSTTManagerResult {
  21. state: AgoraSTTState;
  22. joinChannel: (userId: string, channel: string, userName: string) => Promise<void>;
  23. leaveChannel: () => Promise<void>;
  24. startRecording: (languages: { value: string; label: string }[]) => Promise<void>;
  25. stopRecording: () => Promise<void>;
  26. clearTranscriptions: () => void;
  27. }
  28. export const useAgoraSTTManager = (): UseAgoraSTTManagerResult => {
  29. const { sttManager } = useAgoraSTT();
  30. const [state, setState] = useState<AgoraSTTState>({
  31. isConnected: false,
  32. isRecording: false,
  33. isTranscribing: false,
  34. isConnecting: false,
  35. error: null,
  36. transcriptionResults: [],
  37. currentTranscription: '',
  38. microphonePermission: 'prompt'
  39. });
  40. const updateState = useCallback((updates: Partial<AgoraSTTState>) => {
  41. setState(prev => ({ ...prev, ...updates }));
  42. }, []);
  43. const setError = useCallback((error: string | null) => {
  44. updateState({ error });
  45. }, [updateState]);
  46. const joinChannel = useCallback(async (userId: string, channel: string, userName: string): Promise<void> => {
  47. try {
  48. updateState({ error: null, isConnecting: true });
  49. // 初始化 STT 管理器
  50. await sttManager.init({ userId, channel, userName });
  51. updateState({
  52. isConnected: true,
  53. isConnecting: false,
  54. error: null
  55. });
  56. toast.success('成功加入频道');
  57. } catch (error) {
  58. const errorMessage = error instanceof Error ? error.message : '未知错误';
  59. setError(`加入频道失败: ${errorMessage}`);
  60. updateState({ isConnecting: false });
  61. toast.error('加入频道失败');
  62. }
  63. }, [sttManager, updateState, setError]);
  64. const leaveChannel = useCallback(async (): Promise<void> => {
  65. try {
  66. await sttManager.destroy();
  67. updateState({
  68. isConnected: false,
  69. isRecording: false,
  70. isTranscribing: false,
  71. currentTranscription: ''
  72. });
  73. toast.success('已离开频道');
  74. } catch (error) {
  75. console.error('离开频道失败:', error);
  76. toast.error('离开频道失败');
  77. }
  78. }, [sttManager, updateState]);
  79. const checkMicrophonePermission = useCallback(async (): Promise<boolean> => {
  80. try {
  81. const permissionStatus = await navigator.permissions.query({ name: 'microphone' as PermissionName });
  82. updateState({ microphonePermission: permissionStatus.state as 'granted' | 'denied' | 'prompt' });
  83. if (permissionStatus.state === 'denied') {
  84. setError('麦克风权限已被拒绝,请在浏览器设置中启用');
  85. return false;
  86. }
  87. return true;
  88. } catch (error) {
  89. console.warn('Microphone permission API not supported');
  90. return true;
  91. }
  92. }, [updateState, setError]);
  93. const requestMicrophonePermission = useCallback(async (): Promise<boolean> => {
  94. try {
  95. const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  96. stream.getTracks().forEach(track => track.stop());
  97. updateState({ microphonePermission: 'granted' });
  98. return true;
  99. } catch (error) {
  100. updateState({ microphonePermission: 'denied' });
  101. setError('麦克风权限请求被拒绝,请允许访问麦克风');
  102. return false;
  103. }
  104. }, [updateState, setError]);
  105. const startRecording = useCallback(async (languages: { value: string; label: string }[]): Promise<void> => {
  106. if (!state.isConnected) {
  107. setError('未连接到 Agora 频道');
  108. return;
  109. }
  110. // 检查麦克风权限
  111. if (!(await checkMicrophonePermission())) {
  112. return;
  113. }
  114. // 如果权限是prompt状态,请求权限
  115. if (state.microphonePermission === 'prompt') {
  116. if (!(await requestMicrophonePermission())) {
  117. return;
  118. }
  119. }
  120. try {
  121. // 转换语言格式为 STT 管理器期望的格式
  122. const sttLanguages = languages.map(lang => ({
  123. source: lang.value,
  124. target: []
  125. }));
  126. // 开始转录
  127. await sttManager.startTranscription({ languages: sttLanguages });
  128. updateState({
  129. isRecording: true,
  130. isTranscribing: true,
  131. error: null
  132. });
  133. toast.success('开始录音');
  134. } catch (error) {
  135. const errorMessage = error instanceof Error ? error.message : '未知错误';
  136. setError(`开始录音失败: ${errorMessage}`);
  137. toast.error('开始录音失败');
  138. }
  139. }, [state.isConnected, state.microphonePermission, sttManager, checkMicrophonePermission, requestMicrophonePermission, updateState, setError]);
  140. const stopRecording = useCallback(async (): Promise<void> => {
  141. try {
  142. await sttManager.stopTranscription();
  143. updateState({
  144. isRecording: false,
  145. isTranscribing: false
  146. });
  147. toast.success('停止录音');
  148. } catch (error) {
  149. console.error('停止录音失败:', error);
  150. toast.error('停止录音失败');
  151. }
  152. }, [sttManager, updateState]);
  153. const clearTranscriptions = useCallback((): void => {
  154. updateState({
  155. transcriptionResults: [],
  156. currentTranscription: ''
  157. });
  158. }, [updateState]);
  159. // 监听转录结果
  160. useEffect(() => {
  161. // TODO: 实现监听 STT 管理器的转录结果事件
  162. // sttManager.on('transcription', (result) => {
  163. // const newResult: TranscriptionResult = {
  164. // text: result.text,
  165. // isFinal: result.isFinal,
  166. // timestamp: Date.now(),
  167. // confidence: result.confidence
  168. // };
  169. //
  170. // setState(prev => ({
  171. // ...prev,
  172. // transcriptionResults: [...prev.transcriptionResults, newResult],
  173. // currentTranscription: result.isFinal ? '' : result.text
  174. // }));
  175. // });
  176. // 临时模拟转录结果
  177. if (state.isRecording && state.isConnected) {
  178. const interval = setInterval(() => {
  179. if (Math.random() > 0.7) {
  180. const newResult: TranscriptionResult = {
  181. text: `模拟转录文本 ${Date.now()}`,
  182. isFinal: Math.random() > 0.8,
  183. timestamp: Date.now(),
  184. confidence: Math.random() * 0.5 + 0.5
  185. };
  186. setState(prev => ({
  187. ...prev,
  188. transcriptionResults: [...prev.transcriptionResults, newResult],
  189. currentTranscription: newResult.isFinal ? '' : newResult.text
  190. }));
  191. }
  192. }, 2000);
  193. return () => clearInterval(interval);
  194. }
  195. }, [state.isRecording, state.isConnected]);
  196. return {
  197. state,
  198. joinChannel,
  199. leaveChannel,
  200. startRecording,
  201. stopRecording,
  202. clearTranscriptions
  203. };
  204. };