livekit-client 0.18.5 → 1.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.
- package/README.md +1 -1
- package/dist/api/SignalClient.d.ts +2 -2
- package/dist/api/SignalClient.d.ts.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/livekit-client.esm.mjs +257 -254
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/options.d.ts +1 -68
- package/dist/options.d.ts.map +1 -1
- package/dist/room/DeviceManager.d.ts.map +1 -1
- package/dist/room/RTCEngine.d.ts +3 -2
- package/dist/room/RTCEngine.d.ts.map +1 -1
- package/dist/room/Room.d.ts +11 -7
- package/dist/room/Room.d.ts.map +1 -1
- package/dist/room/events.d.ts +6 -12
- package/dist/room/events.d.ts.map +1 -1
- package/dist/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/room/participant/Participant.d.ts +0 -4
- package/dist/room/participant/Participant.d.ts.map +1 -1
- package/dist/room/participant/RemoteParticipant.d.ts +3 -2
- package/dist/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/room/track/RemoteVideoTrack.d.ts +24 -1
- package/dist/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/room/track/Track.d.ts +2 -1
- package/dist/room/track/Track.d.ts.map +1 -1
- package/dist/room/track/options.d.ts +0 -30
- package/dist/room/track/options.d.ts.map +1 -1
- package/dist/room/track/types.d.ts +5 -0
- package/dist/room/track/types.d.ts.map +1 -1
- package/dist/test/MockMediaStreamTrack.d.ts +26 -0
- package/dist/test/MockMediaStreamTrack.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/api/SignalClient.ts +32 -7
- package/src/index.ts +4 -3
- package/src/options.ts +0 -82
- package/src/room/DeviceManager.ts +4 -1
- package/src/room/RTCEngine.ts +16 -7
- package/src/room/Room.ts +66 -42
- package/src/room/events.ts +7 -14
- package/src/room/participant/LocalParticipant.ts +4 -0
- package/src/room/participant/Participant.ts +0 -5
- package/src/room/participant/RemoteParticipant.ts +16 -5
- package/src/room/participant/publishUtils.test.ts +2 -2
- package/src/room/track/LocalVideoTrack.ts +1 -1
- package/src/room/track/RemoteVideoTrack.test.ts +149 -0
- package/src/room/track/RemoteVideoTrack.ts +118 -39
- package/src/room/track/Track.ts +18 -2
- package/src/room/track/create.ts +1 -1
- package/src/room/track/options.ts +1 -31
- package/src/room/track/types.ts +5 -0
- package/src/room/track/utils.test.ts +6 -6
- package/src/test/MockMediaStreamTrack.ts +83 -0
- package/src/version.ts +1 -1
- package/dist/connect.d.ts +0 -24
- package/dist/connect.d.ts.map +0 -1
- package/src/connect.ts +0 -98
package/src/api/SignalClient.ts
CHANGED
@@ -123,15 +123,25 @@ export class SignalClient {
|
|
123
123
|
this.requestQueue = new Queue();
|
124
124
|
}
|
125
125
|
|
126
|
-
async join(
|
126
|
+
async join(
|
127
|
+
url: string,
|
128
|
+
token: string,
|
129
|
+
opts?: SignalOptions,
|
130
|
+
abortSignal?: AbortSignal,
|
131
|
+
): Promise<JoinResponse> {
|
127
132
|
// during a full reconnect, we'd want to start the sequence even if currently
|
128
133
|
// connected
|
129
134
|
this.isConnected = false;
|
130
|
-
const res = await this.connect(
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
+
const res = await this.connect(
|
136
|
+
url,
|
137
|
+
token,
|
138
|
+
{
|
139
|
+
autoSubscribe: opts?.autoSubscribe,
|
140
|
+
publishOnly: opts?.publishOnly,
|
141
|
+
adaptiveStream: opts?.adaptiveStream,
|
142
|
+
},
|
143
|
+
abortSignal,
|
144
|
+
);
|
135
145
|
return res as JoinResponse;
|
136
146
|
}
|
137
147
|
|
@@ -142,7 +152,12 @@ export class SignalClient {
|
|
142
152
|
});
|
143
153
|
}
|
144
154
|
|
145
|
-
connect(
|
155
|
+
connect(
|
156
|
+
url: string,
|
157
|
+
token: string,
|
158
|
+
opts: ConnectOpts,
|
159
|
+
abortSignal?: AbortSignal,
|
160
|
+
): Promise<JoinResponse | void> {
|
146
161
|
if (url.startsWith('http')) {
|
147
162
|
url = url.replace('http', 'ws');
|
148
163
|
}
|
@@ -154,6 +169,15 @@ export class SignalClient {
|
|
154
169
|
const params = createConnectionParams(token, clientInfo, opts);
|
155
170
|
|
156
171
|
return new Promise<JoinResponse | void>((resolve, reject) => {
|
172
|
+
const abortHandler = () => {
|
173
|
+
ws.close();
|
174
|
+
this.close();
|
175
|
+
reject(new ConnectionError('room connection has been cancelled'));
|
176
|
+
};
|
177
|
+
if (abortSignal?.aborted) {
|
178
|
+
abortHandler();
|
179
|
+
}
|
180
|
+
abortSignal?.addEventListener('abort', abortHandler);
|
157
181
|
log.debug(`connecting to ${url + params}`);
|
158
182
|
this.ws = undefined;
|
159
183
|
const ws = new WebSocket(url + params);
|
@@ -204,6 +228,7 @@ export class SignalClient {
|
|
204
228
|
// handle join message only
|
205
229
|
if (msg.join) {
|
206
230
|
this.isConnected = true;
|
231
|
+
abortSignal?.removeEventListener('abort', abortHandler);
|
207
232
|
resolve(msg.join);
|
208
233
|
} else {
|
209
234
|
reject(new ConnectionError('did not receive join response'));
|
package/src/index.ts
CHANGED
@@ -4,7 +4,7 @@ import LocalParticipant from './room/participant/LocalParticipant';
|
|
4
4
|
import Participant, { ConnectionQuality } from './room/participant/Participant';
|
5
5
|
import { ParticipantTrackPermission } from './room/participant/ParticipantTrackPermission';
|
6
6
|
import RemoteParticipant from './room/participant/RemoteParticipant';
|
7
|
-
import Room, { RoomState } from './room/Room';
|
7
|
+
import Room, { ConnectionState, RoomState } from './room/Room';
|
8
8
|
import LocalAudioTrack from './room/track/LocalAudioTrack';
|
9
9
|
import LocalTrack from './room/track/LocalTrack';
|
10
10
|
import LocalTrackPublication from './room/track/LocalTrackPublication';
|
@@ -12,10 +12,9 @@ import LocalVideoTrack from './room/track/LocalVideoTrack';
|
|
12
12
|
import RemoteAudioTrack from './room/track/RemoteAudioTrack';
|
13
13
|
import RemoteTrack from './room/track/RemoteTrack';
|
14
14
|
import RemoteTrackPublication from './room/track/RemoteTrackPublication';
|
15
|
-
import RemoteVideoTrack from './room/track/RemoteVideoTrack';
|
15
|
+
import RemoteVideoTrack, { ElementInfo } from './room/track/RemoteVideoTrack';
|
16
16
|
import { TrackPublication } from './room/track/TrackPublication';
|
17
17
|
|
18
|
-
export * from './connect';
|
19
18
|
export * from './options';
|
20
19
|
export * from './room/errors';
|
21
20
|
export * from './room/events';
|
@@ -29,6 +28,7 @@ export {
|
|
29
28
|
setLogExtension,
|
30
29
|
LogLevel,
|
31
30
|
Room,
|
31
|
+
ConnectionState,
|
32
32
|
RoomState,
|
33
33
|
DataPacket_Kind,
|
34
34
|
Participant,
|
@@ -46,4 +46,5 @@ export {
|
|
46
46
|
TrackPublication,
|
47
47
|
VideoQuality,
|
48
48
|
ConnectionQuality,
|
49
|
+
ElementInfo,
|
49
50
|
};
|
package/src/options.ts
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
import { LogLevel } from './logger';
|
2
1
|
import {
|
3
2
|
AudioCaptureOptions,
|
4
|
-
CreateLocalTracksOptions,
|
5
3
|
TrackPublishDefaults,
|
6
4
|
VideoCaptureOptions,
|
7
5
|
} from './room/track/options';
|
@@ -74,83 +72,3 @@ export interface RoomConnectOptions {
|
|
74
72
|
*/
|
75
73
|
publishOnly?: string;
|
76
74
|
}
|
77
|
-
|
78
|
-
/**
|
79
|
-
* @deprecated use new Room([[RoomOptions]]) and room.connect([[RoomConnectOptions]]) instead
|
80
|
-
*
|
81
|
-
* if video or audio tracks are created as part of [[connect]], it'll automatically
|
82
|
-
* publish those tracks to the room.
|
83
|
-
*/
|
84
|
-
export interface ConnectOptions extends CreateLocalTracksOptions {
|
85
|
-
/** autosubscribe to room tracks upon connect, defaults to true */
|
86
|
-
autoSubscribe?: boolean;
|
87
|
-
|
88
|
-
/**
|
89
|
-
* publish only mode
|
90
|
-
*/
|
91
|
-
publishOnly?: string;
|
92
|
-
|
93
|
-
/**
|
94
|
-
* see [[RoomOptions.adaptiveStream]]
|
95
|
-
*/
|
96
|
-
adaptiveStream?: AdaptiveStreamSettings | boolean;
|
97
|
-
|
98
|
-
/**
|
99
|
-
* alias for adaptiveStream
|
100
|
-
* @deprecated
|
101
|
-
*/
|
102
|
-
autoManageVideo?: boolean;
|
103
|
-
|
104
|
-
/**
|
105
|
-
* see [[RoomOptions.dynacast]]
|
106
|
-
*/
|
107
|
-
dynacast?: boolean;
|
108
|
-
|
109
|
-
/** configures LiveKit internal log level */
|
110
|
-
logLevel?: LogLevel;
|
111
|
-
|
112
|
-
/**
|
113
|
-
* set ICE servers. When deployed correctly, LiveKit automatically uses the built-in TURN servers
|
114
|
-
*/
|
115
|
-
iceServers?: RTCIceServer[];
|
116
|
-
|
117
|
-
/**
|
118
|
-
* use to override any RTCConfiguration options.
|
119
|
-
*/
|
120
|
-
rtcConfig?: RTCConfiguration;
|
121
|
-
|
122
|
-
/**
|
123
|
-
* capture and publish audio track on connect, defaults to false
|
124
|
-
*
|
125
|
-
* If this option is used, you will not be notified if user denies capture permission.
|
126
|
-
*/
|
127
|
-
audio?: boolean;
|
128
|
-
|
129
|
-
/**
|
130
|
-
* capture and publish video track on connect, defaults to false
|
131
|
-
*
|
132
|
-
* If this option is used, you will not be notified if user denies capture permission.
|
133
|
-
*/
|
134
|
-
video?: boolean;
|
135
|
-
|
136
|
-
/**
|
137
|
-
* default options to use when capturing user's audio
|
138
|
-
*/
|
139
|
-
audioCaptureDefaults?: AudioCaptureOptions;
|
140
|
-
|
141
|
-
/**
|
142
|
-
* default options to use when capturing user's video
|
143
|
-
*/
|
144
|
-
videoCaptureDefaults?: VideoCaptureOptions;
|
145
|
-
|
146
|
-
/**
|
147
|
-
* default options to use when publishing tracks
|
148
|
-
*/
|
149
|
-
publishDefaults?: TrackPublishDefaults;
|
150
|
-
|
151
|
-
/**
|
152
|
-
* should local tracks be stopped when they are unpublished. defaults to true
|
153
|
-
* set this to false if you would prefer to clean up unpublished local tracks manually.
|
154
|
-
*/
|
155
|
-
stopLocalTrackOnUnpublish?: boolean;
|
156
|
-
}
|
@@ -32,8 +32,11 @@ export default class DeviceManager {
|
|
32
32
|
video: kind !== 'audioinput' && kind !== 'audiooutput',
|
33
33
|
audio: kind !== 'videoinput',
|
34
34
|
};
|
35
|
-
await navigator.mediaDevices.getUserMedia(permissionsToAcquire);
|
35
|
+
const stream = await navigator.mediaDevices.getUserMedia(permissionsToAcquire);
|
36
36
|
devices = await navigator.mediaDevices.enumerateDevices();
|
37
|
+
stream.getTracks().forEach((track) => {
|
38
|
+
track.stop();
|
39
|
+
});
|
37
40
|
}
|
38
41
|
}
|
39
42
|
if (kind) {
|
package/src/room/RTCEngine.ts
CHANGED
@@ -48,6 +48,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
48
48
|
|
49
49
|
rtcConfig: RTCConfiguration = {};
|
50
50
|
|
51
|
+
get isClosed() {
|
52
|
+
return this._isClosed;
|
53
|
+
}
|
54
|
+
|
51
55
|
private lossyDC?: RTCDataChannel;
|
52
56
|
|
53
57
|
// @ts-ignore noUnusedLocals
|
@@ -64,7 +68,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
64
68
|
|
65
69
|
private pcState: PCState = PCState.New;
|
66
70
|
|
67
|
-
private
|
71
|
+
private _isClosed: boolean = true;
|
68
72
|
|
69
73
|
private pendingTrackResolvers: { [key: string]: (info: TrackInfo) => void } = {};
|
70
74
|
|
@@ -94,13 +98,18 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
94
98
|
this.client = new SignalClient();
|
95
99
|
}
|
96
100
|
|
97
|
-
async join(
|
101
|
+
async join(
|
102
|
+
url: string,
|
103
|
+
token: string,
|
104
|
+
opts?: SignalOptions,
|
105
|
+
abortSignal?: AbortSignal,
|
106
|
+
): Promise<JoinResponse> {
|
98
107
|
this.url = url;
|
99
108
|
this.token = token;
|
100
109
|
this.signalOpts = opts;
|
101
110
|
|
102
|
-
const joinResponse = await this.client.join(url, token, opts);
|
103
|
-
this.
|
111
|
+
const joinResponse = await this.client.join(url, token, opts, abortSignal);
|
112
|
+
this._isClosed = false;
|
104
113
|
|
105
114
|
this.subscriberPrimary = joinResponse.subscriberPrimary;
|
106
115
|
if (!this.publisher) {
|
@@ -117,7 +126,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
117
126
|
}
|
118
127
|
|
119
128
|
close() {
|
120
|
-
this.
|
129
|
+
this._isClosed = true;
|
121
130
|
|
122
131
|
this.removeAllListeners();
|
123
132
|
if (this.publisher && this.publisher.pc.signalingState !== 'closed') {
|
@@ -403,7 +412,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
403
412
|
// continues to work, we can reconnect to websocket to continue the session
|
404
413
|
// after a number of retries, we'll close and give up permanently
|
405
414
|
private handleDisconnect = (connection: string) => {
|
406
|
-
if (this.
|
415
|
+
if (this._isClosed) {
|
407
416
|
return;
|
408
417
|
}
|
409
418
|
log.debug(`${connection} disconnected`);
|
@@ -414,7 +423,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
414
423
|
|
415
424
|
const delay = this.reconnectAttempts * this.reconnectAttempts * 300;
|
416
425
|
setTimeout(async () => {
|
417
|
-
if (this.
|
426
|
+
if (this._isClosed) {
|
418
427
|
return;
|
419
428
|
}
|
420
429
|
if (
|
package/src/room/Room.ts
CHANGED
@@ -35,12 +35,16 @@ import { AdaptiveStreamSettings, RemoteTrack } from './track/types';
|
|
35
35
|
import { getNewAudioContext } from './track/utils';
|
36
36
|
import { isWeb, unpackStreamId } from './utils';
|
37
37
|
|
38
|
-
export enum
|
38
|
+
export enum ConnectionState {
|
39
39
|
Disconnected = 'disconnected',
|
40
|
+
Connecting = 'connecting',
|
40
41
|
Connected = 'connected',
|
41
42
|
Reconnecting = 'reconnecting',
|
42
43
|
}
|
43
44
|
|
45
|
+
/** @deprecated RoomState has been renamed to [[ConnectionState]] */
|
46
|
+
export const RoomState = ConnectionState;
|
47
|
+
|
44
48
|
/**
|
45
49
|
* In LiveKit, a room is the logical grouping for a list of participants.
|
46
50
|
* Participants in a room can publish tracks, and subscribe to others' tracks.
|
@@ -50,7 +54,7 @@ export enum RoomState {
|
|
50
54
|
* @noInheritDoc
|
51
55
|
*/
|
52
56
|
class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) {
|
53
|
-
state:
|
57
|
+
state: ConnectionState = ConnectionState.Disconnected;
|
54
58
|
|
55
59
|
/** map of sid: [[RemoteParticipant]] */
|
56
60
|
participants: Map<string, RemoteParticipant>;
|
@@ -87,6 +91,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
87
91
|
|
88
92
|
private audioContext?: AudioContext;
|
89
93
|
|
94
|
+
/** used for aborting pending connections to a LiveKit server */
|
95
|
+
private abortController?: AbortController;
|
96
|
+
|
90
97
|
/**
|
91
98
|
* Creates a new Room, the primary construct for a LiveKit session.
|
92
99
|
* @param options
|
@@ -150,18 +157,16 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
150
157
|
.on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
|
151
158
|
.on(EngineEvent.DataPacketReceived, this.handleDataPacket)
|
152
159
|
.on(EngineEvent.Resuming, () => {
|
153
|
-
this.
|
160
|
+
this.setAndEmitConnectionState(ConnectionState.Reconnecting);
|
154
161
|
this.emit(RoomEvent.Reconnecting);
|
155
|
-
this.emit(RoomEvent.StateChanged, this.state);
|
156
162
|
})
|
157
163
|
.on(EngineEvent.Resumed, () => {
|
158
|
-
this.
|
164
|
+
this.setAndEmitConnectionState(ConnectionState.Connected);
|
159
165
|
this.emit(RoomEvent.Reconnected);
|
160
|
-
this.emit(RoomEvent.StateChanged, this.state);
|
161
166
|
this.updateSubscriptions();
|
162
167
|
})
|
163
168
|
.on(EngineEvent.SignalResumed, () => {
|
164
|
-
if (this.state ===
|
169
|
+
if (this.state === ConnectionState.Reconnecting) {
|
165
170
|
this.sendSyncState();
|
166
171
|
}
|
167
172
|
})
|
@@ -186,11 +191,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
186
191
|
|
187
192
|
connect = async (url: string, token: string, opts?: RoomConnectOptions) => {
|
188
193
|
// guard against calling connect
|
189
|
-
if (this.state !==
|
194
|
+
if (this.state !== ConnectionState.Disconnected) {
|
190
195
|
log.warn(`already connected to room ${this.name}`);
|
191
196
|
return;
|
192
197
|
}
|
193
198
|
|
199
|
+
this.setAndEmitConnectionState(ConnectionState.Connecting);
|
200
|
+
|
201
|
+
if (!this.abortController || this.abortController.signal.aborted) {
|
202
|
+
this.abortController = new AbortController();
|
203
|
+
}
|
204
|
+
|
194
205
|
// recreate engine if previously disconnected
|
195
206
|
this.createEngine();
|
196
207
|
|
@@ -203,12 +214,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
203
214
|
this.connOptions = opts;
|
204
215
|
|
205
216
|
try {
|
206
|
-
const joinResponse = await this.engine.join(
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
217
|
+
const joinResponse = await this.engine.join(
|
218
|
+
url,
|
219
|
+
token,
|
220
|
+
{
|
221
|
+
autoSubscribe: opts?.autoSubscribe,
|
222
|
+
publishOnly: opts?.publishOnly,
|
223
|
+
adaptiveStream:
|
224
|
+
typeof this.options?.adaptiveStream === 'object' ? true : this.options?.adaptiveStream,
|
225
|
+
},
|
226
|
+
this.abortController.signal,
|
227
|
+
);
|
212
228
|
log.debug(
|
213
229
|
`connected to Livekit Server version: ${joinResponse.serverVersion}, region: ${joinResponse.serverRegion}`,
|
214
230
|
);
|
@@ -223,7 +239,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
223
239
|
this.options.dynacast = false;
|
224
240
|
}
|
225
241
|
|
226
|
-
this.state = RoomState.Connected;
|
227
242
|
const pi = joinResponse.participant!;
|
228
243
|
|
229
244
|
this.localParticipant.sid = pi.sid;
|
@@ -232,9 +247,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
232
247
|
this.localParticipant.updateInfo(pi);
|
233
248
|
// forward metadata changed for the local participant
|
234
249
|
this.localParticipant
|
235
|
-
.on(ParticipantEvent.MetadataChanged, (metadata: string | undefined) => {
|
236
|
-
this.emit(RoomEvent.MetadataChanged, metadata, this.localParticipant);
|
237
|
-
})
|
238
250
|
.on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
|
239
251
|
this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
|
240
252
|
})
|
@@ -275,9 +287,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
275
287
|
this.name = joinResponse.room!.name;
|
276
288
|
this.sid = joinResponse.room!.sid;
|
277
289
|
this.metadata = joinResponse.room!.metadata;
|
278
|
-
this.emit(RoomEvent.StateChanged, this.state);
|
279
290
|
} catch (err) {
|
280
291
|
this.engine.close();
|
292
|
+
this.setAndEmitConnectionState(ConnectionState.Disconnected);
|
281
293
|
throw err;
|
282
294
|
}
|
283
295
|
|
@@ -286,18 +298,30 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
286
298
|
const connectTimeout = setTimeout(() => {
|
287
299
|
// timeout
|
288
300
|
this.engine.close();
|
301
|
+
this.setAndEmitConnectionState(ConnectionState.Disconnected);
|
289
302
|
reject(new ConnectionError('could not connect after timeout'));
|
290
303
|
}, maxICEConnectTimeout);
|
304
|
+
const abortHandler = () => {
|
305
|
+
log.warn('closing engine');
|
306
|
+
clearTimeout(connectTimeout);
|
307
|
+
this.engine.close();
|
308
|
+
this.setAndEmitConnectionState(ConnectionState.Disconnected);
|
309
|
+
reject(new ConnectionError('room connection has been cancelled'));
|
310
|
+
};
|
311
|
+
if (this.abortController?.signal.aborted) {
|
312
|
+
abortHandler();
|
313
|
+
}
|
314
|
+
this.abortController?.signal.addEventListener('abort', abortHandler);
|
291
315
|
|
292
316
|
this.engine.once(EngineEvent.Connected, () => {
|
293
317
|
clearTimeout(connectTimeout);
|
294
|
-
|
318
|
+
this.abortController?.signal.removeEventListener('abort', abortHandler);
|
295
319
|
// also hook unload event
|
296
320
|
if (isWeb()) {
|
297
321
|
window.addEventListener('beforeunload', this.onBeforeUnload);
|
298
322
|
navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange);
|
299
323
|
}
|
300
|
-
|
324
|
+
this.setAndEmitConnectionState(ConnectionState.Connected);
|
301
325
|
resolve(this);
|
302
326
|
});
|
303
327
|
});
|
@@ -307,6 +331,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
307
331
|
* disconnects the room, emits [[RoomEvent.Disconnected]]
|
308
332
|
*/
|
309
333
|
disconnect = (stopTracks = true) => {
|
334
|
+
if (this.state === ConnectionState.Connecting) {
|
335
|
+
// try aborting pending connection attempt
|
336
|
+
log.warn('abort connection attempt');
|
337
|
+
this.abortController?.abort();
|
338
|
+
return;
|
339
|
+
}
|
310
340
|
// send leave
|
311
341
|
if (this.engine) {
|
312
342
|
this.engine.client.sendLeave();
|
@@ -482,9 +512,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
482
512
|
}
|
483
513
|
|
484
514
|
private handleRestarting = () => {
|
485
|
-
this.
|
515
|
+
this.setAndEmitConnectionState(ConnectionState.Reconnecting);
|
486
516
|
this.emit(RoomEvent.Reconnecting);
|
487
|
-
this.emit(RoomEvent.StateChanged, this.state);
|
488
517
|
|
489
518
|
// also unwind existing participants & existing subscriptions
|
490
519
|
for (const p of this.participants.values()) {
|
@@ -494,9 +523,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
494
523
|
|
495
524
|
private handleRestarted = async (joinResponse: JoinResponse) => {
|
496
525
|
log.debug(`reconnected to server region ${joinResponse.serverRegion}`);
|
497
|
-
this.
|
526
|
+
this.setAndEmitConnectionState(ConnectionState.Connected);
|
498
527
|
this.emit(RoomEvent.Reconnected);
|
499
|
-
this.emit(RoomEvent.StateChanged, this.state);
|
500
528
|
|
501
529
|
// rehydrate participants
|
502
530
|
if (joinResponse.participant) {
|
@@ -524,9 +552,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
524
552
|
};
|
525
553
|
|
526
554
|
private handleDisconnect(shouldStopTracks = true) {
|
527
|
-
if (this.state === RoomState.Disconnected) {
|
528
|
-
return;
|
529
|
-
}
|
530
555
|
this.participants.forEach((p) => {
|
531
556
|
p.tracks.forEach((pub) => {
|
532
557
|
p.unpublishTrack(pub.trackSid);
|
@@ -535,7 +560,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
535
560
|
|
536
561
|
this.localParticipant.tracks.forEach((pub) => {
|
537
562
|
if (pub.track) {
|
538
|
-
this.localParticipant.unpublishTrack(pub.track);
|
563
|
+
this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
|
539
564
|
}
|
540
565
|
if (shouldStopTracks) {
|
541
566
|
pub.track?.detach();
|
@@ -553,9 +578,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
553
578
|
window.removeEventListener('beforeunload', this.onBeforeUnload);
|
554
579
|
navigator.mediaDevices?.removeEventListener('devicechange', this.handleDeviceChange);
|
555
580
|
}
|
556
|
-
this.
|
581
|
+
this.setAndEmitConnectionState(ConnectionState.Disconnected);
|
557
582
|
this.emit(RoomEvent.Disconnected);
|
558
|
-
this.emit(RoomEvent.StateChanged, this.state);
|
559
583
|
}
|
560
584
|
|
561
585
|
private handleParticipantUpdates = (participantInfos: ParticipantInfo[]) => {
|
@@ -821,9 +845,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
821
845
|
.on(ParticipantEvent.TrackUnmuted, (pub: TrackPublication) => {
|
822
846
|
this.emit(RoomEvent.TrackUnmuted, pub, participant);
|
823
847
|
})
|
824
|
-
.on(ParticipantEvent.MetadataChanged, (metadata: string | undefined) => {
|
825
|
-
this.emit(RoomEvent.MetadataChanged, metadata, participant);
|
826
|
-
})
|
827
848
|
.on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
|
828
849
|
this.emit(RoomEvent.ParticipantMetadataChanged, metadata, participant);
|
829
850
|
})
|
@@ -892,6 +913,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
892
913
|
}
|
893
914
|
}
|
894
915
|
|
916
|
+
private setAndEmitConnectionState(state: ConnectionState) {
|
917
|
+
if (state === this.state) {
|
918
|
+
return;
|
919
|
+
}
|
920
|
+
this.state = state;
|
921
|
+
this.emit(RoomEvent.ConnectionStateChanged, this.state);
|
922
|
+
}
|
923
|
+
|
895
924
|
// /** @internal */
|
896
925
|
emit<E extends keyof RoomEventCallbacks>(
|
897
926
|
event: E,
|
@@ -908,7 +937,9 @@ export type RoomEventCallbacks = {
|
|
908
937
|
reconnecting: () => void;
|
909
938
|
reconnected: () => void;
|
910
939
|
disconnected: () => void;
|
911
|
-
stateChanged
|
940
|
+
/** @deprecated stateChanged has been renamed to connectionStateChanged */
|
941
|
+
stateChanged: (state: ConnectionState) => void;
|
942
|
+
connectionStateChanged: (state: ConnectionState) => void;
|
912
943
|
mediaDevicesChanged: () => void;
|
913
944
|
participantConnected: (participant: RemoteParticipant) => void;
|
914
945
|
participantDisconnected: (participant: RemoteParticipant) => void;
|
@@ -932,13 +963,6 @@ export type RoomEventCallbacks = {
|
|
932
963
|
publication: LocalTrackPublication,
|
933
964
|
participant: LocalParticipant,
|
934
965
|
) => void;
|
935
|
-
/**
|
936
|
-
* @deprecated use [[participantMetadataChanged]] instead
|
937
|
-
*/
|
938
|
-
metadataChanged: (
|
939
|
-
metadata: string | undefined,
|
940
|
-
participant?: RemoteParticipant | LocalParticipant,
|
941
|
-
) => void;
|
942
966
|
participantMetadataChanged: (
|
943
967
|
metadata: string | undefined,
|
944
968
|
participant: RemoteParticipant | LocalParticipant,
|
package/src/room/events.ts
CHANGED
@@ -29,9 +29,14 @@ export enum RoomEvent {
|
|
29
29
|
/**
|
30
30
|
* Whenever the connection state of the room changes
|
31
31
|
*
|
32
|
-
* args: ([[
|
32
|
+
* args: ([[ConnectionState]])
|
33
33
|
*/
|
34
|
-
|
34
|
+
ConnectionStateChanged = 'connectionStateChanged',
|
35
|
+
|
36
|
+
/**
|
37
|
+
* @deprecated StateChanged has been renamed to ConnectionStateChanged
|
38
|
+
*/
|
39
|
+
StateChanged = 'connectionStateChanged',
|
35
40
|
|
36
41
|
/**
|
37
42
|
* When input or output devices on the machine have changed.
|
@@ -139,12 +144,6 @@ export enum RoomEvent {
|
|
139
144
|
*/
|
140
145
|
ActiveSpeakersChanged = 'activeSpeakersChanged',
|
141
146
|
|
142
|
-
/**
|
143
|
-
* @deprecated Use ParticipantMetadataChanged instead
|
144
|
-
* @internal
|
145
|
-
*/
|
146
|
-
MetadataChanged = 'metadataChanged',
|
147
|
-
|
148
147
|
/**
|
149
148
|
* Participant metadata is a simple way for app-specific state to be pushed to
|
150
149
|
* all users.
|
@@ -308,12 +307,6 @@ export enum ParticipantEvent {
|
|
308
307
|
*/
|
309
308
|
LocalTrackUnpublished = 'localTrackUnpublished',
|
310
309
|
|
311
|
-
/**
|
312
|
-
* @deprecated Use ParticipantMetadataChanged instead
|
313
|
-
* @internal
|
314
|
-
*/
|
315
|
-
MetadataChanged = 'metadataChanged',
|
316
|
-
|
317
310
|
/**
|
318
311
|
* Participant metadata is a simple way for app-specific state to be pushed to
|
319
312
|
* all users.
|
@@ -436,6 +436,10 @@ export default class LocalParticipant extends Participant {
|
|
436
436
|
];
|
437
437
|
}
|
438
438
|
|
439
|
+
if (!this.engine || this.engine.isClosed) {
|
440
|
+
throw new UnexpectedConnectionState('cannot publish track when not connected');
|
441
|
+
}
|
442
|
+
|
439
443
|
const ti = await this.engine.addTrack(req);
|
440
444
|
const publication = new LocalTrackPublication(track.kind, ti, track);
|
441
445
|
track.sid = ti.sid;
|
@@ -188,7 +188,6 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
|
|
188
188
|
this.metadata = md;
|
189
189
|
|
190
190
|
if (changed) {
|
191
|
-
this.emit(ParticipantEvent.MetadataChanged, prevMetadata);
|
192
191
|
this.emit(ParticipantEvent.ParticipantMetadataChanged, prevMetadata);
|
193
192
|
}
|
194
193
|
}
|
@@ -266,10 +265,6 @@ export type ParticipantEventCallbacks = {
|
|
266
265
|
trackUnmuted: (publication: TrackPublication) => void;
|
267
266
|
localTrackPublished: (publication: LocalTrackPublication) => void;
|
268
267
|
localTrackUnpublished: (publication: LocalTrackPublication) => void;
|
269
|
-
/**
|
270
|
-
* @deprecated use [[participantMetadataChanged]] instead
|
271
|
-
*/
|
272
|
-
metadataChanged: (prevMetadata: string | undefined, participant?: any) => void;
|
273
268
|
participantMetadataChanged: (prevMetadata: string | undefined, participant?: any) => void;
|
274
269
|
dataReceived: (payload: Uint8Array, kind: DataPacket_Kind) => void;
|
275
270
|
isSpeakingChanged: (speaking: boolean) => void;
|
@@ -19,6 +19,8 @@ export default class RemoteParticipant extends Participant {
|
|
19
19
|
|
20
20
|
signalClient: SignalClient;
|
21
21
|
|
22
|
+
private volume?: number;
|
23
|
+
|
22
24
|
/** @internal */
|
23
25
|
static fromParticipantInfo(signalClient: SignalClient, pi: ParticipantInfo): RemoteParticipant {
|
24
26
|
const rp = new RemoteParticipant(signalClient, pi.sid, pi.identity);
|
@@ -68,24 +70,26 @@ export default class RemoteParticipant extends Participant {
|
|
68
70
|
}
|
69
71
|
|
70
72
|
/**
|
71
|
-
* sets the volume on the participant's microphone track
|
73
|
+
* sets the volume on the participant's microphone track
|
74
|
+
* if no track exists the volume will be applied when the microphone track is added
|
72
75
|
*/
|
73
76
|
setVolume(volume: number) {
|
77
|
+
this.volume = volume;
|
74
78
|
const audioPublication = this.getTrack(Track.Source.Microphone);
|
75
|
-
if (audioPublication) {
|
79
|
+
if (audioPublication && audioPublication.track) {
|
76
80
|
(audioPublication.track as RemoteAudioTrack).setVolume(volume);
|
77
81
|
}
|
78
82
|
}
|
79
83
|
|
80
84
|
/**
|
81
85
|
* gets the volume on the participant's microphone track
|
82
|
-
* returns undefined if no microphone track exists
|
83
86
|
*/
|
84
87
|
getVolume() {
|
85
88
|
const audioPublication = this.getTrack(Track.Source.Microphone);
|
86
|
-
if (audioPublication) {
|
89
|
+
if (audioPublication && audioPublication.track) {
|
87
90
|
return (audioPublication.track as RemoteAudioTrack).getVolume();
|
88
91
|
}
|
92
|
+
return this.volume;
|
89
93
|
}
|
90
94
|
|
91
95
|
/** @internal */
|
@@ -153,7 +157,14 @@ export default class RemoteParticipant extends Participant {
|
|
153
157
|
track.start();
|
154
158
|
|
155
159
|
publication.setTrack(track);
|
156
|
-
|
160
|
+
// set participant volume on new microphone tracks
|
161
|
+
if (
|
162
|
+
this.volume !== undefined &&
|
163
|
+
track instanceof RemoteAudioTrack &&
|
164
|
+
track.source === Track.Source.Microphone
|
165
|
+
) {
|
166
|
+
track.setVolume(this.volume);
|
167
|
+
}
|
157
168
|
this.emit(ParticipantEvent.TrackSubscribed, track, publication);
|
158
169
|
|
159
170
|
return publication;
|