react-native-ble-mesh 2.1.0 → 2.1.3
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.
- package/package.json +9 -2
- package/src/MeshNetwork.js +48 -46
- package/src/constants/audio.js +4 -4
- package/src/constants/ble.js +1 -1
- package/src/constants/crypto.js +1 -1
- package/src/constants/errors.js +2 -2
- package/src/constants/events.js +1 -1
- package/src/constants/protocol.js +2 -2
- package/src/crypto/AutoCrypto.js +2 -0
- package/src/crypto/CryptoProvider.js +17 -17
- package/src/crypto/providers/ExpoCryptoProvider.js +12 -7
- package/src/crypto/providers/QuickCryptoProvider.js +50 -15
- package/src/crypto/providers/TweetNaClProvider.js +9 -7
- package/src/errors/AudioError.js +2 -1
- package/src/errors/ConnectionError.js +2 -2
- package/src/errors/CryptoError.js +1 -1
- package/src/errors/HandshakeError.js +2 -2
- package/src/errors/MeshError.js +4 -4
- package/src/errors/MessageError.js +2 -2
- package/src/errors/ValidationError.js +3 -3
- package/src/expo/withBLEMesh.js +10 -10
- package/src/hooks/AppStateManager.js +10 -4
- package/src/hooks/useMesh.js +7 -7
- package/src/hooks/useMessages.js +13 -12
- package/src/hooks/usePeers.js +10 -9
- package/src/index.js +2 -2
- package/src/mesh/dedup/BloomFilter.js +1 -0
- package/src/mesh/dedup/DedupManager.js +4 -7
- package/src/mesh/dedup/MessageCache.js +3 -0
- package/src/mesh/fragment/Assembler.js +5 -4
- package/src/mesh/fragment/Fragmenter.js +2 -2
- package/src/mesh/monitor/ConnectionQuality.js +17 -8
- package/src/mesh/monitor/NetworkMonitor.js +22 -15
- package/src/mesh/peer/Peer.js +4 -9
- package/src/mesh/peer/PeerDiscovery.js +18 -19
- package/src/mesh/peer/PeerManager.js +14 -14
- package/src/mesh/router/MessageRouter.js +15 -15
- package/src/mesh/router/PathFinder.js +10 -13
- package/src/mesh/router/RouteTable.js +8 -7
- package/src/mesh/store/StoreAndForwardManager.js +20 -23
- package/src/protocol/message.js +5 -13
- package/src/protocol/serializer.js +4 -4
- package/src/protocol/validator.js +7 -6
- package/src/service/BatteryOptimizer.js +18 -17
- package/src/service/EmergencyManager.js +19 -26
- package/src/service/HandshakeManager.js +100 -2
- package/src/service/MeshService.js +106 -22
- package/src/service/SessionManager.js +38 -3
- package/src/service/audio/AudioManager.js +80 -38
- package/src/service/audio/buffer/FrameBuffer.js +7 -8
- package/src/service/audio/buffer/JitterBuffer.js +1 -1
- package/src/service/audio/codec/LC3Codec.js +22 -20
- package/src/service/audio/codec/LC3Decoder.js +10 -10
- package/src/service/audio/codec/LC3Encoder.js +11 -9
- package/src/service/audio/session/AudioSession.js +14 -17
- package/src/service/audio/session/VoiceMessage.js +15 -22
- package/src/service/audio/transport/AudioFragmenter.js +17 -9
- package/src/service/audio/transport/AudioFramer.js +8 -12
- package/src/service/file/FileAssembler.js +4 -2
- package/src/service/file/FileChunker.js +1 -1
- package/src/service/file/FileManager.js +26 -20
- package/src/service/file/FileMessage.js +7 -12
- package/src/service/text/TextManager.js +55 -28
- package/src/service/text/broadcast/BroadcastManager.js +14 -17
- package/src/service/text/channel/Channel.js +10 -14
- package/src/service/text/channel/ChannelManager.js +10 -10
- package/src/service/text/message/TextMessage.js +12 -19
- package/src/service/text/message/TextSerializer.js +2 -2
- package/src/storage/AsyncStorageAdapter.js +17 -14
- package/src/storage/MemoryStorage.js +11 -8
- package/src/storage/MessageStore.js +22 -30
- package/src/storage/Storage.js +9 -9
- package/src/transport/BLETransport.js +16 -14
- package/src/transport/MockTransport.js +7 -2
- package/src/transport/MultiTransport.js +13 -6
- package/src/transport/Transport.js +9 -9
- package/src/transport/WiFiDirectTransport.js +25 -24
- package/src/transport/adapters/BLEAdapter.js +19 -19
- package/src/transport/adapters/NodeBLEAdapter.js +24 -23
- package/src/transport/adapters/RNBLEAdapter.js +19 -24
- package/src/utils/EventEmitter.js +17 -12
- package/src/utils/LRUCache.js +10 -4
- package/src/utils/RateLimiter.js +1 -1
- package/src/utils/compression.js +6 -8
- package/src/utils/encoding.js +8 -2
- package/src/utils/retry.js +11 -13
- package/src/utils/time.js +9 -4
- package/src/utils/validation.js +1 -1
|
@@ -18,7 +18,7 @@ const { unpackFrame, createStreamFrame } = require('./transport/AudioFramer');
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Audio manager states
|
|
21
|
-
* @constant {
|
|
21
|
+
* @constant {any}
|
|
22
22
|
*/
|
|
23
23
|
const MANAGER_STATE = Object.freeze({
|
|
24
24
|
UNINITIALIZED: 'uninitialized',
|
|
@@ -37,28 +37,29 @@ const MANAGER_STATE = Object.freeze({
|
|
|
37
37
|
class AudioManager extends EventEmitter {
|
|
38
38
|
/**
|
|
39
39
|
* Creates a new AudioManager
|
|
40
|
-
* @param {
|
|
41
|
-
* @param {string} [options.quality='MEDIUM'] - Audio quality preset
|
|
40
|
+
* @param {any} [options] - Manager options
|
|
42
41
|
*/
|
|
43
42
|
constructor(options = {}) {
|
|
44
43
|
super();
|
|
45
44
|
|
|
46
|
-
/** @private */
|
|
45
|
+
/** @type {string} @private */
|
|
47
46
|
this._quality = options.quality || 'MEDIUM';
|
|
48
|
-
/** @private */
|
|
47
|
+
/** @type {string} @private */
|
|
49
48
|
this._state = MANAGER_STATE.UNINITIALIZED;
|
|
50
|
-
/** @private */
|
|
49
|
+
/** @type {any} @private */
|
|
51
50
|
this._meshService = null;
|
|
52
|
-
/** @private */
|
|
51
|
+
/** @type {any} @private */
|
|
53
52
|
this._codec = null;
|
|
54
|
-
/** @private */
|
|
53
|
+
/** @type {Map<string, any>} @private */
|
|
55
54
|
this._sessions = new Map(); // peerId -> AudioSession
|
|
56
|
-
/** @private */
|
|
55
|
+
/** @type {Map<string, any>} @private */
|
|
57
56
|
this._pendingRequests = new Map(); // peerId -> { resolve, reject, timeout }
|
|
58
57
|
/** @private */
|
|
59
58
|
this._voiceAssembler = new AudioAssembler();
|
|
60
|
-
/** @private */
|
|
59
|
+
/** @type {any} @private */
|
|
61
60
|
this._senderId = null;
|
|
61
|
+
/** @type {string|undefined} @private */
|
|
62
|
+
this._lastVoiceMessagePeerId = undefined;
|
|
62
63
|
|
|
63
64
|
this._setupAssemblerEvents();
|
|
64
65
|
}
|
|
@@ -68,15 +69,15 @@ class AudioManager extends EventEmitter {
|
|
|
68
69
|
* @private
|
|
69
70
|
*/
|
|
70
71
|
_setupAssemblerEvents() {
|
|
71
|
-
this._voiceAssembler.on('complete', (data) => {
|
|
72
|
+
this._voiceAssembler.on('complete', (/** @type {any} */ data) => {
|
|
72
73
|
this._handleVoiceMessageComplete(data);
|
|
73
74
|
});
|
|
74
75
|
|
|
75
|
-
this._voiceAssembler.on('progress', (data) => {
|
|
76
|
+
this._voiceAssembler.on('progress', (/** @type {any} */ data) => {
|
|
76
77
|
this.emit(EVENTS.VOICE_MESSAGE_PROGRESS, data);
|
|
77
78
|
});
|
|
78
79
|
|
|
79
|
-
this._voiceAssembler.on('timeout', (data) => {
|
|
80
|
+
this._voiceAssembler.on('timeout', (/** @type {any} */ data) => {
|
|
80
81
|
this.emit(EVENTS.VOICE_MESSAGE_FAILED, {
|
|
81
82
|
...data,
|
|
82
83
|
error: AudioError.voiceMessageTimeout(data.messageId)
|
|
@@ -86,7 +87,7 @@ class AudioManager extends EventEmitter {
|
|
|
86
87
|
|
|
87
88
|
/**
|
|
88
89
|
* Initializes the audio manager
|
|
89
|
-
* @param {
|
|
90
|
+
* @param {any} meshService - Mesh service instance
|
|
90
91
|
* @returns {Promise<void>}
|
|
91
92
|
*/
|
|
92
93
|
async initialize(meshService) {
|
|
@@ -104,13 +105,13 @@ class AudioManager extends EventEmitter {
|
|
|
104
105
|
this._senderId = identity.publicKey || new Uint8Array(32);
|
|
105
106
|
|
|
106
107
|
// Initialize codec
|
|
107
|
-
const qualityConfig = AUDIO_QUALITY[this._quality];
|
|
108
|
+
const qualityConfig = /** @type {any} */ (AUDIO_QUALITY)[this._quality];
|
|
108
109
|
this._codec = new LC3Codec(qualityConfig);
|
|
109
110
|
await this._codec.initialize();
|
|
110
111
|
|
|
111
112
|
this._setState(MANAGER_STATE.READY);
|
|
112
113
|
this.emit('initialized', { quality: this._quality, codec: this._codec.getConfig() });
|
|
113
|
-
} catch (error) {
|
|
114
|
+
} catch (/** @type {any} */ error) {
|
|
114
115
|
this._setState(MANAGER_STATE.ERROR);
|
|
115
116
|
throw AudioError.codecInitFailed({ reason: error.message });
|
|
116
117
|
}
|
|
@@ -122,6 +123,7 @@ class AudioManager extends EventEmitter {
|
|
|
122
123
|
*/
|
|
123
124
|
async destroy() {
|
|
124
125
|
// End all sessions and wait for completion
|
|
126
|
+
/** @type {Promise<void>[]} */
|
|
125
127
|
const endPromises = [];
|
|
126
128
|
for (const [, session] of this._sessions) {
|
|
127
129
|
if (session && typeof session.end === 'function') {
|
|
@@ -156,7 +158,8 @@ class AudioManager extends EventEmitter {
|
|
|
156
158
|
* @param {string} quality - Quality preset (LOW, MEDIUM, HIGH)
|
|
157
159
|
*/
|
|
158
160
|
setQuality(quality) {
|
|
159
|
-
|
|
161
|
+
const qualities = /** @type {any} */ (AUDIO_QUALITY);
|
|
162
|
+
if (!qualities[quality]) {
|
|
160
163
|
throw AudioError.invalidConfig(`Unknown quality: ${quality}`);
|
|
161
164
|
}
|
|
162
165
|
this._quality = quality;
|
|
@@ -172,7 +175,7 @@ class AudioManager extends EventEmitter {
|
|
|
172
175
|
|
|
173
176
|
/**
|
|
174
177
|
* Returns codec info
|
|
175
|
-
* @returns {
|
|
178
|
+
* @returns {any}
|
|
176
179
|
*/
|
|
177
180
|
getCodecInfo() {
|
|
178
181
|
return this._codec ? this._codec.getConfig() : null;
|
|
@@ -181,7 +184,7 @@ class AudioManager extends EventEmitter {
|
|
|
181
184
|
/**
|
|
182
185
|
* Requests an audio stream with a peer
|
|
183
186
|
* @param {string} peerId - Remote peer ID
|
|
184
|
-
* @returns {Promise<
|
|
187
|
+
* @returns {Promise<any>}
|
|
185
188
|
*/
|
|
186
189
|
async requestStream(peerId) {
|
|
187
190
|
this._validateReady();
|
|
@@ -194,7 +197,7 @@ class AudioManager extends EventEmitter {
|
|
|
194
197
|
peerId,
|
|
195
198
|
codec: this._codec,
|
|
196
199
|
isInitiator: true,
|
|
197
|
-
sendCallback: (data) => this._sendStreamData(data)
|
|
200
|
+
sendCallback: (/** @type {any} */ data) => this._sendStreamData(data)
|
|
198
201
|
});
|
|
199
202
|
|
|
200
203
|
session.setRequesting();
|
|
@@ -219,7 +222,7 @@ class AudioManager extends EventEmitter {
|
|
|
219
222
|
/**
|
|
220
223
|
* Accepts an incoming stream request
|
|
221
224
|
* @param {string} peerId - Remote peer ID
|
|
222
|
-
* @returns {Promise<
|
|
225
|
+
* @returns {Promise<any>}
|
|
223
226
|
*/
|
|
224
227
|
async acceptStream(peerId) {
|
|
225
228
|
this._validateReady();
|
|
@@ -270,7 +273,7 @@ class AudioManager extends EventEmitter {
|
|
|
270
273
|
/**
|
|
271
274
|
* Gets a session by peer ID
|
|
272
275
|
* @param {string} peerId - Peer ID
|
|
273
|
-
* @returns {
|
|
276
|
+
* @returns {any}
|
|
274
277
|
*/
|
|
275
278
|
getSession(peerId) {
|
|
276
279
|
return this._sessions.get(peerId);
|
|
@@ -278,11 +281,11 @@ class AudioManager extends EventEmitter {
|
|
|
278
281
|
|
|
279
282
|
/**
|
|
280
283
|
* Gets all active sessions
|
|
281
|
-
* @returns {
|
|
284
|
+
* @returns {any[]}
|
|
282
285
|
*/
|
|
283
286
|
getActiveSessions() {
|
|
284
287
|
return Array.from(this._sessions.values())
|
|
285
|
-
.filter(s => s.getState() === AUDIO_SESSION_STATE.ACTIVE);
|
|
288
|
+
.filter((/** @type {any} */ s) => s.getState() === AUDIO_SESSION_STATE.ACTIVE);
|
|
286
289
|
}
|
|
287
290
|
|
|
288
291
|
/**
|
|
@@ -301,7 +304,7 @@ class AudioManager extends EventEmitter {
|
|
|
301
304
|
/**
|
|
302
305
|
* Sends a voice message to a peer
|
|
303
306
|
* @param {string} peerId - Remote peer ID
|
|
304
|
-
* @param {
|
|
307
|
+
* @param {any} message - Voice message to send
|
|
305
308
|
* @returns {Promise<string>} Message ID
|
|
306
309
|
*/
|
|
307
310
|
async sendVoiceMessage(peerId, message) {
|
|
@@ -353,7 +356,10 @@ class AudioManager extends EventEmitter {
|
|
|
353
356
|
}
|
|
354
357
|
}
|
|
355
358
|
|
|
356
|
-
/**
|
|
359
|
+
/**
|
|
360
|
+
* @param {string} peerId
|
|
361
|
+
* @private
|
|
362
|
+
*/
|
|
357
363
|
_handleStreamRequest(peerId) {
|
|
358
364
|
if (this._sessions.has(peerId)) { return; }
|
|
359
365
|
|
|
@@ -361,7 +367,7 @@ class AudioManager extends EventEmitter {
|
|
|
361
367
|
peerId,
|
|
362
368
|
codec: this._codec,
|
|
363
369
|
isInitiator: false,
|
|
364
|
-
sendCallback: (data) => this._sendStreamData(data)
|
|
370
|
+
sendCallback: (/** @type {any} */ data) => this._sendStreamData(data)
|
|
365
371
|
});
|
|
366
372
|
|
|
367
373
|
session.setPending();
|
|
@@ -370,7 +376,10 @@ class AudioManager extends EventEmitter {
|
|
|
370
376
|
this.emit(EVENTS.AUDIO_STREAM_REQUEST, { peerId });
|
|
371
377
|
}
|
|
372
378
|
|
|
373
|
-
/**
|
|
379
|
+
/**
|
|
380
|
+
* @param {string} peerId
|
|
381
|
+
* @private
|
|
382
|
+
*/
|
|
374
383
|
_handleStreamAccept(peerId) {
|
|
375
384
|
const request = this._pendingRequests.get(peerId);
|
|
376
385
|
if (request) {
|
|
@@ -383,7 +392,11 @@ class AudioManager extends EventEmitter {
|
|
|
383
392
|
}
|
|
384
393
|
}
|
|
385
394
|
|
|
386
|
-
/**
|
|
395
|
+
/**
|
|
396
|
+
* @param {string} peerId
|
|
397
|
+
* @param {Uint8Array} payload
|
|
398
|
+
* @private
|
|
399
|
+
*/
|
|
387
400
|
_handleStreamReject(peerId, payload) {
|
|
388
401
|
const request = this._pendingRequests.get(peerId);
|
|
389
402
|
if (request) {
|
|
@@ -395,20 +408,27 @@ class AudioManager extends EventEmitter {
|
|
|
395
408
|
}
|
|
396
409
|
}
|
|
397
410
|
|
|
398
|
-
/**
|
|
411
|
+
/**
|
|
412
|
+
* @param {string} peerId
|
|
413
|
+
* @param {Uint8Array} payload
|
|
414
|
+
* @private
|
|
415
|
+
*/
|
|
399
416
|
_handleStreamData(peerId, payload) {
|
|
400
417
|
const session = this._sessions.get(peerId);
|
|
401
418
|
if (session && session.getState() === AUDIO_SESSION_STATE.ACTIVE) {
|
|
402
419
|
try {
|
|
403
420
|
const { frame, sequenceNumber, timestampDelta } = unpackFrame(payload);
|
|
404
421
|
session.receiveAudio(frame, sequenceNumber, timestampDelta);
|
|
405
|
-
} catch (error) {
|
|
422
|
+
} catch (/** @type {any} */ error) {
|
|
406
423
|
this.emit('error', error);
|
|
407
424
|
}
|
|
408
425
|
}
|
|
409
426
|
}
|
|
410
427
|
|
|
411
|
-
/**
|
|
428
|
+
/**
|
|
429
|
+
* @param {string} peerId
|
|
430
|
+
* @private
|
|
431
|
+
*/
|
|
412
432
|
_handleStreamEnd(peerId) {
|
|
413
433
|
const session = this._sessions.get(peerId);
|
|
414
434
|
if (session) {
|
|
@@ -418,7 +438,11 @@ class AudioManager extends EventEmitter {
|
|
|
418
438
|
}
|
|
419
439
|
}
|
|
420
440
|
|
|
421
|
-
/**
|
|
441
|
+
/**
|
|
442
|
+
* @param {string} peerId
|
|
443
|
+
* @param {Uint8Array} payload
|
|
444
|
+
* @private
|
|
445
|
+
*/
|
|
422
446
|
_handleVoiceMessageFragment(peerId, payload) {
|
|
423
447
|
const fullPayload = new Uint8Array(payload.length + 1);
|
|
424
448
|
fullPayload[0] = payload[0]; // type is already in payload for fragments
|
|
@@ -431,7 +455,10 @@ class AudioManager extends EventEmitter {
|
|
|
431
455
|
}
|
|
432
456
|
}
|
|
433
457
|
|
|
434
|
-
/**
|
|
458
|
+
/**
|
|
459
|
+
* @param {any} param0
|
|
460
|
+
* @private
|
|
461
|
+
*/
|
|
435
462
|
_handleVoiceMessageComplete({ messageId, size }) {
|
|
436
463
|
// Note: The actual voice message data comes from the assembler
|
|
437
464
|
this.emit(EVENTS.VOICE_MESSAGE_RECEIVED, {
|
|
@@ -441,13 +468,21 @@ class AudioManager extends EventEmitter {
|
|
|
441
468
|
});
|
|
442
469
|
}
|
|
443
470
|
|
|
444
|
-
/**
|
|
471
|
+
/**
|
|
472
|
+
* @param {any} param0
|
|
473
|
+
* @private
|
|
474
|
+
*/
|
|
445
475
|
async _sendStreamData({ peerId, frame, sequenceNumber, timestampDelta }) {
|
|
446
476
|
const packed = createStreamFrame(frame, sequenceNumber, timestampDelta);
|
|
447
477
|
await this._sendRaw(peerId, packed);
|
|
448
478
|
}
|
|
449
479
|
|
|
450
|
-
/**
|
|
480
|
+
/**
|
|
481
|
+
* @param {string} peerId
|
|
482
|
+
* @param {number} type
|
|
483
|
+
* @param {Uint8Array} payload
|
|
484
|
+
* @private
|
|
485
|
+
*/
|
|
451
486
|
async _sendMessage(peerId, type, payload) {
|
|
452
487
|
const data = new Uint8Array(1 + payload.length);
|
|
453
488
|
data[0] = type;
|
|
@@ -455,7 +490,11 @@ class AudioManager extends EventEmitter {
|
|
|
455
490
|
await this._sendRaw(peerId, data);
|
|
456
491
|
}
|
|
457
492
|
|
|
458
|
-
/**
|
|
493
|
+
/**
|
|
494
|
+
* @param {string} peerId
|
|
495
|
+
* @param {Uint8Array} data
|
|
496
|
+
* @private
|
|
497
|
+
*/
|
|
459
498
|
async _sendRaw(peerId, data) {
|
|
460
499
|
if (this._meshService && typeof this._meshService._sendRaw === 'function') {
|
|
461
500
|
await this._meshService._sendRaw(peerId, data);
|
|
@@ -474,7 +513,10 @@ class AudioManager extends EventEmitter {
|
|
|
474
513
|
}
|
|
475
514
|
}
|
|
476
515
|
|
|
477
|
-
/**
|
|
516
|
+
/**
|
|
517
|
+
* @param {string} newState
|
|
518
|
+
* @private
|
|
519
|
+
*/
|
|
478
520
|
_setState(newState) {
|
|
479
521
|
this._state = newState;
|
|
480
522
|
}
|
|
@@ -14,18 +14,16 @@ const { VOICE_MESSAGE_CONFIG } = require('../../../constants/audio');
|
|
|
14
14
|
class FrameBuffer {
|
|
15
15
|
/**
|
|
16
16
|
* Creates a new FrameBuffer
|
|
17
|
-
* @param {
|
|
18
|
-
* @param {number} [options.maxFrames=30000] - Maximum frames to store
|
|
19
|
-
* @param {number} [options.maxBytes] - Maximum bytes to store
|
|
17
|
+
* @param {any} [options] - Buffer options
|
|
20
18
|
*/
|
|
21
19
|
constructor(options = {}) {
|
|
22
|
-
/** @private */
|
|
20
|
+
/** @type {number} @private */
|
|
23
21
|
this._maxFrames = options.maxFrames || 30000;
|
|
24
|
-
/** @private */
|
|
22
|
+
/** @type {number} @private */
|
|
25
23
|
this._maxBytes = options.maxBytes || VOICE_MESSAGE_CONFIG.MAX_SIZE_BYTES;
|
|
26
|
-
/** @private */
|
|
24
|
+
/** @type {Uint8Array[]} @private */
|
|
27
25
|
this._frames = [];
|
|
28
|
-
/** @private */
|
|
26
|
+
/** @type {number} @private */
|
|
29
27
|
this._totalBytes = 0;
|
|
30
28
|
}
|
|
31
29
|
|
|
@@ -99,6 +97,7 @@ class FrameBuffer {
|
|
|
99
97
|
*/
|
|
100
98
|
serialize() {
|
|
101
99
|
// Format: [frameCount(4)] + [frameLen(2) + frameData]...
|
|
100
|
+
/** @type {Uint8Array[]} */
|
|
102
101
|
const parts = [];
|
|
103
102
|
const countBytes = new Uint8Array(4);
|
|
104
103
|
new DataView(countBytes.buffer).setUint32(0, this._frames.length, false);
|
|
@@ -111,7 +110,7 @@ class FrameBuffer {
|
|
|
111
110
|
parts.push(frame);
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
const totalLen = parts.reduce((sum, p) => sum + p.length, 0);
|
|
113
|
+
const totalLen = parts.reduce((/** @type {number} */ sum, /** @type {Uint8Array} */ p) => sum + p.length, 0);
|
|
115
114
|
const result = new Uint8Array(totalLen);
|
|
116
115
|
let offset = 0;
|
|
117
116
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
/* global __DEV__ */
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @fileoverview LC3 codec wrapper for React Native
|
|
@@ -17,30 +18,27 @@ const { LC3_CONFIG, AUDIO_QUALITY } = require('../../../constants/audio');
|
|
|
17
18
|
class LC3Codec extends EventEmitter {
|
|
18
19
|
/**
|
|
19
20
|
* Creates a new LC3Codec instance
|
|
20
|
-
* @param {
|
|
21
|
-
* @param {number} [options.sampleRate=16000] - Sample rate in Hz
|
|
22
|
-
* @param {number} [options.frameMs=10] - Frame duration in milliseconds
|
|
23
|
-
* @param {number} [options.bitRate=24000] - Bit rate in bps
|
|
24
|
-
* @param {number} [options.channels=1] - Number of audio channels
|
|
21
|
+
* @param {any} [options] - Codec configuration
|
|
25
22
|
*/
|
|
26
23
|
constructor(options = {}) {
|
|
27
24
|
super();
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
/** @type {any} */
|
|
27
|
+
const quality = options.quality ? /** @type {any} */ (AUDIO_QUALITY)[options.quality] : null;
|
|
30
28
|
|
|
31
|
-
/** @private */
|
|
29
|
+
/** @type {number} @private */
|
|
32
30
|
this._sampleRate = quality?.sampleRate || options.sampleRate || LC3_CONFIG.DEFAULT_SAMPLE_RATE;
|
|
33
|
-
/** @private */
|
|
31
|
+
/** @type {number} @private */
|
|
34
32
|
this._frameMs = quality?.frameMs || options.frameMs || LC3_CONFIG.DEFAULT_FRAME_DURATION_MS;
|
|
35
|
-
/** @private */
|
|
33
|
+
/** @type {number} @private */
|
|
36
34
|
this._bitRate = quality?.bitRate || options.bitRate || LC3_CONFIG.DEFAULT_BIT_RATE;
|
|
37
|
-
/** @private */
|
|
35
|
+
/** @type {number} @private */
|
|
38
36
|
this._channels = quality?.channels || options.channels || LC3_CONFIG.DEFAULT_CHANNELS;
|
|
39
|
-
/** @private */
|
|
37
|
+
/** @type {boolean} @private */
|
|
40
38
|
this._initialized = false;
|
|
41
|
-
/** @private */
|
|
39
|
+
/** @type {any} @private */
|
|
42
40
|
this._nativeModule = null;
|
|
43
|
-
/** @private */
|
|
41
|
+
/** @type {boolean} @private */
|
|
44
42
|
this._useMock = false;
|
|
45
43
|
|
|
46
44
|
this._validateConfig();
|
|
@@ -82,12 +80,14 @@ class LC3Codec extends EventEmitter {
|
|
|
82
80
|
} else {
|
|
83
81
|
// Use mock implementation for testing
|
|
84
82
|
this._useMock = true;
|
|
85
|
-
|
|
83
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
84
|
+
console.warn('LC3Codec: Native module not available, using mock implementation');
|
|
85
|
+
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
this._initialized = true;
|
|
89
89
|
this.emit('initialized', this.getConfig());
|
|
90
|
-
} catch (error) {
|
|
90
|
+
} catch (/** @type {any} */ error) {
|
|
91
91
|
throw AudioError.codecInitFailed({ reason: error.message });
|
|
92
92
|
}
|
|
93
93
|
}
|
|
@@ -95,7 +95,7 @@ class LC3Codec extends EventEmitter {
|
|
|
95
95
|
/**
|
|
96
96
|
* Attempts to load the native LC3 module
|
|
97
97
|
* @private
|
|
98
|
-
* @returns {
|
|
98
|
+
* @returns {any}
|
|
99
99
|
*/
|
|
100
100
|
_loadNativeModule() {
|
|
101
101
|
try {
|
|
@@ -150,7 +150,7 @@ class LC3Codec extends EventEmitter {
|
|
|
150
150
|
try {
|
|
151
151
|
const result = await this._nativeModule.encode(Array.from(pcmSamples));
|
|
152
152
|
return new Uint8Array(result);
|
|
153
|
-
} catch (error) {
|
|
153
|
+
} catch (/** @type {any} */ error) {
|
|
154
154
|
throw AudioError.encodingFailed({ reason: error.message });
|
|
155
155
|
}
|
|
156
156
|
}
|
|
@@ -172,7 +172,7 @@ class LC3Codec extends EventEmitter {
|
|
|
172
172
|
try {
|
|
173
173
|
const result = await this._nativeModule.decode(Array.from(lc3Frame));
|
|
174
174
|
return new Int16Array(result);
|
|
175
|
-
} catch (error) {
|
|
175
|
+
} catch (/** @type {any} */ error) {
|
|
176
176
|
throw AudioError.decodingFailed({ reason: error.message });
|
|
177
177
|
}
|
|
178
178
|
}
|
|
@@ -193,13 +193,14 @@ class LC3Codec extends EventEmitter {
|
|
|
193
193
|
try {
|
|
194
194
|
const result = await this._nativeModule.decodePLC();
|
|
195
195
|
return new Int16Array(result);
|
|
196
|
-
} catch (error) {
|
|
196
|
+
} catch (/** @type {any} */ error) {
|
|
197
197
|
throw AudioError.decodingFailed({ reason: error.message });
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
/**
|
|
202
202
|
* Mock encode implementation for testing
|
|
203
|
+
* @param {Int16Array} pcmSamples
|
|
203
204
|
* @private
|
|
204
205
|
*/
|
|
205
206
|
_mockEncode(pcmSamples) {
|
|
@@ -217,6 +218,7 @@ class LC3Codec extends EventEmitter {
|
|
|
217
218
|
|
|
218
219
|
/**
|
|
219
220
|
* Mock decode implementation for testing
|
|
221
|
+
* @param {Uint8Array} lc3Frame
|
|
220
222
|
* @private
|
|
221
223
|
*/
|
|
222
224
|
_mockDecode(lc3Frame) {
|
|
@@ -261,7 +263,7 @@ class LC3Codec extends EventEmitter {
|
|
|
261
263
|
|
|
262
264
|
/**
|
|
263
265
|
* Returns the current codec configuration
|
|
264
|
-
* @returns {
|
|
266
|
+
* @returns {any}
|
|
265
267
|
*/
|
|
266
268
|
getConfig() {
|
|
267
269
|
return {
|
|
@@ -16,7 +16,7 @@ const AudioError = require('../../../errors/AudioError');
|
|
|
16
16
|
class LC3Decoder extends EventEmitter {
|
|
17
17
|
/**
|
|
18
18
|
* Creates a new LC3Decoder
|
|
19
|
-
* @param {
|
|
19
|
+
* @param {any} codec - Initialized LC3 codec instance
|
|
20
20
|
*/
|
|
21
21
|
constructor(codec) {
|
|
22
22
|
super();
|
|
@@ -25,17 +25,17 @@ class LC3Decoder extends EventEmitter {
|
|
|
25
25
|
throw AudioError.codecInitFailed({ reason: 'Codec must be initialized' });
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/** @private */
|
|
28
|
+
/** @type {any} @private */
|
|
29
29
|
this._codec = codec;
|
|
30
|
-
/** @private */
|
|
30
|
+
/** @type {number} @private */
|
|
31
31
|
this._frameSamples = codec.getFrameSamples();
|
|
32
|
-
/** @private */
|
|
32
|
+
/** @type {number} @private */
|
|
33
33
|
this._framesDecoded = 0;
|
|
34
|
-
/** @private */
|
|
34
|
+
/** @type {number} @private */
|
|
35
35
|
this._plcFrames = 0;
|
|
36
|
-
/** @private */
|
|
36
|
+
/** @type {number} @private */
|
|
37
37
|
this._errors = 0;
|
|
38
|
-
/** @private */
|
|
38
|
+
/** @type {number} @private */
|
|
39
39
|
this._lastSequence = -1;
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -63,7 +63,7 @@ class LC3Decoder extends EventEmitter {
|
|
|
63
63
|
this._framesDecoded++;
|
|
64
64
|
this.emit('samples', { samples, index: this._framesDecoded });
|
|
65
65
|
return samples;
|
|
66
|
-
} catch (error) {
|
|
66
|
+
} catch (/** @type {any} */ error) {
|
|
67
67
|
this._errors++;
|
|
68
68
|
this.emit('error', error);
|
|
69
69
|
throw error;
|
|
@@ -80,7 +80,7 @@ class LC3Decoder extends EventEmitter {
|
|
|
80
80
|
this._plcFrames++;
|
|
81
81
|
this.emit('plc', { samples, plcCount: this._plcFrames });
|
|
82
82
|
return samples;
|
|
83
|
-
} catch (error) {
|
|
83
|
+
} catch (/** @type {any} */ error) {
|
|
84
84
|
this._errors++;
|
|
85
85
|
this.emit('error', error);
|
|
86
86
|
// Return silence on PLC failure
|
|
@@ -106,7 +106,7 @@ class LC3Decoder extends EventEmitter {
|
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
108
|
* Returns decoder statistics
|
|
109
|
-
* @returns {
|
|
109
|
+
* @returns {any}
|
|
110
110
|
*/
|
|
111
111
|
getStats() {
|
|
112
112
|
return {
|
|
@@ -16,7 +16,7 @@ const AudioError = require('../../../errors/AudioError');
|
|
|
16
16
|
class LC3Encoder extends EventEmitter {
|
|
17
17
|
/**
|
|
18
18
|
* Creates a new LC3Encoder
|
|
19
|
-
* @param {
|
|
19
|
+
* @param {any} codec - Initialized LC3 codec instance
|
|
20
20
|
*/
|
|
21
21
|
constructor(codec) {
|
|
22
22
|
super();
|
|
@@ -25,17 +25,17 @@ class LC3Encoder extends EventEmitter {
|
|
|
25
25
|
throw AudioError.codecInitFailed({ reason: 'Codec must be initialized' });
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/** @private */
|
|
28
|
+
/** @type {any} @private */
|
|
29
29
|
this._codec = codec;
|
|
30
|
-
/** @private */
|
|
30
|
+
/** @type {number} @private */
|
|
31
31
|
this._frameSamples = codec.getFrameSamples();
|
|
32
32
|
/** @private */
|
|
33
33
|
this._sampleBuffer = new Int16Array(this._frameSamples * 2);
|
|
34
|
-
/** @private */
|
|
34
|
+
/** @type {number} @private */
|
|
35
35
|
this._bufferOffset = 0;
|
|
36
|
-
/** @private */
|
|
36
|
+
/** @type {number} @private */
|
|
37
37
|
this._framesEncoded = 0;
|
|
38
|
-
/** @private */
|
|
38
|
+
/** @type {number} @private */
|
|
39
39
|
this._errors = 0;
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -45,6 +45,7 @@ class LC3Encoder extends EventEmitter {
|
|
|
45
45
|
* @returns {Promise<Uint8Array[]>} Array of encoded LC3 frames
|
|
46
46
|
*/
|
|
47
47
|
async pushSamples(samples) {
|
|
48
|
+
/** @type {Uint8Array[]} */
|
|
48
49
|
const frames = [];
|
|
49
50
|
let offset = 0;
|
|
50
51
|
|
|
@@ -68,7 +69,7 @@ class LC3Encoder extends EventEmitter {
|
|
|
68
69
|
frames.push(encoded);
|
|
69
70
|
this._framesEncoded++;
|
|
70
71
|
this.emit('frame', { frame: encoded, index: this._framesEncoded });
|
|
71
|
-
} catch (error) {
|
|
72
|
+
} catch (/** @type {any} */ error) {
|
|
72
73
|
this._errors++;
|
|
73
74
|
this.emit('error', error);
|
|
74
75
|
}
|
|
@@ -84,6 +85,7 @@ class LC3Encoder extends EventEmitter {
|
|
|
84
85
|
* @returns {Promise<Uint8Array[]>} Array of encoded LC3 frames
|
|
85
86
|
*/
|
|
86
87
|
async flush() {
|
|
88
|
+
/** @type {Uint8Array[]} */
|
|
87
89
|
const frames = [];
|
|
88
90
|
|
|
89
91
|
if (this._bufferOffset > 0) {
|
|
@@ -96,7 +98,7 @@ class LC3Encoder extends EventEmitter {
|
|
|
96
98
|
frames.push(encoded);
|
|
97
99
|
this._framesEncoded++;
|
|
98
100
|
this.emit('frame', { frame: encoded, index: this._framesEncoded, padded: true });
|
|
99
|
-
} catch (error) {
|
|
101
|
+
} catch (/** @type {any} */ error) {
|
|
100
102
|
this._errors++;
|
|
101
103
|
this.emit('error', error);
|
|
102
104
|
}
|
|
@@ -117,7 +119,7 @@ class LC3Encoder extends EventEmitter {
|
|
|
117
119
|
|
|
118
120
|
/**
|
|
119
121
|
* Returns encoder statistics
|
|
120
|
-
* @returns {
|
|
122
|
+
* @returns {any}
|
|
121
123
|
*/
|
|
122
124
|
getStats() {
|
|
123
125
|
return {
|