livekit-client 1.5.0 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. package/dist/livekit-client.esm.mjs +2257 -5488
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/src/api/SignalClient.d.ts +3 -2
  6. package/dist/src/api/SignalClient.d.ts.map +1 -1
  7. package/dist/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  8. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  9. package/dist/src/connectionHelper/checks/Checker.d.ts +4 -4
  10. package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
  11. package/dist/src/index.d.ts +3 -2
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/logger.d.ts +3 -3
  14. package/dist/src/logger.d.ts.map +1 -1
  15. package/dist/src/options.d.ts +4 -1
  16. package/dist/src/options.d.ts.map +1 -1
  17. package/dist/src/proto/google/protobuf/timestamp.d.ts +4 -4
  18. package/dist/src/proto/google/protobuf/timestamp.d.ts.map +1 -1
  19. package/dist/src/proto/livekit_models.d.ts +4 -4
  20. package/dist/src/proto/livekit_models.d.ts.map +1 -1
  21. package/dist/src/proto/livekit_rtc.d.ts +4 -4
  22. package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
  23. package/dist/src/room/PCTransport.d.ts +7 -1
  24. package/dist/src/room/PCTransport.d.ts.map +1 -1
  25. package/dist/src/room/RTCEngine.d.ts +10 -4
  26. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  27. package/dist/src/room/Room.d.ts +21 -4
  28. package/dist/src/room/Room.d.ts.map +1 -1
  29. package/dist/src/room/events.d.ts +5 -0
  30. package/dist/src/room/events.d.ts.map +1 -1
  31. package/dist/src/room/participant/LocalParticipant.d.ts +3 -2
  32. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  33. package/dist/src/room/participant/Participant.d.ts +1 -1
  34. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  35. package/dist/src/room/track/LocalTrack.d.ts +1 -0
  36. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  37. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  38. package/dist/src/room/track/Track.d.ts +2 -1
  39. package/dist/src/room/track/Track.d.ts.map +1 -1
  40. package/dist/src/room/track/TrackPublication.d.ts +1 -1
  41. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  42. package/dist/src/room/track/options.d.ts +3 -3
  43. package/dist/src/room/track/options.d.ts.map +1 -1
  44. package/dist/src/room/track/types.d.ts +3 -3
  45. package/dist/src/room/track/types.d.ts.map +1 -1
  46. package/dist/src/room/types.d.ts +13 -0
  47. package/dist/src/room/types.d.ts.map +1 -0
  48. package/dist/src/room/utils.d.ts +44 -0
  49. package/dist/src/room/utils.d.ts.map +1 -1
  50. package/dist/ts4.2/src/api/SignalClient.d.ts +3 -2
  51. package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  52. package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +4 -4
  53. package/dist/ts4.2/src/index.d.ts +3 -2
  54. package/dist/ts4.2/src/logger.d.ts +3 -3
  55. package/dist/ts4.2/src/options.d.ts +4 -1
  56. package/dist/ts4.2/src/proto/google/protobuf/timestamp.d.ts +4 -4
  57. package/dist/ts4.2/src/proto/livekit_models.d.ts +4 -4
  58. package/dist/ts4.2/src/proto/livekit_rtc.d.ts +4 -4
  59. package/dist/ts4.2/src/room/PCTransport.d.ts +7 -1
  60. package/dist/ts4.2/src/room/RTCEngine.d.ts +10 -4
  61. package/dist/ts4.2/src/room/Room.d.ts +21 -4
  62. package/dist/ts4.2/src/room/events.d.ts +5 -0
  63. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +3 -2
  64. package/dist/ts4.2/src/room/participant/Participant.d.ts +1 -1
  65. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -0
  66. package/dist/ts4.2/src/room/track/Track.d.ts +2 -1
  67. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +1 -1
  68. package/dist/ts4.2/src/room/track/options.d.ts +3 -3
  69. package/dist/ts4.2/src/room/track/types.d.ts +3 -3
  70. package/dist/ts4.2/src/room/types.d.ts +13 -0
  71. package/dist/ts4.2/src/room/utils.d.ts +44 -0
  72. package/package.json +23 -23
  73. package/src/api/SignalClient.ts +40 -16
  74. package/src/connectionHelper/checks/turn.ts +1 -1
  75. package/src/connectionHelper/checks/websocket.ts +1 -1
  76. package/src/index.ts +5 -0
  77. package/src/options.ts +5 -1
  78. package/src/room/PCTransport.ts +11 -1
  79. package/src/room/RTCEngine.ts +111 -49
  80. package/src/room/Room.ts +234 -63
  81. package/src/room/events.ts +5 -0
  82. package/src/room/participant/LocalParticipant.ts +46 -22
  83. package/src/room/participant/RemoteParticipant.ts +5 -5
  84. package/src/room/participant/publishUtils.ts +1 -1
  85. package/src/room/track/LocalAudioTrack.ts +1 -1
  86. package/src/room/track/LocalTrack.ts +20 -1
  87. package/src/room/track/LocalVideoTrack.ts +1 -1
  88. package/src/room/track/RemoteVideoTrack.ts +4 -0
  89. package/src/room/track/Track.ts +22 -5
  90. package/src/room/types.ts +12 -0
  91. package/src/room/utils.ts +150 -12
