@ermis-network/ermis-chat-sdk 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +330 -0
  2. package/dist/encryption/index.browser.cjs +13045 -0
  3. package/dist/encryption/index.browser.cjs.map +1 -0
  4. package/dist/encryption/index.browser.mjs +12959 -0
  5. package/dist/encryption/index.browser.mjs.map +1 -0
  6. package/dist/encryption/index.cjs +13045 -0
  7. package/dist/encryption/index.cjs.map +1 -0
  8. package/dist/encryption/index.d.mts +3 -0
  9. package/dist/encryption/index.d.ts +3 -0
  10. package/dist/encryption/index.mjs +12959 -0
  11. package/dist/encryption/index.mjs.map +1 -0
  12. package/dist/index-CcvHIY5q.d.mts +4988 -0
  13. package/dist/index-CcvHIY5q.d.ts +4988 -0
  14. package/dist/index.browser.cjs +20192 -5766
  15. package/dist/index.browser.cjs.map +1 -1
  16. package/dist/index.browser.full-bundle.min.js +20 -16
  17. package/dist/index.browser.full-bundle.min.js.map +1 -1
  18. package/dist/index.browser.mjs +20106 -5731
  19. package/dist/index.browser.mjs.map +1 -1
  20. package/dist/index.cjs +20191 -5765
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.mts +15 -1337
  23. package/dist/index.d.ts +15 -1337
  24. package/dist/index.mjs +20106 -5731
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/wasm_worker.worker.mjs +8 -4
  27. package/dist/wasm_worker.worker.mjs.map +1 -1
  28. package/package.json +21 -6
  29. package/public/e2ee-media-stream-worker.js +627 -0
  30. package/public/openmls_wasm_bg.wasm +0 -0
  31. package/src/attachment_utils.ts +0 -148
  32. package/src/auth.ts +0 -352
  33. package/src/channel.ts +0 -1879
  34. package/src/channel_state.ts +0 -612
  35. package/src/client.ts +0 -1759
  36. package/src/client_state.ts +0 -55
  37. package/src/connection.ts +0 -587
  38. package/src/ermis_call_node.ts +0 -1046
  39. package/src/errors.ts +0 -60
  40. package/src/events.ts +0 -46
  41. package/src/hevc_decoder_config.ts +0 -305
  42. package/src/index.ts +0 -17
  43. package/src/media_stream_receiver.ts +0 -593
  44. package/src/media_stream_sender.ts +0 -465
  45. package/src/shims/empty.ts +0 -1
  46. package/src/signal_message.ts +0 -171
  47. package/src/system_message.ts +0 -259
  48. package/src/token_manager.ts +0 -48
  49. package/src/types.ts +0 -594
  50. package/src/utils.ts +0 -553
  51. package/src/wasm/ermis_call_node_wasm.d.ts +0 -156
  52. package/src/wasm/ermis_call_node_wasm.js +0 -1568
  53. package/src/wasm_worker.ts +0 -219
  54. package/src/wasm_worker_proxy.ts +0 -244
