sources/webrtc-reviewed/src_front/src/webrtc/session.ts

Текстовый файл из учебного комплекта, не свидетельство установленного runtime. Вернуться к учебнику · Исходный файл

   1 import type { AppConfig } from "../config/appConfig";
   2 import type { AssistantElements } from "../ui/elements";
   3 import type { AudioState } from "../audio/audioState";
   4 import { startMic, stopMic } from "../audio/mic";
   5 import { negotiate } from "./negotiate";
   6 import { createEnergyVad, type VadInstance } from "../audio/vad";
   7 import { startWaveforms } from "../ui/waves";
   8 
   9 type Session = {
  10   peer: RTCPeerConnection;
  11   sender: RTCRtpSender;
  12   micStream: MediaStream;
  13   vad: VadInstance | null;
  14   wavesStop: (() => void) | null;
  15 };
  16 
  17 let session: Session | null = null;
  18 
  19 export async function connectSession(
  20   config: AppConfig,
  21   el: AssistantElements,
  22   audio: AudioState,
  23   onVadText: (t: string) => void,
  24   onSpeechActivityChange?: (active: boolean) => void,
  25   opts?: { sessionId?: string | null },
  26 ): Promise<string | null> {
  27   if (session) return null;
  28 
  29   const micStream = await startMic(audio);
  30   let peer: RTCPeerConnection | null = null;
  31   let vad: VadInstance | null = null;
  32   let wavesStop: (() => void) | null = null;
  33 
  34   try {
  35     peer = new RTCPeerConnection({
  36       iceServers: config.webrtc.iceServers,
  37       iceCandidatePoolSize: config.webrtc.iceCandidatePoolSize,
  38       bundlePolicy: config.webrtc.bundlePolicy,
  39       rtcpMuxPolicy: config.webrtc.rtcpMuxPolicy,
  40     });
  41 
  42     peer.ontrack = (e) => {
  43       if (el.remoteAudio) el.remoteAudio.srcObject = e.streams[0];
  44     };
  45 
  46     const track = micStream.getAudioTracks()[0];
  47     const sender = peer.addTrack(track, micStream);
  48 
  49     if (audio.vadEnabled) {
  50       vad = createEnergyVad(
  51         track,
  52         config,
  53         () => audio.vadThreshold,
  54         (v) => onVadText(`${v} dBFS`),
  55         onSpeechActivityChange,
  56       );
  57       await sender.replaceTrack(vad.track);
  58     }
  59 
  60     const waves = startWaveforms(el, config, micStream);
  61     wavesStop = waves.stop;
  62 
  63     const sessionId = await negotiate(peer, config, opts);
  64 
  65     session = { peer, sender, micStream, vad, wavesStop };
  66     return sessionId;
  67   } catch (error) {
  68     try {
  69       vad?.stop();
  70     } catch {
  71       // Preserve the connection error while attempting every cleanup step.
  72     }
  73     try {
  74       wavesStop?.();
  75     } catch {
  76       // Preserve the connection error while attempting every cleanup step.
  77     }
  78     try {
  79       stopMic(micStream);
  80     } catch {
  81       // Preserve the connection error while attempting every cleanup step.
  82     }
  83     try {
  84       peer?.close();
  85     } catch {
  86       // Preserve the connection error while attempting every cleanup step.
  87     }
  88     throw error;
  89   }
  90 }
  91 
  92 export function disconnectSession(): void {
  93   if (!session) return;
  94 
  95   session.vad?.stop();
  96   session.wavesStop?.();
  97 
  98   stopMic(session.micStream);
  99   session.peer.close();
 100 
 101   session = null;
 102 }
 103 
 104 export function isConnected(): boolean {
 105   return !!session;
 106 }
 107 
 108 export function setMicEnabled(enabled: boolean): void {
 109   if (!session) return;
 110   const track = session.sender.track;
 111   if (track) track.enabled = enabled;
 112 }