1- import { PalabraClientData } from '~/PalabraClient.model' ;
1+ import { PalabraClientData , TrackSid } from '~/PalabraClient.model' ;
22import { PalabraApiClient } from '~/api/api' ;
33import { PalabraWebRtcTransport } from '~/transport/PalabraWebRtcTransport' ;
44import { PipelineConfigManager } from '~/config/PipelineConfigManager' ;
55import { TargetLangCode } from '~/utils/target' ;
66import { SourceLangCode } from '~/utils/source' ;
77import {
8+ EVENT_CONNECTION_STATE_CHANGED ,
9+ EVENT_ORIGINAL_TRACK_VOLUME_CHANGED ,
810 EVENT_REMOTE_TRACKS_UPDATE ,
911 EVENT_START_TRANSLATION ,
1012 EVENT_STOP_TRANSLATION ,
@@ -14,49 +16,70 @@ import {
1416} from '~/transport/PalabraWebRtcTransport.model' ;
1517import { PalabraBaseEventEmitter } from '~/PalabraBaseEventEmitter' ;
1618import { SessionResponse } from '~/api/api.model' ;
17- import { supportsAudioContextSetSinkId } from './utils' ;
19+ import { supportsAudioContextSetSinkId , VolumeNode } from './utils' ;
20+ import { ConnectionState } from 'livekit-client' ;
21+ import { PipelineConfig } from './config' ;
1822
1923export class PalabraClient extends PalabraBaseEventEmitter {
20- private translateFrom : string ;
21- private translateTo : string ;
24+ private translateFrom : SourceLangCode ;
25+ private translateTo : TargetLangCode ;
2226 private auth : PalabraClientData [ 'auth' ] ;
2327 private apiClient : PalabraApiClient ;
2428 private handleOriginalTrack : PalabraClientData [ 'handleOriginalTrack' ] ;
25- private originalTrack : MediaStreamTrack ;
26- public transport : PalabraWebRtcTransport ;
29+ private originalTrack : MediaStreamTrack | null = null ;
30+ private originalTrackVolumeNode : VolumeNode | null = null ;
31+ public transport : PalabraWebRtcTransport | null = null ;
2732 private transportType : PalabraClientData [ 'transportType' ] ;
2833 private configManager : PipelineConfigManager ;
2934 private audioContext : AudioContext ;
3035
36+
37+ private initialOriginalTrack : MediaStreamTrack | null = null ;
38+
3139 private shouldPlayTranslation : boolean ;
3240
33- private translationTracks = new Map < string , RemoteTrackInfo > ( ) ;
41+ private translationTracks = new Map < TrackSid , RemoteTrackInfo > ( ) ;
3442
3543 private sessionData : SessionResponse | null = null ;
3644
3745 private deviceId = '' ;
3846
47+ private translationStatus : 'init' | 'ongoing' | 'paused' | 'stopped' = 'init' ;
48+
49+ private connectionStatus : ConnectionState | 'init' = 'init' ;
50+
51+ private ignoreAudioContext : PalabraClientData [ 'ignoreAudioContext' ] ;
52+
3953 constructor ( data : PalabraClientData ) {
4054 super ( ) ;
4155
4256 this . auth = data . auth ;
4357 this . translateFrom = data . translateFrom ;
4458 this . translateTo = data . translateTo ;
4559 this . handleOriginalTrack = data . handleOriginalTrack ;
46- this . apiClient = new PalabraApiClient ( this . auth , data . apiBaseUrl ?? 'https://api.palabra.ai' ) ;
60+ this . apiClient = new PalabraApiClient ( this . auth , data . apiBaseUrl ?? 'https://api.palabra.ai' , data . intent ) ;
4761
4862 this . transportType = data . transportType ?? 'webrtc' ;
4963
5064 this . initConfig ( ) ;
5165
5266 this . shouldPlayTranslation = false ;
67+
68+ this . ignoreAudioContext = data . ignoreAudioContext ?? false ;
69+
70+ if ( data . audioContext ) {
71+ this . audioContext = data . audioContext ;
72+ }
5373 }
5474
5575 public async startTranslation ( ) : Promise < boolean > {
5676 try {
77+ this . initAudioContext ( ) ;
78+ await this . wrapOriginalTrack ( ) ;
5779 const transport = await this . createSession ( ) ;
5880 this . initTransportHandlers ( ) ;
5981 await transport . connect ( ) ;
82+ this . translationStatus = 'ongoing' ;
6083 this . emit ( EVENT_START_TRANSLATION ) ;
6184 return true ;
6285 } catch ( error ) {
@@ -72,6 +95,8 @@ export class PalabraClient extends PalabraBaseEventEmitter {
7295 this . stopPlayback ( ) ;
7396 this . closeAudioContext ( ) ;
7497 this . cleanUnusedTracks ( [ ] ) ;
98+ this . translationStatus = 'stopped' ;
99+ this . cleanupOriginalTrack ( ) ;
75100 this . emit ( EVENT_STOP_TRANSLATION ) ;
76101 }
77102
@@ -87,10 +112,33 @@ export class PalabraClient extends PalabraBaseEventEmitter {
87112 } ) ;
88113 }
89114
115+ public async pauseTranslation ( ) {
116+ await this . transport ?. pauseTask ( ) ;
117+ this . translationStatus = 'paused' ;
118+ }
119+
120+ public async resumeTranslation ( ) {
121+ await this . transport ?. resumeTask ( ) ;
122+ this . translationStatus = 'ongoing' ;
123+ }
124+
125+ public getTranslationStatus ( ) {
126+ return this . translationStatus ;
127+ }
128+
129+ public getConnectionStatus ( ) {
130+ return this . connectionStatus ;
131+ }
132+
133+ public getVolume ( language : string ) {
134+ const [ sid , remoteTrackInfo ] = Array . from ( this . translationTracks . entries ( ) ) . find ( entry => entry [ 1 ] . language === language ) ;
135+ if ( ! sid ) return null ;
136+ return remoteTrackInfo . remoteAudioTrack . getVolume ( ) ;
137+ }
138+
90139 public setVolume ( language : string , volume : number ) {
91140 this . translationTracks ?. forEach ( track => {
92141 if ( track . language === language ) {
93- console . log ( 'setting volume' , volume , 'for' , language ) ;
94142 track . remoteAudioTrack . setVolume ( volume ) ;
95143 }
96144 } ) ;
@@ -107,6 +155,7 @@ export class PalabraClient extends PalabraBaseEventEmitter {
107155 }
108156
109157 private initTransportHandlers ( ) {
158+ if ( ! this . transport ) return ;
110159 this . transport . on ( EVENT_REMOTE_TRACKS_UPDATE , ( event ) => {
111160
112161 this . cleanUnusedTracks ( event ) ;
@@ -127,6 +176,11 @@ export class PalabraClient extends PalabraBaseEventEmitter {
127176 PROXY_EVENTS . forEach ( event => {
128177 this . transport . on ( event , ( ...args ) => this . emit ( event , ...args as Parameters < PalabraEvents [ typeof event ] > ) ) ;
129178 } ) ;
179+
180+ this . transport . on ( EVENT_CONNECTION_STATE_CHANGED , ( state ) => {
181+ this . connectionStatus = state ;
182+ this . emit ( EVENT_CONNECTION_STATE_CHANGED , state ) ;
183+ } ) ;
130184 }
131185
132186 public muteOriginalTrack ( ) {
@@ -137,7 +191,39 @@ export class PalabraClient extends PalabraBaseEventEmitter {
137191 this . originalTrack . enabled = true ;
138192 }
139193
140- public async createSession ( ) {
194+ public setOriginalVolume ( volume : number ) {
195+ if ( ! this . originalTrackVolumeNode ) return ;
196+ this . originalTrackVolumeNode . setVolume ( volume ) ;
197+ this . emit ( EVENT_ORIGINAL_TRACK_VOLUME_CHANGED , volume ) ;
198+ }
199+
200+ public getOriginalVolume ( ) {
201+ return this . originalTrackVolumeNode ?. getVolume ( ) ?? 1.0 ;
202+ }
203+
204+ private async wrapOriginalTrack ( ) {
205+ this . initialOriginalTrack = await this . handleOriginalTrack ( ) ;
206+
207+ if ( this . ignoreAudioContext ) {
208+ this . originalTrack = this . initialOriginalTrack ;
209+ return ;
210+ }
211+
212+ this . originalTrackVolumeNode = new VolumeNode ( this . audioContext ) ;
213+ this . originalTrack = this . originalTrackVolumeNode . createChain ( this . initialOriginalTrack ) ;
214+ }
215+
216+ public cleanupOriginalTrack ( ) {
217+ this . originalTrackVolumeNode ?. disconnect ( ) ;
218+
219+ this . originalTrack ?. stop ( ) ;
220+ this . originalTrack = null ;
221+
222+ this . initialOriginalTrack ?. stop ( ) ;
223+ this . initialOriginalTrack = null ;
224+ }
225+
226+ protected async createSession ( ) {
141227 const sessionResponse = await this . apiClient . createStreamingSession ( ) ;
142228
143229 if ( ! sessionResponse || ! sessionResponse . ok ) {
@@ -150,10 +236,6 @@ export class PalabraClient extends PalabraBaseEventEmitter {
150236
151237 this . sessionData = sessionResponse . data ;
152238
153- this . originalTrack = await this . handleOriginalTrack ( ) ;
154-
155- this . initAudioContext ( ) ;
156-
157239 this . transport = new PalabraWebRtcTransport ( {
158240 streamUrl : sessionResponse . data . webrtc_url ,
159241 accessToken : sessionResponse . data . publisher ,
@@ -165,42 +247,62 @@ export class PalabraClient extends PalabraBaseEventEmitter {
165247 return this . transport ;
166248 }
167249
250+ public getConfigManager ( ) {
251+ return this . configManager ;
252+ }
253+
168254 public getConfig ( ) {
169255 return this . configManager . getConfig ( ) ;
170256 }
171257
172- public async deleteSession ( ) {
258+ protected async deleteSession ( ) {
173259 if ( ! this . sessionData ) {
174260 console . error ( 'No session data found' ) ;
175261 return ;
176262 }
177- await this . apiClient . deleteStreamingSession ( this . sessionData . id ) ;
178- this . sessionData = null ;
263+ try {
264+ await this . apiClient . deleteStreamingSession ( this . sessionData . id ) ;
265+ } catch ( error ) {
266+ throw error ;
267+ } finally {
268+ this . sessionData = null ;
269+ }
179270 }
180271
181272 public async setTranslateFrom ( code : PalabraClientData [ 'translateFrom' ] ) {
182273 this . translateFrom = code ;
183274 this . configManager . setSourceLanguage ( code as SourceLangCode ) ;
184- await this . transport . setTask ( this . configManager . getConfig ( ) ) ;
275+ await this . transport ? .setTask ( this . configManager . getConfig ( ) ) ;
185276 }
186277
187- public async setTranslateTo ( code : PalabraClientData [ 'translateTo' ] ) {
278+ public async setTranslateTo ( code : PalabraClientData [ 'translateTo' ] , previousCode ?: PalabraClientData [ 'translateTo' ] ) {
188279 this . translateTo = code ;
189280
190- this . configManager . getConfig ( ) . pipeline . translations
191- . map ( ( translation ) => translation . target_language )
192- . forEach ( ( target ) => {
193- this . configManager . deleteTranslationTarget ( target ) ;
194- } ) ;
281+ const translations = this . configManager . getValue ( 'translations' ) ;
195282
196- this . configManager . addTranslationTarget ( { target_language : code as TargetLangCode } ) ;
283+ if ( translations . length === 0 ) {
284+ this . configManager . addTranslationTarget ( { target_language : code as TargetLangCode } ) ;
285+ await this . transport ?. setTask ( this . configManager . getConfig ( ) ) ;
286+ return ;
287+ }
288+
289+ if ( ! previousCode ) {
290+ translations [ 0 ] . target_language = code ;
291+ await this . transport ?. setTask ( this . configManager . getConfig ( ) ) ;
292+ return ;
293+ }
197294
198- await this . transport . setTask ( this . configManager . getConfig ( ) ) ;
295+ const translation = translations . find ( ( t ) => t . target_language === previousCode ) ;
296+
297+ if ( translation ) {
298+ translation . target_language = code ;
299+ await this . transport ?. setTask ( this . configManager . getConfig ( ) ) ;
300+ }
199301 }
200302
201303 async addTranslationTarget ( target : TargetLangCode ) {
202304 this . configManager . addTranslationTarget ( { target_language : target } ) ;
203- await this . transport . setTask ( this . configManager . getConfig ( ) ) ;
305+ await this . transport ? .setTask ( this . configManager . getConfig ( ) ) ;
204306 }
205307
206308 async removeTranslationTarget ( target : TargetLangCode | TargetLangCode [ ] ) {
@@ -210,18 +312,17 @@ export class PalabraClient extends PalabraBaseEventEmitter {
210312 } else {
211313 this . configManager . deleteTranslationTarget ( target ) ;
212314 }
213- await this . transport . setTask ( this . configManager . getConfig ( ) ) ;
315+ await this . transport ? .setTask ( this . configManager . getConfig ( ) ) ;
214316 }
215317
216318 public async cleanup ( ) {
217319 await this . stopTranslation ( ) ;
218- await this . stopPlayback ( ) ;
219320 this . initConfig ( ) ;
220321 }
221322
222323 async changeAudioOutputDevice ( deviceId : string ) {
223324 this . deviceId = deviceId ;
224- this . transport . getRoom ( ) . switchActiveDevice ( 'audiooutput' , this . deviceId ) ;
325+ this . transport ? .getRoom ( ) . switchActiveDevice ( 'audiooutput' , this . deviceId ) ;
225326 }
226327
227328 private isAttached ( track : RemoteTrackInfo ) {
@@ -237,7 +338,7 @@ export class PalabraClient extends PalabraBaseEventEmitter {
237338 }
238339
239340 private async initAudioContext ( ) {
240- if ( this . audioContext ) return ;
341+ if ( this . audioContext || this . ignoreAudioContext ) return ;
241342 this . audioContext = new AudioContext ( ) ;
242343 }
243344
@@ -252,5 +353,14 @@ export class PalabraClient extends PalabraBaseEventEmitter {
252353 this . configManager . setSourceLanguage ( this . translateFrom as SourceLangCode ) ;
253354 this . configManager . addTranslationTarget ( { target_language : this . translateTo as TargetLangCode } ) ;
254355 }
356+
357+ public getApiClient ( ) {
358+ return this . apiClient ;
359+ }
360+
361+ public async setTask ( task : PipelineConfig ) {
362+ this . configManager . setJSON ( task . pipeline ) ;
363+ await this . transport ?. setTask ( task ) ;
364+ }
255365}
256366
0 commit comments