Skip to content

Commit 47edba2

Browse files
authored
Merge pull request #107 from anam-org/alpha
fix: prevent duplicate audio stream issue
2 parents 986fb3a + cb45b76 commit 47edba2

File tree

4 files changed

+65
-38
lines changed

4 files changed

+65
-38
lines changed

README.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,19 @@ npm install @anam-ai/js-sdk
3434

3535
## Local development
3636

37-
The quickest way to start testing the SDK is to use your API key directly with our SDK and [choose a default persona](#listing-personas) from our predefined examples.
37+
The quickest way to start testing the SDK is to use your API key directly with our SDK and the example persona config shown below.
3838
To use the SDK you first need to create an instance of `AnamClient`. For local development you can do this using the `unsafe_createClientWithApiKey` method.
3939

4040
```typescript
4141
import { unsafe_createClientWithApiKey } from '@anam-ai/js-sdk';
4242

4343
const anamClient = unsafe_createClientWithApiKey('your-api-key', {
44-
personaId: 'chosen-persona-id',
44+
name: 'Cara',
45+
avatarId: '30fa96d0-26c4-4e55-94a0-517025942e18',
46+
voiceId: '6bfbe25a-979d-40f3-a92b-5394170af54b',
47+
brainType: 'ANAM_GPT_4O_MINI_V1',
48+
systemPrompt:
49+
"[STYLE] Reply in natural speech without formatting. Add pauses using '...' and very occasionally a disfluency. [PERSONALITY] You are Cara, a helpful assistant.",
4550
});
4651
```
4752

@@ -50,13 +55,10 @@ const anamClient = unsafe_createClientWithApiKey('your-api-key', {
5055
Once you have an instance of the Anam client initialised you can start a session by streaming to audio and video elements in the DOM.
5156

5257
```typescript
53-
await anamClient.streamToVideoAndAudioElements(
54-
'video-element-id',
55-
'audio-element-id',
56-
);
58+
await anamClient.streamToVideoElement('video-element-id');
5759
```
5860

59-
This will start a new session using the pre-configured persona id and start streaming video and audio to the elements in the DOM with the matching element ids.
61+
This will start a new session using the pre-configured persona id and start streaming video element in the DOM with the matching element id.
6062

6163
To stop a session use the `stopStreaming` method.
6264

@@ -79,13 +81,12 @@ const response = await fetch(`https://api.anam.ai/v1/auth/session-token`, {
7981
},
8082
body: JSON.stringify({
8183
personaConfig: {
82-
name: 'Test Assistant',
83-
personaPreset: 'eva',
84+
name: 'Cara',
85+
avatarId: '30fa96d0-26c4-4e55-94a0-517025942e18',
86+
voiceId: '6bfbe25a-979d-40f3-a92b-5394170af54b',
8487
brainType: 'ANAM_GPT_4O_MINI_V1',
85-
personality: 'You are a helpful and friendly AI assistant.',
8688
systemPrompt:
87-
'You are an AI assistant focused on helping with technical questions.',
88-
fillerPhrases: ['Let me think about that...'],
89+
"[STYLE] Reply in natural speech without formatting. Add pauses using '...' and very occasionally a disfluency. [PERSONALITY] You are Cara, a helpful assistant.",
8990
},
9091
}),
9192
});

src/AnamClient.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
DEFAULT_ANAM_METRICS_BASE_URL,
55
ErrorCode,
66
setErrorMetricsBaseUrl,
7+
setCurrentSessionInfo,
78
} from './lib/ClientError';
89
import {
910
CoreApiRestClient,
@@ -34,6 +35,7 @@ export default class AnamClient {
3435
private inputAudioState: InputAudioState = { isMuted: false };
3536

3637
private sessionId: string | null = null;
38+
private organizationId: string | null = null;
3739

3840
private streamingClient: StreamingClient | null = null;
3941
private apiClient: CoreApiRestClient;
@@ -111,6 +113,9 @@ export default class AnamClient {
111113
// Validate persona configuration based on session token
112114
if (sessionToken) {
113115
const decodedToken = this.decodeJwt(sessionToken);
116+
this.organizationId = decodedToken.accountId;
117+
setCurrentSessionInfo(this.sessionId, this.organizationId);
118+
114119
const tokenType = decodedToken.type?.toLowerCase();
115120

116121
if (tokenType === 'legacy') {
@@ -218,11 +223,15 @@ export default class AnamClient {
218223
'Failed to initialize streaming client',
219224
ErrorCode.CLIENT_ERROR_CODE_SERVER_ERROR,
220225
500,
221-
{ cause: error instanceof Error ? error.message : String(error) },
226+
{
227+
cause: error instanceof Error ? error.message : String(error),
228+
sessionId,
229+
},
222230
);
223231
}
224232

225233
this.sessionId = sessionId;
234+
setCurrentSessionInfo(this.sessionId, this.organizationId);
226235
return sessionId;
227236
}
228237

@@ -286,10 +295,23 @@ export default class AnamClient {
286295
});
287296
}
288297

298+
/**
299+
* @deprecated This method is deprecated. Please use streamToVideoElement instead.
300+
*/
289301
public async streamToVideoAndAudioElements(
290302
videoElementId: string,
291303
audioElementId: string,
292304
userProvidedAudioStream?: MediaStream,
305+
): Promise<void> {
306+
console.warn(
307+
'AnamClient: streamToVideoAndAudioElements is deprecated. To avoid possible audio issues, please use streamToVideoElement instead.',
308+
);
309+
await this.streamToVideoElement(videoElementId, userProvidedAudioStream);
310+
}
311+
312+
public async streamToVideoElement(
313+
videoElementId: string,
314+
userProvidedAudioStream?: MediaStream,
293315
): Promise<void> {
294316
if (this.clientOptions?.disableInputAudio && userProvidedAudioStream) {
295317
console.warn(
@@ -309,6 +331,7 @@ export default class AnamClient {
309331
500,
310332
{
311333
cause: error instanceof Error ? error.message : String(error),
334+
sessionId: this.sessionId,
312335
},
313336
);
314337
}
@@ -321,10 +344,7 @@ export default class AnamClient {
321344
throw new Error('Failed to stream: streaming client is not available');
322345
}
323346

324-
this.streamingClient.setMediaStreamTargetsById(
325-
videoElementId,
326-
audioElementId,
327-
);
347+
this.streamingClient.setMediaStreamTargetById(videoElementId);
328348
this.streamingClient.startConnection();
329349
}
330350

@@ -356,6 +376,7 @@ export default class AnamClient {
356376
this.streamingClient.stopConnection();
357377
this.streamingClient = null;
358378
this.sessionId = null;
379+
setCurrentSessionInfo(null, this.organizationId);
359380
this._isStreaming = false;
360381
}
361382
}

src/lib/ClientError.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export const DEFAULT_ANAM_API_VERSION = '/v1';
1717
let anamCurrentBaseUrl = DEFAULT_ANAM_METRICS_BASE_URL;
1818
let anamCurrentApiVersion = DEFAULT_ANAM_API_VERSION;
1919

20+
let currentSessionId: string | null = null;
21+
let currentOrganizationId: string | null = null;
22+
2023
export const setErrorMetricsBaseUrl = (
2124
baseUrl: string,
2225
apiVersion: string = DEFAULT_ANAM_API_VERSION,
@@ -25,12 +28,33 @@ export const setErrorMetricsBaseUrl = (
2528
anamCurrentApiVersion = apiVersion;
2629
};
2730

31+
export const setCurrentSessionInfo = (
32+
sessionId: string | null,
33+
organizationId: string | null,
34+
) => {
35+
currentSessionId = sessionId;
36+
currentOrganizationId = organizationId;
37+
};
38+
2839
export const sendErrorMetric = async (
2940
name: string,
3041
value: string,
3142
tags?: Record<string, string | number>,
3243
) => {
3344
try {
45+
const metricTags: Record<string, string | number> = {
46+
...CLIENT_METADATA,
47+
...tags,
48+
};
49+
50+
// Add session and organization IDs if available
51+
if (currentSessionId) {
52+
metricTags.sessionId = currentSessionId;
53+
}
54+
if (currentOrganizationId) {
55+
metricTags.organizationId = currentOrganizationId;
56+
}
57+
3458
await fetch(
3559
`${anamCurrentBaseUrl}${anamCurrentApiVersion}/metrics/client`,
3660
{
@@ -41,10 +65,7 @@ export const sendErrorMetric = async (
4165
body: JSON.stringify({
4266
name,
4367
value,
44-
tags: {
45-
...CLIENT_METADATA,
46-
...tags,
47-
},
68+
tags: metricTags,
4869
}),
4970
},
5071
);

src/modules/StreamingClient.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ export class StreamingClient {
3434
private dataChannel: RTCDataChannel | null = null;
3535
private videoElement: HTMLVideoElement | null = null;
3636
private videoStream: MediaStream | null = null;
37-
private audioElement: HTMLAudioElement | null = null;
3837
private audioStream: MediaStream | null = null;
3938
private inputAudioState: InputAudioState = { isMuted: false };
4039
private audioDeviceId: string | undefined;
@@ -155,10 +154,7 @@ export class StreamingClient {
155154
}
156155
}
157156

158-
public setMediaStreamTargetsById(
159-
videoElementId: string,
160-
audioElementId: string,
161-
) {
157+
public setMediaStreamTargetById(videoElementId: string) {
162158
// set up streaming targets
163159
if (videoElementId) {
164160
const videoElement = document.getElementById(videoElementId);
@@ -169,15 +165,6 @@ export class StreamingClient {
169165
}
170166
this.videoElement = videoElement as HTMLVideoElement;
171167
}
172-
if (audioElementId) {
173-
const audioElement = document.getElementById(audioElementId);
174-
if (!audioElement) {
175-
throw new Error(
176-
`StreamingClient: audio element with id ${audioElementId} not found`,
177-
);
178-
}
179-
this.audioElement = audioElement as HTMLAudioElement;
180-
}
181168
}
182169

183170
public startConnection() {
@@ -400,9 +387,6 @@ export class StreamingClient {
400387
AnamEvent.AUDIO_STREAM_STARTED,
401388
this.audioStream,
402389
);
403-
if (this.audioElement) {
404-
this.audioElement.srcObject = this.audioStream;
405-
}
406390
}
407391
}
408392
/**

0 commit comments

Comments
 (0)