@@ -1,465 +0,0 @@
1
- import { base64Encode, createPacketWithHeader } from './utils';
2
- import { AudioConfig, INodeCall, TransceiverState, VideoConfig } from './types';
3
-
4
- export class MediaStreamSender {
5
- private videoEncoder: VideoEncoder | null = null;
6
- private audioEncoder: AudioEncoder | null = null;
7
- private videoReader: ReadableStreamDefaultReader<VideoFrame> | null = null;
8
- private audioReader: ReadableStreamDefaultReader<AudioData> | null = null;
9
-
10
- private localStream: MediaStream | null = null;
11
-
12
- private videoConfig: VideoConfig | null = null;
13
- private audioConfig: AudioConfig | null = null;
14
-
15
- private videoConfigSent: boolean = false;
16
- private audioConfigSent: boolean = false;
17
-
18
- private hasVideo: boolean = false;
19
- private hasAudio: boolean = false;
20
-
21
- private forceKeyFrame: boolean = false;
22
- private isSendingVideo: boolean = false;
23
- private isSendingAudio: boolean = false;
24
-
25
- private nodeCall: INodeCall;
26
-
27
- private healthCallInterval: ReturnType<typeof setInterval> | null = null;
28
-
29
- constructor(nodeCall: INodeCall) {
30
- this.nodeCall = nodeCall;
31
- }
32
-
33
- /**
34
- * Bắt đầu xử lý MediaStream
35
- */
36
- public async connect(address: string): Promise<void> {
37
- try {
38
- await this.nodeCall.connect(address);
39
- await this.sendConnected();
40
- await this.sendConfigs();
41
-
42
- // Start health call keep-alive (every 5s, matching native SDK)
43
- this.startHealthCallInterval();
44
- } catch (error) {
45
- console.error('Error starting MediaStreamSender:', error);
46
- }
47
- }
48
-
49
- public async sendConfigs(): Promise<void> {
50
- try {
51
- await this.sendTransceiverState(this.hasAudio, this.hasVideo);
52
- await this.sendAudioConfig();
53
-
54
- const videoTrack = this.localStream?.getVideoTracks()[0];
55
- if (videoTrack) {
56
- await this.sendVideoConfig();
57
- }
58
- } catch (error) {
59
- console.error('Error sending configs:', error);
60
- }
61
- }
62
-
63
- /**
64
- * Dừng và reset encoders
65
- */
66
- public stop = (): void => {
67
- // Stop health call keep-alive
68
- if (this.healthCallInterval) {
69
- clearInterval(this.healthCallInterval);
70
- this.healthCallInterval = null;
71
- }
72
-
73
- if (this.videoReader) {
74
- try {
75
- this.videoReader.cancel('Stream stopped').catch(() => {});
76
- } catch (e) {}
77
- this.videoReader = null;
78
- }
79
-
80
- if (this.videoEncoder) {
81
- try {
82
- if (this.videoEncoder.state !== 'closed') {
83
- this.videoEncoder.reset(); // Xả frame
84
- this.videoEncoder.close();
85
- }
86
- } catch (e) {}
87
- this.videoEncoder = null;
88
- }
89
-
90
- if (this.audioEncoder) {
91
- try {
92
- if (this.audioEncoder.state !== 'closed') {
93
- this.audioEncoder.reset();
94
- this.audioEncoder.close();
95
- }
96
- } catch (e) {}
97
- this.audioEncoder = null;
98
- }
99
-
100
- if (this.audioReader) {
101
- try {
102
- this.audioReader.cancel('Stream stopped').catch(() => {});
103
- } catch (e) {}
104
- this.audioReader = null;
105
- }
106
-
107
- // Reset configs and flags
108
- this.videoConfig = null;
109
- this.audioConfig = null;
110
-
111
- this.videoConfigSent = false;
112
- this.audioConfigSent = false;
113
- this.hasVideo = false;
114
- this.hasAudio = false;
115
-
116
- if (this.localStream) {
117
- this.localStream.getTracks().forEach((track) => track.stop());
118
- this.localStream = null;
119
- }
120
- };
121
-
122
- public initAudioEncoder = (audioTrack: MediaStreamTrack): void => {
123
- this.localStream = new MediaStream([audioTrack]);
124
- this.audioConfigSent = false;
125
- this.hasAudio = !!audioTrack;
126
-
127
- const audioEncoder = new AudioEncoder({
128
- output: (chunk, metadata) => {
129
- if (metadata?.decoderConfig && !this.audioConfigSent) {
130
- let description: string | undefined = undefined;
131
- if (metadata.decoderConfig.description) {
132
- description = base64Encode(metadata.decoderConfig.description as ArrayBuffer);
133
- }
134
-
135
- this.audioConfig = {
136
- codec: metadata.decoderConfig.codec ?? 'opus',
137
- sampleRate: metadata.decoderConfig.sampleRate ?? 48000,
138
- numberOfChannels: metadata.decoderConfig.numberOfChannels ?? 1,
139
- ...(description && { description }),
140
- };
141
-
142
- this.sendAudioConfig();
143
- }
144
-
145
- if (chunk && this.isReadyToSendData('audio')) {
146
- if (this.isSendingAudio) return; // Backpressure: drop if network is congested
147
-
148
- this.isSendingAudio = true;
149
- const data = new ArrayBuffer(chunk.byteLength);
150
- chunk.copyTo(data);
151
- const timestamp = chunk.timestamp;
152
-
153
- const packet = createPacketWithHeader(data, timestamp, 'audio', null);
154
- this.sendPacketOrQueue(packet, 'audio', null).finally(() => {
155
- this.isSendingAudio = false;
156
- });
157
- }
158
- },
159
- error: (e) => console.error('AudioEncoder error:', e),
160
- });
161
-
162
- audioEncoder.configure({
163
- codec: 'mp4a.40.2',
164
- // codec: 'opus',
165
- sampleRate: 48000,
166
- numberOfChannels: 1,
167
- bitrate: 128000,
168
- // bitrate: 64000,
169
- });
170
-
171
- this.audioEncoder = audioEncoder;
172
- this.processAudioFrames(audioTrack);
173
- };
174
-
175
- public initVideoEncoder(videoTrack: MediaStreamTrack): void {
176
- if (this.localStream) {
177
- this.localStream.addTrack(videoTrack);
178
- }
179
-
180
- this.videoConfigSent = false;
181
- this.hasVideo = !!videoTrack;
182
-
183
- const settings = videoTrack.getSettings();
184
- const videoWidth = settings.width || 1280;
185
- const videoHeight = settings.height || 720;
186
-
187
- const videoEncoder = new VideoEncoder({
188
- output: async (chunk, metadata) => {
189
- if (metadata?.decoderConfig && !this.videoConfigSent) {
190
- let description: string | undefined = undefined;
191
- if (metadata.decoderConfig.description) {
192
- description = base64Encode(metadata.decoderConfig.description as ArrayBuffer);
193
- }
194
-
195
- this.videoConfig = {
196
- codec: metadata.decoderConfig.codec ?? 'hev1.1.6.L93.B0',
197
- codedWidth: metadata.decoderConfig.codedWidth ?? videoWidth,
198
- codedHeight: metadata.decoderConfig.codedHeight ?? videoHeight,
199
- frameRate: 30.0,
200
- orientation: 0,
201
- ...(description && { description }),
202
- };
203
-
204
- await this.sendVideoConfig();
205
- }
206
-
207
- if (chunk && this.isReadyToSendData('video')) {
208
- if (this.isSendingVideo) return; // Backpressure: drop if network is congested
209
-
210
- this.isSendingVideo = true;
211
- const data = new ArrayBuffer(chunk.byteLength);
212
- chunk.copyTo(data);
213
- const frameType = chunk.type === 'key' ? 'video-key' : 'video-delta';
214
- const timestamp = chunk.timestamp;
215
-
216
- const packet = createPacketWithHeader(data, timestamp, frameType, null);
217
- this.sendPacketOrQueue(packet, 'video', frameType).finally(() => {
218
- this.isSendingVideo = false;
219
- });
220
- }
221
- },
222
- error: (e) => console.error('VideoEncoder error:', e),
223
- });
224
-
225
- videoEncoder.configure({
226
- codec: 'hev1.1.6.L93.B0',
227
- width: videoWidth,
228
- height: videoHeight,
229
- bitrate: 500_000,
230
- framerate: 30,
231
- latencyMode: 'realtime',
232
- hardwareAcceleration: 'prefer-hardware',
233
- });
234
-
235
- this.videoEncoder = videoEncoder;
236
- this.processVideoFrames(videoTrack);
237
- }
238
-
239
- public initEncoders = (stream: MediaStream): void => {
240
- const videoTrack = stream.getVideoTracks()[0];
241
- const audioTrack = stream.getAudioTracks()[0];
242
-
243
- if (audioTrack) {
244
- this.initAudioEncoder(audioTrack);
245
- }
246
-
247
- if (videoTrack) {
248
- this.initVideoEncoder(videoTrack);
249
- }
250
- };
251
-
252
- public sendTransceiverState = async (audioEnable: boolean, videoEnable: boolean) => {
253
- const state: TransceiverState = {
254
- audio_enable: !!audioEnable,
255
- video_enable: !!videoEnable,
256
- };
257
- const configPacket = createPacketWithHeader(null, null, 'transciverState', state);
258
- await this.nodeCall.sendControlFrame(configPacket);
259
- };
260
-
261
- public async replaceVideoTrack(track: MediaStreamTrack): Promise<void> {
262
- // 1. Dừng reader của track cũ (quan trọng)
263
- if (this.videoReader) {
264
- try {
265
- // Việc gọi cancel sẽ làm Promise tại dòng await read() bên dưới throw lỗi hoặc trả về done
266
- await this.videoReader.cancel('Replacing track');
267
- } catch (e) {
268
- // Bỏ qua lỗi khi cancel
269
- }
270
- this.videoReader = null;
271
- }
272
-
273
- if (track) {
274
- this.processVideoFrames(track);
275
- }
276
- }
277
-
278
- public async replaceAudioTrack(track: MediaStreamTrack): Promise<void> {
279
- if (this.audioReader) {
280
- try {
281
- await this.audioReader.cancel('Replacing audio track');
282
- } catch (e) {}
283
- this.audioReader = null;
284
- }
285
-
286
- if (track) {
287
- this.processAudioFrames(track);
288
- }
289
- }
290
-
291
- /**
292
- * Yêu cầu gửi keyframe ngay lập tức (được gọi khi nhận REQUEST_KEY_FRAME từ receiver)
293
- */
294
- public requestKeyFrame = (): void => {
295
- console.log('📥 KeyFrame requested');
296
- this.forceKeyFrame = true;
297
- };
298
-
299
- // ================= PRIVATE METHODS =================
300
-
301
- private processVideoFrames = async (videoTrack: MediaStreamTrack) => {
302
- try {
303
- // @ts-ignore: MediaStreamTrackProcessor is explicitly defined in WebCodecs types
304
- const videoProcessor = new MediaStreamTrackProcessor({ track: videoTrack });
305
- this.videoReader = videoProcessor.readable.getReader();
306
-
307
- let frameCounter = 0;
308
- while (true) {
309
- if (!this.videoReader) break;
310
-
311
- const { done, value: frame } = await this.videoReader.read();
312
- if (done) break;
313
-
314
- if (!this.videoEncoder) {
315
- frame?.close();
316
- break;
317
- }
318
-
319
- if (frame) {
320
- frameCounter += 1;
321
- const keyFrame = frameCounter % 60 === 0 || this.forceKeyFrame;
322
- if (this.forceKeyFrame) {
323
- console.log('📤 Sending forced KeyFrame');
324
- this.forceKeyFrame = false;
325
- }
326
- try {
327
- this.videoEncoder.encode(frame, { keyFrame });
328
- } catch (err) {
329
- console.error('Encode error:', err);
330
- } finally {
331
- frame.close();
332
- }
333
- }
334
- }
335
- } catch (error: any) {
336
- console.error(`Error processing video frames: ${error.message}`);
337
- } finally {
338
- if (this.videoReader) {
339
- try {
340
- this.videoReader.releaseLock();
341
- } catch (e) {}
342
- // this.videoReader = null;
343
- }
344
- }
345
- };
346
-
347
- private processAudioFrames = async (audioTrack: MediaStreamTrack) => {
348
- // @ts-ignore
349
- const audioProcessor = new MediaStreamTrackProcessor({ track: audioTrack });
350
- this.audioReader = audioProcessor.readable.getReader();
351
-
352
- try {
353
- while (true) {
354
- if (!this.audioReader) break;
355
-
356
- const { done, value: frame } = await this.audioReader.read();
357
- if (done) break;
358
-
359
- if (!this.audioEncoder) {
360
- frame?.close();
361
- break;
362
- }
363
-
364
- if (frame) {
365
- try {
366
- this.audioEncoder.encode(frame);
367
- } catch (err) {
368
- console.error('Audio Encoding error:', err);
369
- } finally {
370
- frame.close();
371
- }
372
- }
373
- }
374
- } catch (error: any) {
375
- console.error(`Error processing audio frames: ${error.message}`);
376
- } finally {
377
- if (this.audioReader) {
378
- try {
379
- this.audioReader.releaseLock();
380
- } catch (e) {}
381
- }
382
- }
383
- };
384
-
385
- private isReadyToSendData = (type: 'video' | 'audio'): boolean => {
386
- const videoReady = !this.hasVideo || this.videoConfigSent;
387
- const audioReady = !this.hasAudio || this.audioConfigSent;
388
- const allConfigsSent = videoReady && audioReady;
389
-
390
- if (type === 'video') {
391
- return allConfigsSent && this.videoConfigSent;
392
- } else if (type === 'audio') {
393
- return allConfigsSent && this.audioConfigSent;
394
- }
395
-
396
- return false;
397
- };
398
-
399
- private sendVideoConfig = async () => {
400
- if (this.videoConfig && !this.videoConfigSent) {
401
- try {
402
- const configPacket = createPacketWithHeader(null, null, 'videoConfig', this.videoConfig);
403
- await this.nodeCall.sendControlFrame(configPacket);
404
- this.videoConfigSent = true;
405
- } catch (error) {
406
- console.error('Error sending video config:', error);
407
- }
408
- }
409
- };
410
-
411
- private sendAudioConfig = async () => {
412
- if (this.audioConfig && !this.audioConfigSent) {
413
- try {
414
- const configPacket = createPacketWithHeader(null, null, 'audioConfig', this.audioConfig);
415
- await this.nodeCall.sendControlFrame(configPacket);
416
- this.audioConfigSent = true;
417
- } catch (error) {
418
- console.error('Error sending audio config:', error);
419
- }
420
- }
421
- };
422
-
423
- public sendConnected = async () => {
424
- const configPacket = createPacketWithHeader(null, null, 'connected', null);
425
- await this.nodeCall.sendControlFrame(configPacket);
426
- };
427
-
428
- private startHealthCallInterval = (): void => {
429
- if (this.healthCallInterval) {
430
- clearInterval(this.healthCallInterval);
431
- }
432
- this.healthCallInterval = setInterval(() => {
433
- this.sendHealthCall().catch(() => {});
434
- }, 5000);
435
- };
436
-
437
- private sendHealthCall = async (): Promise<void> => {
438
- try {
439
- const packet = createPacketWithHeader(null, null, 'healthCall', null);
440
- await this.nodeCall.sendControlFrame(packet);
441
- } catch (e) {
442
- // Silently ignore — connection may have closed
443
- }
444
- };
445
-
446
- private sendPacketOrQueue = async (
447
- packet: Uint8Array,
448
- type: 'video' | 'audio',
449
- frameType: 'video-key' | 'video-delta' | null,
450
- ) => {
451
- if (!this.isReadyToSendData(type)) {
452
- return;
453
- }
454
-
455
- if (type === 'audio') {
456
- await this.nodeCall.sendAudioFrame(packet);
457
- } else if (type === 'video') {
458
- if (frameType === 'video-key') {
459
- await this.nodeCall.beginWithGop(packet);
460
- } else if (frameType === 'video-delta') {
461
- await this.nodeCall.sendFrame(packet);
462
- }
463
- }
464
- };
465
- }
@@ -1 +0,0 @@
1
- export default null;
@@ -1,171 +0,0 @@
1
- /**
2
- * Call type constants for signal messages.
3
- */
4
- export const CallType = {
5
- AUDIO: 'audio',
6
- VIDEO: 'video',
7
- } as const;
8
-
9
- export type CallTypeValue = (typeof CallType)[keyof typeof CallType];
10
-
11
- /**
12
- * Result of parsing a signal message.
13
- */
14
- export interface SignalMessageResult {
15
- text: string;
16
- duration: string;
17
- callType: CallTypeValue | '';
18
- color: string;
19
- }
20
-
21
- /**
22
- * Translation templates for signal messages.
23
- */
24
- export interface SignalMessageTranslations {
25
- calling?: string; // "Calling..."
26
- incomingAudioCall?: string; // "Incoming audio call..."
27
- incomingVideoCall?: string; // "Incoming video call..."
28
- outgoingAudioCall?: string; // "Outgoing audio call"
29
- outgoingVideoCall?: string; // "Outgoing video call"
30
- missedAudioCall?: string; // "You missed audio call"
31
- missedVideoCall?: string; // "You missed video call"
32
- cancelAudioCall?: string; // "You cancel audio call"
33
- cancelVideoCall?: string; // "You cancel video call"
34
- rejectedAudioCallRecipient?: string; // "Recipient rejected audio call"
35
- rejectedAudioCallYou?: string; // "You rejected audio call"
36
- rejectedVideoCallRecipient?: string; // "Recipient rejected video call"
37
- rejectedVideoCallYou?: string; // "You rejected video call"
38
- busyRecipient?: string; // "Recipient was busy"
39
- durationUnitMin?: string; // "min"
40
- durationUnitSec?: string; // "sec"
41
- }
42
-
43
- /**
44
- * Format duration from milliseconds to human-readable format.
45
- */
46
- function formatDuration(durationMs: string, translations?: SignalMessageTranslations): string {
47
- if (!durationMs) return '';
48
- const ms = parseInt(durationMs, 10);
49
- if (isNaN(ms) || ms <= 0) return '';
50
- const totalSeconds = Math.floor(ms / 1000);
51
- const minutes = Math.floor(totalSeconds / 60);
52
- const seconds = totalSeconds % 60;
53
- const minUnit = translations?.durationUnitMin ?? 'min';
54
- const secUnit = translations?.durationUnitSec ?? 'sec';
55
- return `${minutes} ${minUnit}, ${seconds} ${secUnit}`;
56
- }
57
-
58
- /**
59
- * Parse a raw signal message string into a structured object.
60
- *
61
- * Signal messages represent call events. The raw format is:
62
- * `"<formatId> <callerId> [<enderId> <duration>]"`
63
- *
64
- * @param value - Raw signal message string from the server
65
- * @param myUserId - The current user's ID
66
- * @param translations - Optional translation templates
67
- * @returns Parsed signal message object, or null if input is empty
68
- */
69
- export function parseSignalMessage(
70
- value: string,
71
- myUserId: string,
72
- translations?: SignalMessageTranslations,
73
- ): SignalMessageResult | null {
74
- if (!value || typeof value !== 'string') return null;
75
-
76
- const trimmed = value.trim();
77
- if (!trimmed) return null;
78
-
79
- const parts = trimmed.split(' ');
80
- const number = parseInt(parts[0], 10);
81
- const callerId = parts[1] ?? '';
82
- const isMe = myUserId === callerId;
83
-
84
- let enderId = '';
85
- let duration = '';
86
- let callType: CallTypeValue | '' = '';
87
- let color = '';
88
-
89
- if (number === 3 || number === 6) {
90
- enderId = parts[2] ?? '';
91
- duration = parts[3] === '0' ? '' : (parts[3] ?? '');
92
- }
93
-
94
- let text: string;
95
- switch (number) {
96
- case 1: // AudioCallStarted
97
- text = isMe ? (translations?.calling ?? 'Calling...') : (translations?.incomingAudioCall ?? 'Incoming audio call...');
98
- callType = CallType.AUDIO;
99
- color = '#54D62C';
100
- break;
101
- case 2: // AudioCallMissed
102
- text = isMe ? (translations?.outgoingAudioCall ?? 'Outgoing audio call') : (translations?.missedAudioCall ?? 'You missed audio call');
103
- callType = CallType.AUDIO;
104
- color = '#FF4842';
105
- break;
106
- case 3: // AudioCallEnded
107
- if (duration) {
108
- text = isMe ? (translations?.outgoingAudioCall ?? 'Outgoing audio call') : (translations?.incomingAudioCall ?? 'Incoming audio call');
109
- color = '#54D62C';
110
- } else {
111
- if (enderId === myUserId) {
112
- text = translations?.cancelAudioCall ?? 'You cancel audio call';
113
- } else {
114
- text = translations?.missedAudioCall ?? 'You missed audio call';
115
- }
116
- color = '#FF4842';
117
- }
118
- callType = CallType.AUDIO;
119
- break;
120
- case 4: // VideoCallStarted
121
- text = isMe ? (translations?.calling ?? 'Calling...') : (translations?.incomingVideoCall ?? 'Incoming video call...');
122
- callType = CallType.VIDEO;
123
- color = '#54D62C';
124
- break;
125
- case 5: // VideoCallMissed
126
- text = isMe ? (translations?.outgoingVideoCall ?? 'Outgoing video call') : (translations?.missedVideoCall ?? 'You missed video call');
127
- callType = CallType.VIDEO;
128
- color = '#FF4842';
129
- break;
130
- case 6: // VideoCallEnded
131
- if (duration) {
132
- text = isMe ? (translations?.outgoingVideoCall ?? 'Outgoing video call') : (translations?.incomingVideoCall ?? 'Incoming video call');
133
- color = '#54D62C';
134
- } else {
135
- if (enderId === myUserId) {
136
- text = translations?.cancelVideoCall ?? 'You cancel video call';
137
- } else {
138
- text = translations?.missedVideoCall ?? 'You missed video call';
139
- }
140
- color = '#FF4842';
141
- }
142
- callType = CallType.VIDEO;
143
- break;
144
- case 7: // AudioCallRejected
145
- text = isMe ? (translations?.rejectedAudioCallRecipient ?? 'Recipient rejected audio call') : (translations?.rejectedAudioCallYou ?? 'You rejected audio call');
146
- callType = CallType.AUDIO;
147
- color = '#FF4842';
148
- break;
149
- case 8: // VideoCallRejected
150
- text = isMe ? (translations?.rejectedVideoCallRecipient ?? 'Recipient rejected video call') : (translations?.rejectedVideoCallYou ?? 'You rejected video call');
151
- callType = CallType.VIDEO;
152
- color = '#FF4842';
153
- break;
154
- case 9: // AudioCallBusy
155
- text = isMe ? (translations?.busyRecipient ?? 'Recipient was busy') : (translations?.missedAudioCall ?? 'You missed audio call');
156
- callType = CallType.AUDIO;
157
- color = '#FF4842';
158
- break;
159
- case 10: // VideoCallBusy
160
- text = isMe ? (translations?.busyRecipient ?? 'Recipient was busy') : (translations?.missedVideoCall ?? 'You missed video call');
161
- callType = CallType.VIDEO;
162
- color = '#FF4842';
163
- break;
164
- default:
165
- text = trimmed;
166
- callType = '';
167
- color = '';
168
- }
169
-
170
- return { text, duration: formatDuration(duration, translations), callType, color };
171
- }