@ermis-network/ermis-chat-sdk 1.0.9 → 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/bin/init-call.js +9 -0
  3. package/dist/encryption/index.browser.cjs +13045 -0
  4. package/dist/encryption/index.browser.cjs.map +1 -0
  5. package/dist/encryption/index.browser.mjs +12959 -0
  6. package/dist/encryption/index.browser.mjs.map +1 -0
  7. package/dist/encryption/index.cjs +13045 -0
  8. package/dist/encryption/index.cjs.map +1 -0
  9. package/dist/encryption/index.d.mts +3 -0
  10. package/dist/encryption/index.d.ts +3 -0
  11. package/dist/encryption/index.mjs +12959 -0
  12. package/dist/encryption/index.mjs.map +1 -0
  13. package/dist/index-CcvHIY5q.d.mts +4988 -0
  14. package/dist/index-CcvHIY5q.d.ts +4988 -0
  15. package/dist/index.browser.cjs +20399 -6823
  16. package/dist/index.browser.cjs.map +1 -1
  17. package/dist/index.browser.full-bundle.min.js +20 -18
  18. package/dist/index.browser.full-bundle.min.js.map +1 -1
  19. package/dist/index.browser.mjs +20315 -6790
  20. package/dist/index.browser.mjs.map +1 -1
  21. package/dist/index.cjs +20400 -6824
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.mts +167 -1356
  24. package/dist/index.d.ts +167 -1356
  25. package/dist/index.mjs +20312 -6787
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/wasm_worker.worker.mjs +1600 -0
  28. package/dist/wasm_worker.worker.mjs.map +1 -0
  29. package/package.json +22 -7
  30. package/public/e2ee-media-stream-worker.js +627 -0
  31. package/public/ermis_call_node_wasm_bg.wasm +0 -0
  32. package/public/openmls_wasm_bg.wasm +0 -0
  33. package/src/attachment_utils.ts +0 -148
  34. package/src/auth.ts +0 -352
  35. package/src/channel.ts +0 -1806
  36. package/src/channel_state.ts +0 -607
  37. package/src/client.ts +0 -1617
  38. package/src/client_state.ts +0 -55
  39. package/src/connection.ts +0 -587
  40. package/src/ermis_call_node.ts +0 -978
  41. package/src/errors.ts +0 -60
  42. package/src/events.ts +0 -46
  43. package/src/hevc_decoder_config.ts +0 -305
  44. package/src/index.ts +0 -16
  45. package/src/media_stream_receiver.ts +0 -525
  46. package/src/media_stream_sender.ts +0 -400
  47. package/src/shims/empty.ts +0 -1
  48. package/src/signal_message.ts +0 -146
  49. package/src/system_message.ts +0 -117
  50. package/src/token_manager.ts +0 -48
  51. package/src/types.ts +0 -581
  52. package/src/utils.ts +0 -534
  53. package/src/wasm/ermis_call_node_wasm.d.ts +0 -154
  54. package/src/wasm/ermis_call_node_wasm.js +0 -1498
