Skip to content

Commit c96cf31

Browse files
authored
Merge pull request #6 from PalabraAI/PBN-1860/0.0.5
feat: add new methods
2 parents d6a74e6 + 3153911 commit c96cf31

21 files changed

+532
-85
lines changed

packages/lib/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@palabra-ai/translator",
3-
"version": "0.0.4",
3+
"version": "0.0.5",
44
"private": false,
55
"main": "dist/lib.js",
66
"types": "dist/index.d.ts",

packages/lib/src/PalabraClient.model.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,9 @@ export interface PalabraClientData {
1717
handleOriginalTrack: () => Promise<MediaStreamTrack>;
1818
transportType?: 'webrtc'; // TODO: add websocket transport | 'websocket'
1919
apiBaseUrl?: string;
20-
}
20+
intent?:string;
21+
audioContext?:AudioContext;
22+
ignoreAudioContext?:boolean;
23+
}
24+
25+
export type TrackSid = string;

packages/lib/src/PalabraClient.ts

Lines changed: 141 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { PalabraClientData } from '~/PalabraClient.model';
1+
import { PalabraClientData, TrackSid } from '~/PalabraClient.model';
22
import { PalabraApiClient } from '~/api/api';
33
import { PalabraWebRtcTransport } from '~/transport/PalabraWebRtcTransport';
44
import { PipelineConfigManager } from '~/config/PipelineConfigManager';
55
import { TargetLangCode } from '~/utils/target';
66
import { SourceLangCode } from '~/utils/source';
77
import {
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';
1517
import { PalabraBaseEventEmitter } from '~/PalabraBaseEventEmitter';
1618
import { 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

1923
export 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

Comments
 (0)