@@ -44,9 +44,10 @@ export default class RTCEngine extends RTCEngine_base {
44
44
  private joinAttempts;
45
45
  /** specifies how often an initial join connection is allowed to retry */
46
46
  private maxJoinAttempts;
47
+ private closingLock;
47
48
  constructor(options: InternalRoomOptions);
48
49
  join(url: string, token: string, opts: SignalOptions, abortSignal?: AbortSignal): Promise<JoinResponse>;
49
- close(): void;
50
+ close(): Promise<void>;
50
51
  addTrack(req: AddTrackRequest): Promise<TrackInfo>;
51
52
  removeTrack(sender: RTCRtpSender): void;
52
53
  updateMuteStatus(trackSid: string, muted: boolean): void;
@@ -70,23 +71,28 @@ export default class RTCEngine extends RTCEngine_base {
70
71
  private resumeConnection;
71
72
  waitForPCConnected(): Promise<void>;
72
73
  sendDataPacket(packet: DataPacket, kind: DataPacket_Kind): Promise<void>;
74
+ /**
75
+ * @internal
76
+ */
77
+ ensureDataTransportConnected(kind: DataPacket_Kind, subscriber?: boolean): Promise<void>;
73
78
  private ensurePublisherConnected;
74
79
  /** @internal */
75
- negotiate(): void;
80
+ negotiate(): Promise<void>;
76
81
  dataChannelForKind(kind: DataPacket_Kind, sub?: boolean): RTCDataChannel | undefined;
77
82
  private clearPendingReconnect;
78
83
  private handleBrowserOnLine;
79
84
  private registerOnLineListener;
80
85
  private deregisterOnLineListener;
81
86
  }
82
- export declare type EngineEventCallbacks = {
83
- connected: () => void;
87
+ export type EngineEventCallbacks = {
88
+ connected: (joinResp: JoinResponse) => void;
84
89
  disconnected: (reason?: DisconnectReason) => void;
85
90
  resuming: () => void;
86
91
  resumed: () => void;
87
92
  restarting: () => void;
88
93
  restarted: (joinResp: JoinResponse) => void;
89
94
  signalResumed: () => void;
95
+ closing: () => void;
90
96
  mediaTrackAdded: (track: MediaStreamTrack, streams: MediaStream, receiver?: RTCRtpReceiver) => void;
91
97
  activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void;
92
98
  dataPacketReceived: (userPacket: UserPacket, kind: DataPacket_Kind) => void;
@@ -6,11 +6,12 @@ import type Participant from './participant/Participant';
6
6
  import type { ConnectionQuality } from './participant/Participant';
7
7
  import RemoteParticipant from './participant/RemoteParticipant';
8
8
  import RTCEngine from './RTCEngine';
9
- import type LocalTrackPublication from './track/LocalTrackPublication';
9
+ import LocalTrackPublication from './track/LocalTrackPublication';
10
10
  import type RemoteTrack from './track/RemoteTrack';
11
11
  import RemoteTrackPublication from './track/RemoteTrackPublication';
12
12
  import { Track } from './track/Track';
13
13
  import type { TrackPublication } from './track/TrackPublication';
14
+ import type { SimulationOptions } from './types';
14
15
  export declare enum ConnectionState {
15
16
  Disconnected = "disconnected",
16
17
  Connecting = "connecting",
@@ -59,6 +60,7 @@ declare class Room extends Room_base {
59
60
  private abortController?;
60
61
  /** future holding client initiated connection attempt */
61
62
  private connectFuture?;
63
+ private disconnectLock;
62
64
  /**
63
65
  * Creates a new Room, the primary construct for a LiveKit session.
64
66
  * @param options
@@ -102,7 +104,7 @@ declare class Room extends Room_base {
102
104
  /**
103
105
  * @internal for testing
104
106
  */
105
- simulateScenario(scenario: string): void;
107
+ simulateScenario(scenario: string): Promise<void>;
106
108
  private onBeforeUnload;
107
109
  /**
108
110
  * Browsers have different policies regarding audio playback. Most requiring
@@ -117,7 +119,15 @@ declare class Room extends Room_base {
117
119
  */
118
120
  get canPlaybackAudio(): boolean;
119
121
  /**
120
- * Switches all active device used in this room to the given device.
122
+ * Returns the active audio output device used in this room.
123
+ *
124
+ * Note: to get the active `audioinput` or `videoinput` use [[LocalTrack.getDeviceId()]]
125
+ *
126
+ * @return the previously successfully set audio output device ID or an empty string if the default device is used.
127
+ */
128
+ getActiveAudioOutputDevice(): string;
129
+ /**
130
+ * Switches all active devices used in this room to the given device.
121
131
  *
122
132
  * Note: setting AudioOutput is not supported on some browsers. See [setSinkId](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId#browser_compatibility)
123
133
  *
@@ -127,6 +137,7 @@ declare class Room extends Room_base {
127
137
  * @param deviceId
128
138
  */
129
139
  switchActiveDevice(kind: MediaDeviceKind, deviceId: string): Promise<void>;
140
+ private setupLocalParticipantEvents;
130
141
  private recreateEngine;
131
142
  private onTrackAdded;
132
143
  private handleRestarting;
@@ -163,10 +174,16 @@ declare class Room extends Room_base {
163
174
  private onLocalConnectionQualityChanged;
164
175
  private onMediaDevicesError;
165
176
  private onLocalParticipantPermissionsChanged;
177
+ /**
178
+ * Allows to populate a room with simulated participants.
179
+ * No actual connection to a server will be established, all state is
180
+ * @experimental
181
+ */
182
+ simulateParticipants(options: SimulationOptions): void;
166
183
  emit<E extends keyof RoomEventCallbacks>(event: E, ...args: Parameters<RoomEventCallbacks[E]>): boolean;
167
184
  }
168
185
  export default Room;
169
- export declare type RoomEventCallbacks = {
186
+ export type RoomEventCallbacks = {
170
187
  connected: () => void;
171
188
  reconnecting: () => void;
172
189
  reconnected: () => void;
@@ -367,6 +367,7 @@ export declare enum EngineEvent {
367
367
  Restarting = "restarting",
368
368
  Restarted = "restarted",
369
369
  SignalResumed = "signalResumed",
370
+ Closing = "closing",
370
371
  MediaTrackAdded = "mediaTrackAdded",
371
372
  ActiveSpeakersUpdate = "activeSpeakersUpdate",
372
373
  DataPacketReceived = "dataPacketReceived"
@@ -375,6 +376,10 @@ export declare enum TrackEvent {
375
376
  Message = "message",
376
377
  Muted = "muted",
377
378
  Unmuted = "unmuted",
379
+ /**
380
+ * Only fires on LocalTracks
381
+ */
382
+ Restarted = "restarted",
378
383
  Ended = "ended",
379
384
  Subscribed = "subscribed",
380
385
  Unsubscribed = "unsubscribed",
@@ -88,8 +88,9 @@ export default class LocalParticipant extends Participant {
88
88
  * publish additional codec to existing track
89
89
  */
90
90
  publishAdditionalCodecForTrack(track: LocalTrack | MediaStreamTrack, videoCodec: BackupVideoCodec, options?: TrackPublishOptions): Promise<void>;
91
- unpublishTrack(track: LocalTrack | MediaStreamTrack, stopOnUnpublish?: boolean): LocalTrackPublication | undefined;
92
- unpublishTracks(tracks: LocalTrack[] | MediaStreamTrack[]): LocalTrackPublication[];
91
+ unpublishTrack(track: LocalTrack | MediaStreamTrack, stopOnUnpublish?: boolean): Promise<LocalTrackPublication | undefined>;
92
+ unpublishTracks(tracks: LocalTrack[] | MediaStreamTrack[]): Promise<LocalTrackPublication[]>;
93
+ republishAllTracks(options?: TrackPublishOptions): Promise<void>;
93
94
  /**
94
95
  * Publish a new data payload to the room. Data will be forwarded to each
95
96
  * participant in the room if the destination argument is empty
@@ -68,7 +68,7 @@ export default class Participant extends Participant_base {
68
68
  setConnectionQuality(q: ProtoQuality): void;
69
69
  protected addTrackPublication(publication: TrackPublication): void;
70
70
  }
71
- export declare type ParticipantEventCallbacks = {
71
+ export type ParticipantEventCallbacks = {
72
72
  trackPublished: (publication: RemoteTrackPublication) => void;
73
73
  trackSubscribed: (track: RemoteTrack, publication: RemoteTrackPublication) => void;
74
74
  trackSubscriptionFailed: (trackSid: string) => void;
@@ -23,6 +23,7 @@ export default abstract class LocalTrack extends Track {
23
23
  private _isUpstreamPaused;
24
24
  get isUpstreamPaused(): boolean;
25
25
  get isUserProvided(): boolean;
26
+ waitForDimensions(timeout?: number): Promise<Track.Dimensions>;
26
27
  /**
27
28
  * @returns DeviceID of the device that is currently being used for this track
28
29
  */
@@ -100,10 +100,11 @@ export declare namespace Track {
100
100
  /** @internal */
101
101
  function streamStateFromProto(s: ProtoStreamState): StreamState;
102
102
  }
103
- export declare type TrackEventCallbacks = {
103
+ export type TrackEventCallbacks = {
104
104
  message: () => void;
105
105
  muted: (track?: any) => void;
106
106
  unmuted: (track?: any) => void;
107
+ restarted: (track?: any) => void;
107
108
  ended: (track?: any) => void;
108
109
  updateSettings: () => void;
109
110
  updateSubscription: () => void;
@@ -53,7 +53,7 @@ export declare namespace TrackPublication {
53
53
  NotAllowed = "not_allowed"
54
54
  }
55
55
  }
56
- export declare type PublicationEventCallbacks = {
56
+ export type PublicationEventCallbacks = {
57
57
  muted: () => void;
58
58
  unmuted: () => void;
59
59
  ended: (track?: Track) => void;
@@ -187,13 +187,13 @@ declare const backupCodecs: readonly [
187
187
  "vp8",
188
188
  "h264"
189
189
  ];
190
- export declare type VideoCodec = typeof codecs[number];
191
- export declare type BackupVideoCodec = typeof backupCodecs[number];
190
+ export type VideoCodec = typeof codecs[number];
191
+ export type BackupVideoCodec = typeof backupCodecs[number];
192
192
  export declare function isBackupCodec(codec: string): codec is BackupVideoCodec;
193
193
  /**
194
194
  * scalability modes for svc, only supprot l3t3 now.
195
195
  */
196
- export declare type ScalabilityMode = 'L3T3';
196
+ export type ScalabilityMode = 'L3T3';
197
197
  export declare namespace AudioPresets {
198
198
  const telephone: AudioPreset;
199
199
  const speech: AudioPreset;
@@ -2,9 +2,9 @@ import type LocalAudioTrack from './LocalAudioTrack';
2
2
  import type LocalVideoTrack from './LocalVideoTrack';
3
3
  import type RemoteAudioTrack from './RemoteAudioTrack';
4
4
  import type RemoteVideoTrack from './RemoteVideoTrack';
5
- export declare type AudioTrack = RemoteAudioTrack | LocalAudioTrack;
6
- export declare type VideoTrack = RemoteVideoTrack | LocalVideoTrack;
7
- export declare type AdaptiveStreamSettings = {
5
+ export type AudioTrack = RemoteAudioTrack | LocalAudioTrack;
6
+ export type VideoTrack = RemoteVideoTrack | LocalVideoTrack;
7
+ export type AdaptiveStreamSettings = {
8
8
  /**
9
9
  * Set a custom pixel density, defaults to 1
10
10
  * When streaming videos on a ultra high definition screen this setting
@@ -0,0 +1,13 @@
1
+ export type SimulationOptions = {
2
+ publish?: {
3
+ audio?: boolean;
4
+ video?: boolean;
5
+ };
6
+ participants?: {
7
+ count?: number;
8
+ aspectRatios?: Array<number>;
9
+ audio?: boolean;
10
+ video?: boolean;
11
+ };
12
+ };
13
+ //# sourceMappingURL=types.d.ts.map
@@ -1,4 +1,6 @@
1
1
  import { ClientInfo } from '../proto/livekit_models';
2
+ import type LocalAudioTrack from './track/LocalAudioTrack';
3
+ import type RemoteAudioTrack from './track/RemoteAudioTrack';
2
4
  export declare function unpackStreamId(packed: string): string[];
3
5
  export declare function sleep(duration: number): Promise<void>;
4
6
  /** @internal */
@@ -24,6 +26,7 @@ export interface ObservableMediaElement extends HTMLMediaElement {
24
26
  }
25
27
  export declare function getClientInfo(): ClientInfo;
26
28
  export declare function getEmptyVideoStreamTrack(): MediaStreamTrack;
29
+ export declare function createDummyVideoStreamTrack(width?: number, height?: number, enabled?: boolean, paintContent?: boolean): MediaStreamTrack;
27
30
  export declare function getEmptyAudioStreamTrack(): MediaStreamTrack;
28
31
  export declare class Future<T> {
29
32
  promise: Promise<T>;
@@ -32,4 +35,45 @@ export declare class Future<T> {
32
35
  onFinally?: () => void;
33
36
  constructor(futureBase?: (resolve: (arg: T) => void, reject: (e: any) => void) => void, onFinally?: () => void);
34
37
  }
38
+ export type AudioAnalyserOptions = {
39
+ /**
40
+ * If set to true, the analyser will use a cloned version of the underlying mediastreamtrack, which won't be impacted by muting the track.
41
+ * Useful for local tracks when implementing things like "seems like you're muted, but trying to speak".
42
+ * Defaults to false
43
+ */
44
+ cloneTrack?: boolean;
45
+ /**
46
+ * see https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/fftSize
47
+ */
48
+ fftSize?: number;
49
+ /**
50
+ * see https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/smoothingTimeConstant
51
+ */
52
+ smoothingTimeConstant?: number;
53
+ /**
54
+ * see https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/minDecibels
55
+ */
56
+ minDecibels?: number;
57
+ /**
58
+ * see https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/maxDecibels
59
+ */
60
+ maxDecibels?: number;
61
+ };
62
+ /**
63
+ * Creates and returns an analyser web audio node that is attached to the provided track.
64
+ * Additionally returns a convenience method `calculateVolume` to perform instant volume readings on that track.
65
+ * Call the returned `cleanup` function to close the audioContext that has been created for the instance of this helper
66
+ */
67
+ export declare function createAudioAnalyser(track: LocalAudioTrack | RemoteAudioTrack, options?: AudioAnalyserOptions): {
68
+ calculateVolume: () => number;
69
+ analyser: AnalyserNode;
70
+ cleanup: () => void;
71
+ };
72
+ export declare class Mutex {
73
+ private _locking;
74
+ private _locks;
75
+ constructor();
76
+ isLocked(): boolean;
77
+ lock(): Promise<() => void>;
78
+ }
35
79
  //# sourceMappingURL=utils.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livekit-client",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "description": "JavaScript/TypeScript client SDK for LiveKit",
5
5
  "main": "./dist/livekit-client.umd.js",
6
6
  "unpkg": "./dist/livekit-client.umd.js",
@@ -26,7 +26,7 @@
26
26
  "author": "David Zhao <david@davidzhao.com>",
27
27
  "license": "Apache-2.0",
28
28
  "scripts": {
29
- "build": "rollup --config && yarn downlevel-dts",
29
+ "build": "rollup --config --bundleConfigAsCjs && yarn downlevel-dts",
30
30
  "build:watch": "rollup --watch --config rollup.config.js",
31
31
  "build-docs": "typedoc",
32
32
  "proto": "protoc --plugin=node_modules/ts-proto/protoc-gen-ts_proto --ts_proto_opt=esModuleInterop=true --ts_proto_out=./src/proto --ts_proto_opt=outputClientImpl=false,useOptionals=messages,oneof=unions -I./protocol ./protocol/livekit_rtc.proto ./protocol/livekit_models.proto",
@@ -51,40 +51,40 @@
51
51
  "webrtc-adapter": "^8.1.1"
52
52
  },
53
53
  "devDependencies": {
54
- "@babel/core": "7.19.3",
55
- "@babel/preset-env": "7.19.3",
56
- "@changesets/changelog-github": "0.4.7",
57
- "@changesets/cli": "2.25.0",
58
- "@rollup/plugin-babel": "5.3.1",
59
- "@rollup/plugin-commonjs": "22.0.2",
60
- "@rollup/plugin-json": "4.1.0",
61
- "@rollup/plugin-node-resolve": "14.1.0",
62
- "@types/jest": "29.1.1",
54
+ "@babel/core": "7.20.7",
55
+ "@babel/preset-env": "7.20.2",
56
+ "@changesets/changelog-github": "0.4.8",
57
+ "@changesets/cli": "2.26.0",
58
+ "@rollup/plugin-babel": "6.0.3",
59
+ "@rollup/plugin-commonjs": "24.0.0",
60
+ "@rollup/plugin-json": "6.0.0",
61
+ "@rollup/plugin-node-resolve": "15.0.1",
62
+ "@types/jest": "29.2.5",
63
63
  "@types/sdp-transform": "2.4.5",
64
64
  "@types/ua-parser-js": "0.7.36",
65
- "@types/ws": "8.5.3",
66
- "@typescript-eslint/eslint-plugin": "5.38.1",
67
- "@typescript-eslint/parser": "5.38.1",
65
+ "@types/ws": "8.5.4",
66
+ "@typescript-eslint/eslint-plugin": "5.47.1",
67
+ "@typescript-eslint/parser": "5.47.1",
68
68
  "downlevel-dts": "^0.11.0",
69
- "eslint": "8.24.0",
69
+ "eslint": "8.31.0",
70
70
  "eslint-config-airbnb-typescript": "17.0.0",
71
71
  "eslint-config-prettier": "8.5.0",
72
72
  "eslint-plugin-import": "2.26.0",
73
73
  "gh-pages": "4.0.0",
74
- "jest": "29.1.2",
75
- "prettier": "2.7.1",
76
- "rollup": "2.79.1",
74
+ "jest": "29.3.1",
75
+ "prettier": "2.8.1",
76
+ "rollup": "3.9.1",
77
77
  "rollup-plugin-delete": "^2.0.0",
78
78
  "rollup-plugin-filesize": "9.1.2",
79
79
  "rollup-plugin-re": "1.0.7",
80
80
  "rollup-plugin-terser": "7.0.2",
81
- "rollup-plugin-typescript2": "0.34.0",
81
+ "rollup-plugin-typescript2": "0.34.1",
82
82
  "ts-jest": "29.0.3",
83
- "ts-proto": "1.126.1",
84
- "typedoc": "0.23.15",
83
+ "ts-proto": "1.137.0",
84
+ "typedoc": "0.23.23",
85
85
  "typedoc-plugin-no-inherit": "1.4.0",
86
- "typescript": "4.8.4",
87
- "vite": "3.1.8"
86
+ "typescript": "4.9.4",
87
+ "vite": "4.0.3"
88
88
  },
89
89
  "browserslist": [
90
90
  "safari >= 11",
@@ -30,7 +30,7 @@ import {
30
30
  UpdateTrackSettings,
31
31
  } from '../proto/livekit_rtc';
32
32
  import { ConnectionError, ConnectionErrorReason } from '../room/errors';
33
- import { getClientInfo, sleep } from '../room/utils';
33
+ import { getClientInfo, Mutex, sleep } from '../room/utils';
34
34
 
35
35
  // internal options
36
36
  interface ConnectOpts {
@@ -139,12 +139,15 @@ export class SignalClient {
139
139
 
140
140
  private pingInterval: ReturnType<typeof setInterval> | undefined;
141
141
 
142
+ private closingLock: Mutex;
143
+
142
144
  constructor(useJSON: boolean = false) {
143
145
  this.isConnected = false;
144
146
  this.isReconnecting = false;
145
147
  this.useJSON = useJSON;
146
148
  this.requestQueue = new Queue();
147
149
  this.queuedRequests = [];
150
+ this.closingLock = new Mutex();
148
151
  }
149
152
 
150
153
  async join(
@@ -190,9 +193,9 @@ export class SignalClient {
190
193
  const clientInfo = getClientInfo();
191
194
  const params = createConnectionParams(token, clientInfo, opts);
192
195
 
193
- return new Promise<JoinResponse | void>((resolve, reject) => {
194
- const abortHandler = () => {
195
- this.close();
196
+ return new Promise<JoinResponse | void>(async (resolve, reject) => {
197
+ const abortHandler = async () => {
198
+ await this.close();
196
199
  reject(new ConnectionError('room connection has been cancelled'));
197
200
  };
198
201
 
@@ -202,7 +205,7 @@ export class SignalClient {
202
205
  abortSignal?.addEventListener('abort', abortHandler);
203
206
  log.debug(`connecting to ${url + params}`);
204
207
  if (this.ws) {
205
- this.close();
208
+ await this.close();
206
209
  }
207
210
  this.ws = new WebSocket(url + params);
208
211
  this.ws.binaryType = 'arraybuffer';
@@ -277,7 +280,11 @@ export class SignalClient {
277
280
  }
278
281
  resolve(resp.message.join);
279
282
  } else {
280
- reject(new ConnectionError('did not receive join response'));
283
+ reject(
284
+ new ConnectionError(
285
+ `did not receive join response, got ${resp.message?.$case} instead`,
286
+ ),
287
+ );
281
288
  }
282
289
  return;
283
290
  }
@@ -301,16 +308,33 @@ export class SignalClient {
301
308
  });
302
309
  }
303
310
 
304
- close() {
305
- this.isConnected = false;
306
- if (this.ws) {
307
- this.ws.onclose = null;
308
- this.ws.onmessage = null;
309
- this.ws.onopen = null;
310
- this.ws.close();
311
+ async close() {
312
+ const unlock = await this.closingLock.lock();
313
+ try {
314
+ this.isConnected = false;
315
+ if (this.ws) {
316
+ this.ws.onclose = null;
317
+ this.ws.onmessage = null;
318
+ this.ws.onopen = null;
319
+
320
+ // calling `ws.close()` only starts the closing handshake (CLOSING state), prefer to wait until state is actually CLOSED
321
+ const closePromise = new Promise((resolve) => {
322
+ if (this.ws) {
323
+ this.ws.onclose = resolve;
324
+ } else {
325
+ resolve(true);
326
+ }
327
+ });
328
+
329
+ this.ws.close();
330
+ // 250ms grace period for ws to close gracefully
331
+ await Promise.race([closePromise, sleep(250)]);
332
+ }
333
+ this.ws = undefined;
334
+ this.clearPingInterval();
335
+ } finally {
336
+ unlock();
311
337
  }
312
- this.ws = undefined;
313
- this.clearPingInterval();
314
338
  }
315
339
 
316
340
  // initial offer after joining
@@ -441,7 +465,7 @@ export class SignalClient {
441
465
  if (this.signalLatency) {
442
466
  await sleep(this.signalLatency);
443
467
  }
444
- if (!this.ws || this.ws.readyState < this.ws.OPEN) {
468
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN) {
445
469
  log.error(`cannot send signal request before connected, type: ${message?.$case}`);
446
470
  return;
447
471
  }
@@ -37,7 +37,7 @@ export class TURNCheck extends Checker {
37
37
  } else if (hasTURN && !hasTLS) {
38
38
  this.appendWarning('TURN is configured server side, but TURN/TLS is unavailable.');
39
39
  }
40
- signalClient.close();
40
+ await signalClient.close();
41
41
  if (this.connectOptions?.rtcConfig?.iceServers || hasTURN) {
42
42
  await this.room!.connect(this.url, this.token, {
43
43
  rtcConfig: {
@@ -17,6 +17,6 @@ export class WebSocketCheck extends Checker {
17
17
  maxRetries: 0,
18
18
  });
19
19
  this.appendMessage(`Connected to server, version ${joinRes.serverVersion}.`);
20
- signalClient.close();
20
+ await signalClient.close();
21
21
  }
22
22
  }
package/src/index.ts CHANGED
@@ -23,8 +23,11 @@ import {
23
23
  supportsAdaptiveStream,
24
24
  supportsAV1,
25
25
  supportsDynacast,
26
+ createAudioAnalyser,
26
27
  } from './room/utils';
27
28
 
29
+ import type { AudioAnalyserOptions } from './room/utils';
30
+
28
31
  export * from './options';
29
32
  export * from './room/errors';
30
33
  export * from './room/events';
@@ -43,6 +46,8 @@ export {
43
46
  supportsAdaptiveStream,
44
47
  supportsDynacast,
45
48
  supportsAV1,
49
+ createAudioAnalyser,
50
+ AudioAnalyserOptions,
46
51
  LogLevel,
47
52
  Room,
48
53
  ConnectionState,
package/src/options.ts CHANGED
@@ -7,6 +7,10 @@ import type {
7
7
  } from './room/track/options';
8
8
  import type { AdaptiveStreamSettings } from './room/track/types';
9
9
 
10
+ export interface WebAudioSettings {
11
+ audioContext: AudioContext;
12
+ }
13
+
10
14
  /**
11
15
  * @internal
12
16
  */
@@ -72,7 +76,7 @@ export interface InternalRoomOptions {
72
76
  * experimental flag, mix all audio tracks in web audio
73
77
  */
74
78
 
75
- expWebAudioMix: boolean;
79
+ expWebAudioMix: boolean | WebAudioSettings;
76
80
  }
77
81
 
78
82
  /**
@@ -1,3 +1,4 @@
1
+ import EventEmitter from 'events';
1
2
  import { MediaDescription, parse, write } from 'sdp-transform';
2
3
  import { debounce } from 'ts-debounce';
3
4
  import log from '../logger';
@@ -10,8 +11,13 @@ interface TrackBitrateInfo {
10
11
  maxbr: number;
11
12
  }
12
13
 
14
+ export const PCEvents = {
15
+ NegotiationStarted: 'negotiationStarted',
16
+ NegotiationComplete: 'negotiationComplete',
17
+ } as const;
18
+
13
19
  /** @internal */
14
- export default class PCTransport {
20
+ export default class PCTransport extends EventEmitter {
15
21
  pc: RTCPeerConnection;
16
22
 
17
23
  pendingCandidates: RTCIceCandidateInit[] = [];
@@ -27,6 +33,7 @@ export default class PCTransport {
27
33
  onOffer?: (offer: RTCSessionDescriptionInit) => void;
28
34
 
29
35
  constructor(config?: RTCConfiguration) {
36
+ super();
30
37
  this.pc = new RTCPeerConnection(config);
31
38
  }
32
39
 
@@ -56,11 +63,14 @@ export default class PCTransport {
56
63
  if (this.renegotiate) {
57
64
  this.renegotiate = false;
58
65
  this.createAndSendOffer();
66
+ } else if (sd.type === 'answer') {
67
+ this.emit(PCEvents.NegotiationComplete);
59
68
  }
60
69
  }
61
70
 
62
71
  // debounced negotiate interface
63
72
  negotiate = debounce((onError?: (e: Error) => void) => {
73
+ this.emit(PCEvents.NegotiationStarted);
64
74
  try {
65
75
  this.createAndSendOffer();
66
76
  } catch (e) {