@@ -1,525 +0,0 @@
1
- import { replaceCodecNumber } from './utils';
2
- import { FRAME_TYPE, IMediaReceiverEvents, INodeCall, VideoConfig } from './types';
3
- import { HEVCDecoderConfigurationRecord } from './hevc_decoder_config';
4
-
5
- // Các biến cấu hình Audio Sync
6
- const MAX_AUDIO_LATENCY = 0.1; // 100ms
7
- const MIN_BUFFER_AHEAD = 0.02; // 20ms
8
-
9
- export class MediaStreamReceiver {
10
- private videoDecoder: VideoDecoder | null = null;
11
- private audioDecoder: AudioDecoder | null = null;
12
-
13
- // WritableStreamDefaultWriter<VideoFrame> là type chuẩn của WebCodecs
14
- private videoWriter: WritableStreamDefaultWriter<VideoFrame> | null = null;
15
-
16
- private audioContext: AudioContext | null = null;
17
- private mediaDestination: MediaStreamAudioDestinationNode | null = null;
18
-
19
- private isWaitingForKeyFrame: boolean = true;
20
- private nextStartTime: number = 0;
21
- private lastVideoConfig: VideoConfig | null = null;
22
-
23
- private nodeCall: INodeCall;
24
- private events: IMediaReceiverEvents;
25
-
26
- private generatedStream: MediaStream | null = null;
27
-
28
- constructor(nodeCall: INodeCall, events: IMediaReceiverEvents = {}) {
29
- this.nodeCall = nodeCall;
30
- this.events = events;
31
- }
32
-
33
- public async acceptConnection(): Promise<void> {
34
- try {
35
- await this.nodeCall.acceptConnection();
36
- } catch (error) {
37
- console.error('❌ Error starting MediaStreamReceiver:', error);
38
- }
39
- }
40
-
41
- public getRemoteStream = (): MediaStream | null => {
42
- return this.generatedStream;
43
- };
44
-
45
- /**
46
- * Dừng toàn bộ quá trình và giải phóng tài nguyên
47
- */
48
- public stop = (): void => {
49
- this.resetDecoders();
50
- };
51
-
52
- // ================= PRIVATE METHODS =================
53
-
54
- private initAudioContext = async (): Promise<void> => {
55
- if (!this.audioContext || this.audioContext.state === 'closed') {
56
- const AudioContextClass = window.AudioContext || (window as any).webkitAudioContext;
57
- this.audioContext = new AudioContextClass({
58
- sampleRate: 48000,
59
- latencyHint: 'interactive',
60
- });
61
-
62
- this.mediaDestination = this.audioContext.createMediaStreamDestination();
63
-
64
- if (this.audioContext.state === 'suspended') {
65
- await this.audioContext.resume();
66
- }
67
- this.nextStartTime = this.audioContext.currentTime + MIN_BUFFER_AHEAD;
68
- }
69
- };
70
-
71
- public initDecoders = (callType: string): void => {
72
- // 1. Audio Decoder Setup
73
- if (this.audioDecoder) this.audioDecoder.close();
74
-
75
- // 2. Setup Audio Context & Streams
76
- this.initAudioContext();
77
-
78
- // Đảm bảo mediaDestination đã được tạo trong initAudioContext
79
- if (this.mediaDestination) {
80
- const audioTrack = this.mediaDestination.stream.getAudioTracks()[0];
81
-
82
- // Khởi tạo stream với 1 track audio
83
- this.generatedStream = new MediaStream([audioTrack]);
84
- }
85
-
86
- this.isWaitingForKeyFrame = true;
87
-
88
- // 3. Init AudioDecoder
89
- this.audioDecoder = new AudioDecoder({
90
- output: (audioData) => this.playDecodedAudio(audioData),
91
- error: (err) => console.error('AudioDecoder error:', err),
92
- });
93
-
94
- if (callType === 'video') {
95
- // 4. Setup VideoDecoder
96
- this.setupVideoDecoder();
97
- }
98
-
99
- this.receiveLoop();
100
- };
101
-
102
- public setupVideoDecoder = (): void => {
103
- if (!this.videoWriter) return;
104
-
105
- if (this.videoDecoder) {
106
- try {
107
- if (this.videoDecoder.state !== 'closed') this.videoDecoder.close();
108
- } catch (e) {
109
- /* ignore */
110
- }
111
- }
112
-
113
- const videoDecoder = new VideoDecoder({
114
- output: async (frame) => {
115
- try {
116
- if (!this.videoWriter) {
117
- frame.close();
118
- return;
119
- }
120
-
121
- // Backpressure check: Nếu writer đang bận, drop frame để tránh overflow
122
- if (this.videoWriter.desiredSize! <= 0) {
123
- frame.close();
124
- return;
125
- }
126
-
127
- await this.videoWriter.write(frame);
128
- } catch (err) {
129
- frame.close();
130
- // console.error('Frame write error:', err);
131
- } finally {
132
- frame.close();
133
- }
134
- },
135
- error: (err) => {
136
- console.error('❌ VideoDecoder CRASHED:', err);
137
- this.isWaitingForKeyFrame = true;
138
-
139
- if (this.videoWriter) {
140
- // Chỉ hồi sinh nếu Writer vẫn còn sống
141
- console.log('♻️ Attempting to respawn VideoDecoder...');
142
- this.setupVideoDecoder();
143
- if (this.lastVideoConfig && this.videoDecoder) {
144
- try {
145
- this.videoDecoder.configure(this.lastVideoConfig);
146
- } catch (configErr) {}
147
- }
148
- }
149
- },
150
- });
151
-
152
- this.videoDecoder = videoDecoder;
153
- };
154
-
155
- private playDecodedAudio = (audioData: AudioData): void => {
156
- try {
157
- if (!this.audioContext || !this.mediaDestination) {
158
- audioData.close();
159
- return;
160
- }
161
-
162
- const { numberOfChannels, numberOfFrames, sampleRate } = audioData;
163
- const duration = numberOfFrames / sampleRate;
164
- const currentTime = this.audioContext.currentTime;
165
-
166
- // --- XỬ LÝ LATENCY & SYNC ---
167
- if (this.nextStartTime < currentTime) {
168
- this.nextStartTime = currentTime;
169
- } else if (this.nextStartTime > currentTime + MAX_AUDIO_LATENCY) {
170
- // Nếu buffer quá lớn (latency cao), reset về thời điểm hiện tại
171
- this.nextStartTime = currentTime + MIN_BUFFER_AHEAD;
172
- }
173
-
174
- const audioBuffer = this.audioContext.createBuffer(numberOfChannels, numberOfFrames, sampleRate);
175
- const size = numberOfChannels * numberOfFrames;
176
- const tempBuffer = new Float32Array(size);
177
-
178
- audioData.copyTo(tempBuffer, { planeIndex: 0, format: 'f32-planar' });
179
-
180
- for (let ch = 0; ch < numberOfChannels; ch++) {
181
- const channelData = tempBuffer.subarray(ch * numberOfFrames, (ch + 1) * numberOfFrames);
182
- audioBuffer.copyToChannel(channelData, ch);
183
- }
184
-
185
- const source = this.audioContext.createBufferSource();
186
- source.buffer = audioBuffer;
187
- source.connect(this.mediaDestination);
188
-
189
- source.start(this.nextStartTime);
190
- this.nextStartTime += duration;
191
-
192
- audioData.close();
193
- } catch (err) {
194
- console.error('Error in playDecodedAudio:', err);
195
- audioData?.close();
196
- }
197
- };
198
-
199
- private newCodecFromDescription = (buffer: ArrayBuffer, receivedCodec: string): string => {
200
- // Chỉ check nếu codec là HEVC (hvc1 hoặc hev1)
201
- if (!receivedCodec.toLowerCase().includes('hvc') && !receivedCodec.toLowerCase().includes('hev')) return '';
202
-
203
- try {
204
- const record = HEVCDecoderConfigurationRecord.demux(buffer);
205
- return record.toCodecString();
206
- } catch (error) {
207
- return '';
208
- }
209
- };
210
-
211
- // Vòng lặp chính xử lý dữ liệu
212
- public receiveLoop = async (): Promise<void> => {
213
- const textDecoder = new TextDecoder();
214
-
215
- while (true) {
216
- try {
217
- if (!this.nodeCall) break;
218
-
219
- // Gọi hàm nhận dữ liệu async
220
- const data = await this.nodeCall.asyncRecv();
221
-
222
- const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
223
- const frameType = dataView.getUint8(0);
224
-
225
- // const frameTypeName =
226
- // {
227
- // [FRAME_TYPE.VIDEO_CONFIG]: 'VIDEO_CONFIG',
228
- // [FRAME_TYPE.AUDIO_CONFIG]: 'AUDIO_CONFIG',
229
- // [FRAME_TYPE.VIDEO_KEY]: 'VIDEO_KEY',
230
- // [FRAME_TYPE.VIDEO_DELTA]: 'VIDEO_DELTA',
231
- // [FRAME_TYPE.AUDIO]: 'AUDIO',
232
- // [FRAME_TYPE.CONNECTED]: 'CONNECTED',
233
- // [FRAME_TYPE.TRANSCEIVER_STATE]: 'TRANSCEIVER_STATE',
234
- // [FRAME_TYPE.ORIENTATION]: 'ORIENTATION',
235
- // [FRAME_TYPE.REQUEST_CONFIG]: 'REQUEST_CONFIG',
236
- // [FRAME_TYPE.REQUEST_KEY_FRAME]: 'REQUEST_KEY_FRAME',
237
- // [FRAME_TYPE.END_CALL]: 'END_CALL',
238
- // }[frameType] || 'UNKNOWN';
239
-
240
- // console.log(`----frameType ${frameTypeName}----`, frameType);
241
-
242
- const payloadOffset = (
243
- [
244
- FRAME_TYPE.VIDEO_CONFIG,
245
- FRAME_TYPE.AUDIO_CONFIG,
246
- FRAME_TYPE.CONNECTED,
247
- FRAME_TYPE.TRANSCEIVER_STATE,
248
- FRAME_TYPE.ORIENTATION,
249
- FRAME_TYPE.REQUEST_CONFIG,
250
- FRAME_TYPE.REQUEST_KEY_FRAME,
251
- FRAME_TYPE.END_CALL,
252
- ] as number[]
253
- ).includes(frameType)
254
- ? 1
255
- : 9; // 1 byte type + 8 bytes timestamp
256
-
257
- const payload = new Uint8Array(data.buffer, data.byteOffset + payloadOffset, data.byteLength - payloadOffset);
258
-
259
- switch (frameType) {
260
- // --- VIDEO CONFIG ---
261
- case FRAME_TYPE.VIDEO_CONFIG: {
262
- try {
263
- const videoConfigStr = textDecoder.decode(payload);
264
- const videoConfig = JSON.parse(videoConfigStr);
265
- console.log('--videoConfig--', videoConfig);
266
-
267
- // Setup Video Track Writer & Combine Streams
268
- if (!this.videoWriter) {
269
- // @ts-ignore: MediaStreamTrackGenerator types
270
- const videoTrackGenerator = new MediaStreamTrackGenerator({ kind: 'video' });
271
- this.videoWriter = videoTrackGenerator.writable.getWriter();
272
-
273
- // MERGE: Add Video Track vào Stream Audio đã có sẵn
274
- if (this.generatedStream) {
275
- this.generatedStream.addTrack(videoTrackGenerator);
276
- }
277
- }
278
-
279
- if (!this.videoDecoder) {
280
- this.setupVideoDecoder();
281
- }
282
-
283
- this.isWaitingForKeyFrame = true;
284
-
285
- let descriptionBuffer: ArrayBuffer | undefined = undefined;
286
- if (videoConfig.description) {
287
- try {
288
- const binaryString = atob(videoConfig.description);
289
- const desc = new Uint8Array(binaryString.length);
290
- for (let i = 0; i < binaryString.length; i++) {
291
- desc[i] = binaryString.charCodeAt(i);
292
- }
293
- descriptionBuffer = desc.buffer;
294
- } catch (e) {}
295
- }
296
-
297
- // const newCodec = replaceCodecNumber(videoConfig.codec);
298
-
299
- let newCodec: string = '';
300
- if (descriptionBuffer) {
301
- newCodec =
302
- this.newCodecFromDescription(descriptionBuffer, videoConfig.codec) ||
303
- replaceCodecNumber(videoConfig.codec);
304
- }
305
-
306
- const decoderConfig: VideoConfig = {
307
- codec: newCodec,
308
- frameRate: videoConfig.frameRate,
309
- codedWidth: videoConfig.codedWidth,
310
- codedHeight: videoConfig.codedHeight,
311
- ...(videoConfig.orientation && { rotation: videoConfig.orientation }),
312
- ...(descriptionBuffer && { description: descriptionBuffer }),
313
- };
314
-
315
- this.lastVideoConfig = decoderConfig;
316
-
317
- if (this.videoDecoder && this.videoDecoder.state !== 'closed') {
318
- const support = await VideoDecoder.isConfigSupported(decoderConfig);
319
- if (support.supported) {
320
- this.videoDecoder.configure(decoderConfig);
321
- this.isWaitingForKeyFrame = true;
322
- } else {
323
- console.error('❌ Browser does not support this video config:', decoderConfig);
324
- }
325
- }
326
- } catch (error) {
327
- console.error('❌ Error processing VIDEO_CONFIG:', error);
328
- }
329
- break;
330
- }
331
-
332
- // --- AUDIO CONFIG ---
333
- case FRAME_TYPE.AUDIO_CONFIG: {
334
- const audioConfig = JSON.parse(textDecoder.decode(payload));
335
- console.log('--audioConfig--', audioConfig);
336
-
337
- if (this.audioDecoder?.state !== 'closed') {
338
- this.audioDecoder?.configure({
339
- codec: audioConfig.codec,
340
- sampleRate: audioConfig.sampleRate,
341
- numberOfChannels: audioConfig.numberOfChannels,
342
- });
343
- }
344
- break;
345
- }
346
-
347
- // --- VIDEO DATA ---
348
- case FRAME_TYPE.VIDEO_KEY:
349
- case FRAME_TYPE.VIDEO_DELTA: {
350
- if (!this.videoDecoder || this.videoDecoder.state !== 'configured') break;
351
- const isKeyFrame = frameType === FRAME_TYPE.VIDEO_KEY;
352
-
353
- if (this.isWaitingForKeyFrame) {
354
- if (!isKeyFrame) break;
355
- console.log('✅ Resumed decoding at KeyFrame');
356
- this.isWaitingForKeyFrame = false;
357
- }
358
-
359
- if (!isKeyFrame && this.videoDecoder.decodeQueueSize > 15) {
360
- console.warn('⚠️ Queue > 15. Dropping & Waiting for KeyFrame...');
361
- // Nếu drop bất kỳ frame nào, ta phải chờ Key Frame tiếp theo mới decode được
362
- this.isWaitingForKeyFrame = true;
363
- break;
364
- }
365
-
366
- // const timestamp = dataView.getUint32(1, false);
367
- const timestampBigInt = dataView.getBigUint64(1, false);
368
- const timestamp = Number(timestampBigInt);
369
-
370
- try {
371
- this.videoDecoder.decode(
372
- new EncodedVideoChunk({
373
- type: isKeyFrame ? 'key' : 'delta',
374
- timestamp: timestamp,
375
- data: payload,
376
- }),
377
- );
378
- } catch (decodeErr) {
379
- console.error('Video decode failed:', decodeErr);
380
- if ((this.videoDecoder as VideoDecoder).state === 'closed') {
381
- // this.setupVideoDecoder();
382
- // if (this.lastVideoConfig) {
383
- // this.videoDecoder?.configure(this.lastVideoConfig);
384
- // }
385
- this.isWaitingForKeyFrame = true;
386
- }
387
- }
388
- break;
389
- }
390
-
391
- // --- AUDIO DATA ---
392
- case FRAME_TYPE.AUDIO: {
393
- if (this.audioDecoder?.state === 'configured') {
394
- // const timestamp = dataView.getUint32(1, false);
395
- const timestampBigInt = dataView.getBigUint64(1, false);
396
- const timestamp = Number(timestampBigInt);
397
- this.audioDecoder.decode(
398
- new EncodedAudioChunk({
399
- type: 'key',
400
- timestamp: timestamp,
401
- data: payload,
402
- }),
403
- );
404
- }
405
- break;
406
- }
407
-
408
- case FRAME_TYPE.CONNECTED:
409
- if (this.events.onConnected) {
410
- this.events.onConnected();
411
- }
412
- break;
413
-
414
- case FRAME_TYPE.TRANSCEIVER_STATE:
415
- const transceiverState = JSON.parse(textDecoder.decode(payload));
416
- if (this.events.onTransceiverState) {
417
- this.events.onTransceiverState(transceiverState);
418
- }
419
- break;
420
-
421
- case FRAME_TYPE.ORIENTATION: {
422
- const orientation = dataView.getInt32(1, false);
423
- if (this.videoDecoder && this.lastVideoConfig && this.lastVideoConfig.rotation !== orientation) {
424
- this.lastVideoConfig.rotation = orientation;
425
- try {
426
- // 1. Configure lại với rotation mới
427
- this.videoDecoder.configure(this.lastVideoConfig);
428
-
429
- // 2. QUAN TRỌNG: Phải chờ KeyFrame mới để tránh lỗi decode Delta frame sau khi reset
430
- this.isWaitingForKeyFrame = true;
431
-
432
- console.log('🔄 Reconfigured rotation to', orientation, '- Waiting for next KeyFrame');
433
- } catch (configErr) {
434
- console.error('Error reconfiguring VideoDecoder with new orientation:', configErr);
435
- }
436
- }
437
- break;
438
- }
439
-
440
- case FRAME_TYPE.REQUEST_CONFIG:
441
- console.log('📥 Received REQUEST_CONFIG');
442
- if (this.events.onRequestConfig) {
443
- this.events.onRequestConfig();
444
- }
445
- break;
446
-
447
- case FRAME_TYPE.REQUEST_KEY_FRAME:
448
- console.log('📥 Received REQUEST_KEY_FRAME');
449
- if (this.events.onRequestKeyFrame) {
450
- this.events.onRequestKeyFrame();
451
- }
452
- break;
453
-
454
- case FRAME_TYPE.END_CALL:
455
- console.log('📥 Received END_CALL');
456
- if (this.events.onEndCall) {
457
- this.events.onEndCall();
458
- }
459
- break;
460
-
461
- default:
462
- console.warn('❓ Unknown frame type received:', frameType);
463
- break;
464
- }
465
- } catch (error) {
466
- console.error('Stream loop error', error);
467
- // Có thể thêm delay nhỏ ở đây để tránh spam error nếu loop lỗi liên tục
468
- await new Promise((r) => setTimeout(r, 200));
469
- }
470
- }
471
- };
472
-
473
- private resetDecoders = (): void => {
474
- if (this.videoWriter) {
475
- try {
476
- this.videoWriter.abort('Stream stopped').catch(() => {});
477
- this.videoWriter.releaseLock();
478
- } catch (e) {
479
- console.warn('Error closing video writer:', e);
480
- }
481
- this.videoWriter = null;
482
- }
483
-
484
- if (this.videoDecoder) {
485
- try {
486
- if (this.videoDecoder.state !== 'closed') {
487
- this.videoDecoder.reset();
488
- this.videoDecoder.close();
489
- }
490
- } catch (e) {}
491
- this.videoDecoder = null;
492
- }
493
-
494
- if (this.audioDecoder) {
495
- try {
496
- if (this.audioDecoder.state !== 'closed') {
497
- this.audioDecoder.reset();
498
- this.audioDecoder.close();
499
- }
500
- } catch (e) {}
501
- this.audioDecoder = null;
502
- }
503
-
504
- // Đóng AudioContext
505
- if (this.audioContext) {
506
- try {
507
- this.audioContext.close();
508
- } catch (e) {
509
- console.warn('Error closing audio context:', e);
510
- }
511
- this.audioContext = null;
512
- }
513
-
514
- // Reset các biến
515
- this.isWaitingForKeyFrame = true;
516
- this.mediaDestination = null;
517
- this.nextStartTime = 0;
518
- this.lastVideoConfig = null;
519
-
520
- if (this.generatedStream) {
521
- this.generatedStream.getTracks().forEach((track) => track.stop());
522
- this.generatedStream = null;
523
- }
524
- };
525
- }