@livekit/rtc-node 0.13.27 → 0.13.28
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/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/participant.cjs +3 -0
- package/dist/participant.cjs.map +1 -1
- package/dist/participant.d.cts +2 -1
- package/dist/participant.d.ts +2 -1
- package/dist/participant.d.ts.map +1 -1
- package/dist/participant.js +3 -0
- package/dist/participant.js.map +1 -1
- package/dist/room.cjs +42 -0
- package/dist/room.cjs.map +1 -1
- package/dist/room.d.cts +18 -1
- package/dist/room.d.ts +18 -1
- package/dist/room.d.ts.map +1 -1
- package/dist/room.js +42 -0
- package/dist/room.js.map +1 -1
- package/dist/track_publication.cjs +11 -0
- package/dist/track_publication.cjs.map +1 -1
- package/dist/track_publication.d.cts +9 -0
- package/dist/track_publication.d.ts +9 -0
- package/dist/track_publication.d.ts.map +1 -1
- package/dist/track_publication.js +11 -0
- package/dist/track_publication.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +5 -4
- package/src/index.ts +11 -2
- package/src/participant.ts +5 -0
- package/src/room.ts +64 -1
- package/src/tests/e2e.test.ts +157 -0
- package/src/track_publication.ts +12 -0
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "LiveKit RTC Node",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"author": "LiveKit",
|
|
6
|
-
"version": "0.13.
|
|
6
|
+
"version": "0.13.28",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"require": "dist/index.cjs",
|
|
9
9
|
"types": "dist/index.d.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@datastructures-js/deque": "1.0.8",
|
|
34
34
|
"@livekit/mutex": "^1.0.0",
|
|
35
35
|
"@livekit/typed-emitter": "^3.0.0",
|
|
36
|
-
"@livekit/rtc-ffi-bindings": "0.12.
|
|
36
|
+
"@livekit/rtc-ffi-bindings": "0.12.56",
|
|
37
37
|
"pino": "^9.0.0",
|
|
38
38
|
"pino-pretty": "^13.0.0"
|
|
39
39
|
},
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"prettier": "^3.0.3",
|
|
45
45
|
"tsup": "^8.3.5",
|
|
46
46
|
"typescript": "5.8.2",
|
|
47
|
-
"livekit-server-sdk": "2.15.
|
|
47
|
+
"livekit-server-sdk": "2.15.3"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">= 18"
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"prebuild": "node -p \"'export const SDK_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > src/version.ts",
|
|
54
54
|
"build": "pnpm prebuild && tsup --onSuccess \"tsc --declaration --emitDeclarationOnly\"",
|
|
55
55
|
"lint": "eslint -f unix \"src/**/*.ts\" --ignore-pattern \"src/proto/*\"",
|
|
56
|
-
"test": "vitest run src"
|
|
56
|
+
"test": "vitest run src",
|
|
57
|
+
"test:e2e": "node scripts/run-e2e.mjs"
|
|
57
58
|
}
|
|
58
59
|
}
|
package/src/index.ts
CHANGED
|
@@ -15,7 +15,11 @@ export type { E2EEOptions, KeyProviderOptions } from './e2ee.js';
|
|
|
15
15
|
export { dispose } from './ffi_client.js';
|
|
16
16
|
export { LocalParticipant, Participant, RemoteParticipant } from './participant.js';
|
|
17
17
|
export { EncryptionState, EncryptionType } from '@livekit/rtc-ffi-bindings';
|
|
18
|
-
export {
|
|
18
|
+
export {
|
|
19
|
+
DisconnectReason,
|
|
20
|
+
ParticipantKind,
|
|
21
|
+
ParticipantKindDetail,
|
|
22
|
+
} from '@livekit/rtc-ffi-bindings';
|
|
19
23
|
export {
|
|
20
24
|
ConnectionQuality,
|
|
21
25
|
ConnectionState,
|
|
@@ -25,7 +29,12 @@ export {
|
|
|
25
29
|
IceTransportType,
|
|
26
30
|
TrackPublishOptions,
|
|
27
31
|
} from '@livekit/rtc-ffi-bindings';
|
|
28
|
-
export {
|
|
32
|
+
export {
|
|
33
|
+
SimulateScenarioKind,
|
|
34
|
+
StreamState,
|
|
35
|
+
TrackKind,
|
|
36
|
+
TrackSource,
|
|
37
|
+
} from '@livekit/rtc-ffi-bindings';
|
|
29
38
|
export { VideoBufferType, VideoCodec, VideoRotation } from '@livekit/rtc-ffi-bindings';
|
|
30
39
|
export { ConnectError, Room, RoomEvent, type RoomOptions, type RtcConfiguration } from './room.js';
|
|
31
40
|
export { RpcError, type PerformRpcParams, type RpcInvocationData } from './rpc.js';
|
package/src/participant.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type OwnedParticipant,
|
|
8
8
|
type ParticipantInfo,
|
|
9
9
|
ParticipantKind,
|
|
10
|
+
type ParticipantKindDetail,
|
|
10
11
|
} from '@livekit/rtc-ffi-bindings';
|
|
11
12
|
import {
|
|
12
13
|
ChatMessage as ChatMessageModel,
|
|
@@ -128,6 +129,10 @@ export abstract class Participant {
|
|
|
128
129
|
return this.info.kind ?? ParticipantKind.STANDARD;
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
get kindDetails(): ParticipantKindDetail[] {
|
|
133
|
+
return this.info.kindDetails ?? [];
|
|
134
|
+
}
|
|
135
|
+
|
|
131
136
|
get disconnectReason(): DisconnectReason | undefined {
|
|
132
137
|
if (this.info.disconnectReason === DisconnectReason.UNKNOWN_REASON) {
|
|
133
138
|
return undefined;
|
package/src/room.ts
CHANGED
|
@@ -5,7 +5,11 @@ import { Mutex } from '@livekit/mutex';
|
|
|
5
5
|
import { EncryptionState, type EncryptionType } from '@livekit/rtc-ffi-bindings';
|
|
6
6
|
import type { FfiEvent } from '@livekit/rtc-ffi-bindings';
|
|
7
7
|
import { DisconnectReason, type OwnedParticipant } from '@livekit/rtc-ffi-bindings';
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
DataStream_Trailer,
|
|
10
|
+
DisconnectCallback,
|
|
11
|
+
TrackPublicationInfo,
|
|
12
|
+
} from '@livekit/rtc-ffi-bindings';
|
|
9
13
|
import {
|
|
10
14
|
type ConnectCallback,
|
|
11
15
|
ConnectRequest,
|
|
@@ -21,6 +25,9 @@ import {
|
|
|
21
25
|
type IceServer,
|
|
22
26
|
IceTransportType,
|
|
23
27
|
type RoomInfo,
|
|
28
|
+
type SimulateScenarioCallback,
|
|
29
|
+
type SimulateScenarioKind,
|
|
30
|
+
type SimulateScenarioResponse,
|
|
24
31
|
} from '@livekit/rtc-ffi-bindings';
|
|
25
32
|
import { TrackKind } from '@livekit/rtc-ffi-bindings';
|
|
26
33
|
import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
|
|
@@ -325,6 +332,36 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
|
|
|
325
332
|
this.removeAllListeners();
|
|
326
333
|
}
|
|
327
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Trigger a reconnection / chaos scenario for testing. Most useful in
|
|
337
|
+
* tests to deterministically force a Resume (signal-only reconnect that
|
|
338
|
+
* preserves the PeerConnection and existing publications) or a full
|
|
339
|
+
* reconnect (the SDK rebuilds the RtcSession and re-publishes existing
|
|
340
|
+
* local tracks; `RoomEvent.Reconnected` fires).
|
|
341
|
+
*/
|
|
342
|
+
async simulateScenario(scenario: SimulateScenarioKind): Promise<void> {
|
|
343
|
+
if (!this.isConnected || !this.ffiHandle) {
|
|
344
|
+
throw new Error('simulateScenario requires a connected room');
|
|
345
|
+
}
|
|
346
|
+
const res = FfiClient.instance.request<SimulateScenarioResponse>({
|
|
347
|
+
message: {
|
|
348
|
+
case: 'simulateScenario',
|
|
349
|
+
value: {
|
|
350
|
+
roomHandle: this.ffiHandle.handle,
|
|
351
|
+
scenario,
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
const cb = await FfiClient.instance.waitFor<SimulateScenarioCallback>(
|
|
356
|
+
(ev: FfiEvent) =>
|
|
357
|
+
ev.message.case === 'simulateScenario' && ev.message.value.asyncId === res.asyncId,
|
|
358
|
+
{ signal: this.disconnectController.signal },
|
|
359
|
+
);
|
|
360
|
+
if (cb.error) {
|
|
361
|
+
throw new Error(`simulateScenario failed: ${cb.error}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
328
365
|
private updateConnectionState(newState: ConnectionState) {
|
|
329
366
|
if (this._connectionState === newState) {
|
|
330
367
|
return;
|
|
@@ -481,6 +518,19 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
|
|
|
481
518
|
const publication = this.localParticipant.trackPublications.get(ev.value.publicationSid!);
|
|
482
519
|
this.localParticipant.trackPublications.delete(ev.value.publicationSid!);
|
|
483
520
|
this.emit(RoomEvent.LocalTrackUnpublished, publication!, this.localParticipant!);
|
|
521
|
+
} else if ((ev.case as string) == 'localTrackRepublished') {
|
|
522
|
+
const value = (ev as any).value;
|
|
523
|
+
const previousSid: string = value.previousSid!;
|
|
524
|
+
const newInfo: TrackPublicationInfo = value.info!;
|
|
525
|
+
const publication = this.localParticipant.trackPublications.get(previousSid);
|
|
526
|
+
if (publication) {
|
|
527
|
+
publication.updateInfo(newInfo);
|
|
528
|
+
this.localParticipant.trackPublications.delete(previousSid);
|
|
529
|
+
this.localParticipant.trackPublications.set(publication.sid!, publication);
|
|
530
|
+
this.emit(RoomEvent.LocalTrackRepublished, publication, previousSid, this.localParticipant);
|
|
531
|
+
} else {
|
|
532
|
+
log.warn(`RoomEvent.LocalTrackRepublished: previous publication not found: ${previousSid}`);
|
|
533
|
+
}
|
|
484
534
|
} else if (ev.case == 'localTrackSubscribed') {
|
|
485
535
|
const publication = this.localParticipant.trackPublications.get(ev.value.trackSid!);
|
|
486
536
|
if (publication) {
|
|
@@ -903,6 +953,18 @@ export type RoomCallbacks = {
|
|
|
903
953
|
publication: LocalTrackPublication,
|
|
904
954
|
participant: LocalParticipant,
|
|
905
955
|
) => void;
|
|
956
|
+
/**
|
|
957
|
+
* Fired when the SDK auto-republished a local track during a full
|
|
958
|
+
* reconnect. The publication object's identity is preserved (the same
|
|
959
|
+
* instance is updated in place with the new server-assigned SIDs);
|
|
960
|
+
* `previousSid` is provided for callers that key external state on the
|
|
961
|
+
* old SID and need to reconcile.
|
|
962
|
+
*/
|
|
963
|
+
localTrackRepublished: (
|
|
964
|
+
publication: LocalTrackPublication,
|
|
965
|
+
previousSid: string,
|
|
966
|
+
participant: LocalParticipant,
|
|
967
|
+
) => void;
|
|
906
968
|
localTrackSubscribed: (track: LocalTrack) => void;
|
|
907
969
|
trackPublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void;
|
|
908
970
|
trackUnpublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void;
|
|
@@ -960,6 +1022,7 @@ export enum RoomEvent {
|
|
|
960
1022
|
ParticipantDisconnected = 'participantDisconnected',
|
|
961
1023
|
LocalTrackPublished = 'localTrackPublished',
|
|
962
1024
|
LocalTrackUnpublished = 'localTrackUnpublished',
|
|
1025
|
+
LocalTrackRepublished = 'localTrackRepublished',
|
|
963
1026
|
LocalTrackSubscribed = 'localTrackSubscribed',
|
|
964
1027
|
TrackPublished = 'trackPublished',
|
|
965
1028
|
TrackUnpublished = 'trackUnpublished',
|
package/src/tests/e2e.test.ts
CHANGED
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
Room,
|
|
16
16
|
RoomEvent,
|
|
17
17
|
RpcError,
|
|
18
|
+
SimulateScenarioKind,
|
|
19
|
+
TrackKind,
|
|
18
20
|
TrackPublishOptions,
|
|
19
21
|
TrackSource,
|
|
20
22
|
dispose,
|
|
@@ -682,4 +684,159 @@ describeE2E('livekit-rtc e2e', () => {
|
|
|
682
684
|
},
|
|
683
685
|
testTimeoutMs,
|
|
684
686
|
);
|
|
687
|
+
|
|
688
|
+
// -- Reconnect scenarios --
|
|
689
|
+
//
|
|
690
|
+
// Both tests verify the user-visible behavior: after the scenario fires,
|
|
691
|
+
// the subscriber continues to receive the publisher's tone. The full
|
|
692
|
+
// reconnect test additionally asserts there is exactly one audio
|
|
693
|
+
// publication on each side (regression: duplicate-publish bug).
|
|
694
|
+
|
|
695
|
+
const runReconnectScenario = async (scenario: SimulateScenarioKind) => {
|
|
696
|
+
const { rooms } = await connectTestRooms(2);
|
|
697
|
+
const [subRoom, pubRoom] = rooms;
|
|
698
|
+
|
|
699
|
+
const pubRateHz = 48_000;
|
|
700
|
+
const source = new AudioSource(pubRateHz, 1);
|
|
701
|
+
const track = LocalAudioTrack.createAudioTrack('reconnect_tone', source);
|
|
702
|
+
const opts = new TrackPublishOptions();
|
|
703
|
+
opts.source = TrackSource.SOURCE_MICROPHONE;
|
|
704
|
+
await pubRoom!.localParticipant!.publishTrack(track, opts);
|
|
705
|
+
|
|
706
|
+
let tonePhase = 0;
|
|
707
|
+
const samplesPer10ms = Math.floor(pubRateHz / 100);
|
|
708
|
+
const amplitude = 0.8 * 32767;
|
|
709
|
+
const sineHz = 60;
|
|
710
|
+
let toneRunning = true;
|
|
711
|
+
const toneTask = (async () => {
|
|
712
|
+
while (toneRunning) {
|
|
713
|
+
const frame = AudioFrame.create(pubRateHz, 1, samplesPer10ms);
|
|
714
|
+
for (let s = 0; s < samplesPer10ms; s++) {
|
|
715
|
+
frame.data[s] = Math.round(
|
|
716
|
+
amplitude * Math.sin((2 * Math.PI * sineHz * tonePhase) / pubRateHz),
|
|
717
|
+
);
|
|
718
|
+
tonePhase++;
|
|
719
|
+
}
|
|
720
|
+
await source.captureFrame(frame);
|
|
721
|
+
}
|
|
722
|
+
})();
|
|
723
|
+
|
|
724
|
+
// Subscriber-side: re-attach an AudioStream every time TrackSubscribed
|
|
725
|
+
// fires (a full reconnect may issue TrackUnsubscribed → TrackSubscribed
|
|
726
|
+
// with a fresh remote track).
|
|
727
|
+
const sub = {
|
|
728
|
+
lastFrameAt: 0,
|
|
729
|
+
collectFromMs: Number.POSITIVE_INFINITY,
|
|
730
|
+
collected: [] as Int16Array[],
|
|
731
|
+
readers: [] as ReturnType<AudioStream['getReader']>[],
|
|
732
|
+
};
|
|
733
|
+
const attach = (remoteTrack: unknown) => {
|
|
734
|
+
const stream = new AudioStream(remoteTrack as any, {
|
|
735
|
+
sampleRate: pubRateHz,
|
|
736
|
+
numChannels: 1,
|
|
737
|
+
});
|
|
738
|
+
const reader = stream.getReader();
|
|
739
|
+
sub.readers.push(reader);
|
|
740
|
+
(async () => {
|
|
741
|
+
try {
|
|
742
|
+
while (true) {
|
|
743
|
+
const { done, value } = await reader.read();
|
|
744
|
+
if (done) break;
|
|
745
|
+
sub.lastFrameAt = Date.now();
|
|
746
|
+
if (sub.lastFrameAt >= sub.collectFromMs) {
|
|
747
|
+
sub.collected.push(channelSamples(value, 0));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
} catch {
|
|
751
|
+
// reader released
|
|
752
|
+
}
|
|
753
|
+
})();
|
|
754
|
+
};
|
|
755
|
+
subRoom!.on(RoomEvent.TrackSubscribed, (t) => attach(t));
|
|
756
|
+
|
|
757
|
+
try {
|
|
758
|
+
await waitFor(() => sub.lastFrameAt > 0 && Date.now() - sub.lastFrameAt < 500, {
|
|
759
|
+
timeoutMs: 10_000,
|
|
760
|
+
debugName: 'initial audio flow',
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
const simulateAt = Date.now();
|
|
764
|
+
await pubRoom!.simulateScenario(scenario);
|
|
765
|
+
|
|
766
|
+
// Wait for audio to actually flow again post-simulate: a frame
|
|
767
|
+
// received well after the simulate AND a fresh latest-frame timestamp.
|
|
768
|
+
await waitFor(
|
|
769
|
+
() => sub.lastFrameAt >= simulateAt + 500 && Date.now() - sub.lastFrameAt < 300,
|
|
770
|
+
{ timeoutMs: 30_000, debugName: 'audio re-established after simulate' },
|
|
771
|
+
);
|
|
772
|
+
// Drain post-recovery buffer/jitter, then collect a 2s window of
|
|
773
|
+
// steady-state samples for tone detection.
|
|
774
|
+
await delay(1_500);
|
|
775
|
+
sub.collected.length = 0;
|
|
776
|
+
sub.collectFromMs = Date.now();
|
|
777
|
+
await waitFor(() => sub.collected.reduce((a, s) => a + s.length, 0) >= pubRateHz * 2, {
|
|
778
|
+
timeoutMs: 15_000,
|
|
779
|
+
debugName: 'post-simulate audio sampling',
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
const totalLen = sub.collected.reduce((a, s) => a + s.length, 0);
|
|
783
|
+
const concat = new Int16Array(totalLen);
|
|
784
|
+
let off = 0;
|
|
785
|
+
for (const s of sub.collected) {
|
|
786
|
+
concat.set(s, off);
|
|
787
|
+
off += s.length;
|
|
788
|
+
}
|
|
789
|
+
const detected = estimateFreqHz(concat, pubRateHz);
|
|
790
|
+
// Wider tolerance than the clean-path sine test: post-reconnect
|
|
791
|
+
// audio has brief discontinuities, and the autocorrelation is
|
|
792
|
+
// integer-lag (next neighbors to 60Hz are exactly 80Hz/40Hz), so
|
|
793
|
+
// ±20Hz lands right on the failure boundary under CI load.
|
|
794
|
+
expect(Math.abs(detected - sineHz)).toBeLessThan(25);
|
|
795
|
+
|
|
796
|
+
return { rooms, subRoom: subRoom!, pubRoom: pubRoom! };
|
|
797
|
+
} finally {
|
|
798
|
+
toneRunning = false;
|
|
799
|
+
await toneTask;
|
|
800
|
+
for (const r of sub.readers) {
|
|
801
|
+
try {
|
|
802
|
+
r.releaseLock();
|
|
803
|
+
} catch {
|
|
804
|
+
// ignore
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
await track.close();
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
itRaw(
|
|
812
|
+
'resume keeps audio flowing on the subscriber side',
|
|
813
|
+
async () => {
|
|
814
|
+
const { rooms } = await runReconnectScenario(SimulateScenarioKind.SIMULATE_SIGNAL_RECONNECT);
|
|
815
|
+
await Promise.all(rooms.map((r) => r.disconnect()));
|
|
816
|
+
},
|
|
817
|
+
testTimeoutMs * 4,
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
itRaw(
|
|
821
|
+
'full reconnect keeps audio flowing and ends with one publication on the subscriber',
|
|
822
|
+
async () => {
|
|
823
|
+
const { rooms, subRoom, pubRoom } = await runReconnectScenario(
|
|
824
|
+
SimulateScenarioKind.SIMULATE_FULL_RECONNECT,
|
|
825
|
+
);
|
|
826
|
+
|
|
827
|
+
try {
|
|
828
|
+
// Regression: subscriber must see exactly ONE audio publication after
|
|
829
|
+
// recovery — not duplicates from the auto-republish path.
|
|
830
|
+
const subscriberAudioPubs = Array.from(
|
|
831
|
+
subRoom.remoteParticipants
|
|
832
|
+
.get(pubRoom.localParticipant!.identity)!
|
|
833
|
+
.trackPublications.values(),
|
|
834
|
+
).filter((p) => p.kind === TrackKind.KIND_AUDIO);
|
|
835
|
+
expect(subscriberAudioPubs.length).toBe(1);
|
|
836
|
+
} finally {
|
|
837
|
+
await Promise.all(rooms.map((r) => r.disconnect()));
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
testTimeoutMs * 4,
|
|
841
|
+
);
|
|
685
842
|
});
|
package/src/track_publication.ts
CHANGED
|
@@ -66,6 +66,18 @@ export abstract class TrackPublication {
|
|
|
66
66
|
get encryptionType(): EncryptionType | undefined {
|
|
67
67
|
return this.info?.encryptionType;
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Update the publication's info in place. Used by the SDK when the
|
|
72
|
+
* server re-issues IDs / metadata for an existing publication (e.g.
|
|
73
|
+
* after a full reconnect). Application code holding a cached
|
|
74
|
+
* publication reference continues to read fresh values via the
|
|
75
|
+
* unchanged object identity.
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
updateInfo(info: TrackPublicationInfo): void {
|
|
79
|
+
this.info = info;
|
|
80
|
+
}
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
export class LocalTrackPublication extends TrackPublication {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "0.13.
|
|
1
|
+
export const SDK_VERSION = "0.13.28";
|