livekit-client 0.15.2 → 0.16.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/dist/api/SignalClient.d.ts +11 -3
- package/dist/api/SignalClient.js +92 -28
- package/dist/api/SignalClient.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +5 -0
- package/dist/proto/livekit_models.d.ts +33 -0
- package/dist/proto/livekit_models.js +263 -4
- package/dist/proto/livekit_models.js.map +1 -1
- package/dist/proto/livekit_rtc.d.ts +48 -10
- package/dist/proto/livekit_rtc.js +273 -22
- package/dist/proto/livekit_rtc.js.map +1 -1
- package/dist/room/PCTransport.js +4 -0
- package/dist/room/PCTransport.js.map +1 -1
- package/dist/room/RTCEngine.d.ts +10 -2
- package/dist/room/RTCEngine.js +182 -42
- package/dist/room/RTCEngine.js.map +1 -1
- package/dist/room/Room.d.ts +15 -0
- package/dist/room/Room.js +165 -20
- package/dist/room/Room.js.map +1 -1
- package/dist/room/events.d.ts +42 -20
- package/dist/room/events.js +41 -19
- package/dist/room/events.js.map +1 -1
- package/dist/room/participant/LocalParticipant.d.ts +25 -4
- package/dist/room/participant/LocalParticipant.js +47 -20
- package/dist/room/participant/LocalParticipant.js.map +1 -1
- package/dist/room/participant/Participant.d.ts +1 -1
- package/dist/room/participant/ParticipantTrackPermission.d.ts +19 -0
- package/dist/room/participant/ParticipantTrackPermission.js +16 -0
- package/dist/room/participant/ParticipantTrackPermission.js.map +1 -0
- package/dist/room/participant/RemoteParticipant.d.ts +2 -2
- package/dist/room/participant/RemoteParticipant.js +11 -14
- package/dist/room/participant/RemoteParticipant.js.map +1 -1
- package/dist/room/participant/publishUtils.js +1 -1
- package/dist/room/participant/publishUtils.js.map +1 -1
- package/dist/room/participant/publishUtils.test.js +9 -0
- package/dist/room/participant/publishUtils.test.js.map +1 -1
- package/dist/room/track/LocalTrack.d.ts +0 -3
- package/dist/room/track/LocalTrack.js +1 -6
- package/dist/room/track/LocalTrack.js.map +1 -1
- package/dist/room/track/LocalTrackPublication.d.ts +5 -1
- package/dist/room/track/LocalTrackPublication.js +15 -5
- package/dist/room/track/LocalTrackPublication.js.map +1 -1
- package/dist/room/track/LocalVideoTrack.js +2 -0
- package/dist/room/track/LocalVideoTrack.js.map +1 -1
- package/dist/room/track/RemoteAudioTrack.d.ts +5 -14
- package/dist/room/track/RemoteAudioTrack.js +7 -32
- package/dist/room/track/RemoteAudioTrack.js.map +1 -1
- package/dist/room/track/RemoteTrack.d.ts +14 -0
- package/dist/room/track/RemoteTrack.js +47 -0
- package/dist/room/track/RemoteTrack.js.map +1 -0
- package/dist/room/track/RemoteTrackPublication.d.ts +10 -2
- package/dist/room/track/RemoteTrackPublication.js +51 -13
- package/dist/room/track/RemoteTrackPublication.js.map +1 -1
- package/dist/room/track/RemoteVideoTrack.d.ts +3 -9
- package/dist/room/track/RemoteVideoTrack.js +16 -36
- package/dist/room/track/RemoteVideoTrack.js.map +1 -1
- package/dist/room/track/Track.d.ts +3 -0
- package/dist/room/track/Track.js +14 -5
- package/dist/room/track/Track.js.map +1 -1
- package/dist/room/track/TrackPublication.d.ts +12 -1
- package/dist/room/track/TrackPublication.js +23 -7
- package/dist/room/track/TrackPublication.js.map +1 -1
- package/dist/room/track/create.js +5 -0
- package/dist/room/track/create.js.map +1 -1
- package/dist/room/utils.d.ts +2 -0
- package/dist/room/utils.js +12 -1
- package/dist/room/utils.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +3 -3
- package/src/api/SignalClient.ts +444 -0
- package/src/connect.ts +100 -0
- package/src/index.ts +47 -0
- package/src/logger.ts +22 -0
- package/src/options.ts +152 -0
- package/src/proto/livekit_models.ts +1863 -0
- package/src/proto/livekit_rtc.ts +3415 -0
- package/src/room/DeviceManager.ts +57 -0
- package/src/room/PCTransport.ts +86 -0
- package/src/room/RTCEngine.ts +582 -0
- package/src/room/Room.ts +840 -0
- package/src/room/errors.ts +65 -0
- package/src/room/events.ts +398 -0
- package/src/room/participant/LocalParticipant.ts +685 -0
- package/src/room/participant/Participant.ts +214 -0
- package/src/room/participant/ParticipantTrackPermission.ts +32 -0
- package/src/room/participant/RemoteParticipant.ts +241 -0
- package/src/room/participant/publishUtils.test.ts +105 -0
- package/src/room/participant/publishUtils.ts +180 -0
- package/src/room/stats.ts +130 -0
- package/src/room/track/LocalAudioTrack.ts +112 -0
- package/src/room/track/LocalTrack.ts +124 -0
- package/src/room/track/LocalTrackPublication.ts +66 -0
- package/src/room/track/LocalVideoTrack.test.ts +70 -0
- package/src/room/track/LocalVideoTrack.ts +416 -0
- package/src/room/track/RemoteAudioTrack.ts +58 -0
- package/src/room/track/RemoteTrack.ts +59 -0
- package/src/room/track/RemoteTrackPublication.ts +198 -0
- package/src/room/track/RemoteVideoTrack.ts +215 -0
- package/src/room/track/Track.ts +307 -0
- package/src/room/track/TrackPublication.ts +120 -0
- package/src/room/track/create.ts +120 -0
- package/src/room/track/defaults.ts +23 -0
- package/src/room/track/options.ts +229 -0
- package/src/room/track/types.ts +8 -0
- package/src/room/track/utils.test.ts +93 -0
- package/src/room/track/utils.ts +76 -0
- package/src/room/utils.ts +58 -0
- package/src/version.ts +2 -0
- package/.github/workflows/publish.yaml +0 -55
- package/.github/workflows/test.yaml +0 -36
- package/example/index.html +0 -248
- package/example/sample.ts +0 -621
- package/example/styles.css +0 -144
- package/example/webpack.config.js +0 -33
package/example/sample.ts
DELETED
@@ -1,621 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
DataPacket_Kind, LocalParticipant,
|
3
|
-
LocalTrack,
|
4
|
-
MediaDeviceFailure,
|
5
|
-
Participant,
|
6
|
-
ParticipantEvent,
|
7
|
-
RemoteAudioTrack,
|
8
|
-
RemoteParticipant, RemoteVideoTrack, Room, RoomConnectOptions, RoomEvent,
|
9
|
-
RoomOptions, RoomState, setLogLevel, Track, TrackPublication,
|
10
|
-
VideoCaptureOptions, VideoPresets,
|
11
|
-
} from '../src/index';
|
12
|
-
import { ConnectionQuality } from '../src/room/participant/Participant';
|
13
|
-
|
14
|
-
const $ = (id: string) => document.getElementById(id);
|
15
|
-
|
16
|
-
const state = {
|
17
|
-
isFrontFacing: false,
|
18
|
-
encoder: new TextEncoder(),
|
19
|
-
decoder: new TextDecoder(),
|
20
|
-
defaultDevices: new Map<MediaDeviceKind, string>(),
|
21
|
-
bitrateInterval: undefined as any,
|
22
|
-
};
|
23
|
-
let currentRoom: Room | undefined;
|
24
|
-
|
25
|
-
// handles actions from the HTML
|
26
|
-
const appActions = {
|
27
|
-
connectWithFormInput: async () => {
|
28
|
-
const url = (<HTMLInputElement>$('url')).value;
|
29
|
-
const token = (<HTMLInputElement>$('token')).value;
|
30
|
-
const simulcast = (<HTMLInputElement>$('simulcast')).checked;
|
31
|
-
const dynacast = (<HTMLInputElement>$('dynacast')).checked;
|
32
|
-
const forceTURN = (<HTMLInputElement>$('force-turn')).checked;
|
33
|
-
const adaptiveStream = (<HTMLInputElement>$('adaptive-stream')).checked;
|
34
|
-
const shouldPublish = (<HTMLInputElement>$('publish-option')).checked;
|
35
|
-
|
36
|
-
setLogLevel('debug');
|
37
|
-
|
38
|
-
const roomOpts: RoomOptions = {
|
39
|
-
adaptiveStream,
|
40
|
-
dynacast,
|
41
|
-
publishDefaults: {
|
42
|
-
simulcast,
|
43
|
-
},
|
44
|
-
videoCaptureDefaults: {
|
45
|
-
resolution: VideoPresets.hd.resolution,
|
46
|
-
},
|
47
|
-
};
|
48
|
-
|
49
|
-
const connectOpts: RoomConnectOptions = {};
|
50
|
-
if (forceTURN) {
|
51
|
-
connectOpts.rtcConfig = {
|
52
|
-
iceTransportPolicy: 'relay',
|
53
|
-
};
|
54
|
-
}
|
55
|
-
const room = await appActions.connectToRoom(url, token, roomOpts, connectOpts);
|
56
|
-
|
57
|
-
if (room && shouldPublish) {
|
58
|
-
await room.localParticipant.enableCameraAndMicrophone();
|
59
|
-
updateButtonsForPublishState();
|
60
|
-
}
|
61
|
-
|
62
|
-
state.bitrateInterval = setInterval(renderBitrate, 1000);
|
63
|
-
},
|
64
|
-
|
65
|
-
connectToRoom: async (
|
66
|
-
url: string,
|
67
|
-
token: string,
|
68
|
-
roomOptions?: RoomOptions,
|
69
|
-
connectOptions?: RoomConnectOptions,
|
70
|
-
): Promise<Room | undefined> => {
|
71
|
-
const room = new Room(roomOptions);
|
72
|
-
room
|
73
|
-
.on(RoomEvent.ParticipantConnected, participantConnected)
|
74
|
-
.on(RoomEvent.ParticipantDisconnected, participantDisconnected)
|
75
|
-
.on(RoomEvent.DataReceived, handleData)
|
76
|
-
.on(RoomEvent.Disconnected, handleRoomDisconnect)
|
77
|
-
.on(RoomEvent.Reconnecting, () => appendLog('Reconnecting to room'))
|
78
|
-
.on(RoomEvent.Reconnected, () => appendLog('Successfully reconnected!'))
|
79
|
-
.on(RoomEvent.LocalTrackPublished, () => {
|
80
|
-
renderParticipant(room.localParticipant);
|
81
|
-
updateButtonsForPublishState();
|
82
|
-
renderScreenShare();
|
83
|
-
})
|
84
|
-
.on(RoomEvent.LocalTrackUnpublished, () => {
|
85
|
-
renderParticipant(room.localParticipant);
|
86
|
-
updateButtonsForPublishState();
|
87
|
-
renderScreenShare();
|
88
|
-
})
|
89
|
-
.on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
90
|
-
appendLog('new metadata for room', metadata);
|
91
|
-
})
|
92
|
-
.on(RoomEvent.MediaDevicesChanged, handleDevicesChanged)
|
93
|
-
.on(RoomEvent.AudioPlaybackStatusChanged, () => {
|
94
|
-
if (room.canPlaybackAudio) {
|
95
|
-
$('start-audio-button')?.setAttribute('disabled', 'true');
|
96
|
-
} else {
|
97
|
-
$('start-audio-button')?.removeAttribute('disabled');
|
98
|
-
}
|
99
|
-
})
|
100
|
-
.on(RoomEvent.MediaDevicesError, (e: Error) => {
|
101
|
-
const failure = MediaDeviceFailure.getFailure(e);
|
102
|
-
appendLog('media device failure', failure);
|
103
|
-
})
|
104
|
-
.on(RoomEvent.ConnectionQualityChanged,
|
105
|
-
(quality: ConnectionQuality, participant: Participant) => {
|
106
|
-
appendLog('connection quality changed', participant.identity, quality);
|
107
|
-
});
|
108
|
-
|
109
|
-
try {
|
110
|
-
await room.connect(url, token, connectOptions);
|
111
|
-
} catch (error) {
|
112
|
-
let message: any = error;
|
113
|
-
if (error.message) {
|
114
|
-
message = error.message;
|
115
|
-
}
|
116
|
-
appendLog('could not connect:', message);
|
117
|
-
return;
|
118
|
-
}
|
119
|
-
|
120
|
-
appendLog('connected to room', room.name);
|
121
|
-
currentRoom = room;
|
122
|
-
window.currentRoom = room;
|
123
|
-
setButtonsForState(true);
|
124
|
-
|
125
|
-
appendLog('room participants', room.participants.keys());
|
126
|
-
room.participants.forEach((participant) => {
|
127
|
-
participantConnected(participant);
|
128
|
-
});
|
129
|
-
participantConnected(room.localParticipant);
|
130
|
-
|
131
|
-
return room;
|
132
|
-
},
|
133
|
-
|
134
|
-
toggleAudio: async () => {
|
135
|
-
if (!currentRoom) return;
|
136
|
-
const enabled = currentRoom.localParticipant.isMicrophoneEnabled;
|
137
|
-
if (enabled) {
|
138
|
-
appendLog('disabling audio');
|
139
|
-
} else {
|
140
|
-
appendLog('enabling audio');
|
141
|
-
}
|
142
|
-
await currentRoom.localParticipant.setMicrophoneEnabled(!enabled);
|
143
|
-
updateButtonsForPublishState();
|
144
|
-
},
|
145
|
-
|
146
|
-
toggleVideo: async () => {
|
147
|
-
if (!currentRoom) return;
|
148
|
-
const enabled = currentRoom.localParticipant.isCameraEnabled;
|
149
|
-
if (enabled) {
|
150
|
-
appendLog('disabling video');
|
151
|
-
} else {
|
152
|
-
appendLog('enabling video');
|
153
|
-
}
|
154
|
-
await currentRoom.localParticipant.setCameraEnabled(!enabled);
|
155
|
-
renderParticipant(currentRoom.localParticipant);
|
156
|
-
|
157
|
-
// update display
|
158
|
-
updateButtonsForPublishState();
|
159
|
-
},
|
160
|
-
|
161
|
-
flipVideo: () => {
|
162
|
-
const videoPub = currentRoom?.localParticipant.getTrack(Track.Source.Camera);
|
163
|
-
if (!videoPub) {
|
164
|
-
return;
|
165
|
-
}
|
166
|
-
if (state.isFrontFacing) {
|
167
|
-
setButtonState('flip-video-button', 'Front Camera', false);
|
168
|
-
} else {
|
169
|
-
setButtonState('flip-video-button', 'Back Camera', false);
|
170
|
-
}
|
171
|
-
state.isFrontFacing = !state.isFrontFacing;
|
172
|
-
const options: VideoCaptureOptions = {
|
173
|
-
resolution: VideoPresets.qhd.resolution,
|
174
|
-
facingMode: state.isFrontFacing ? 'user' : 'environment',
|
175
|
-
};
|
176
|
-
videoPub.videoTrack?.restartTrack(options);
|
177
|
-
},
|
178
|
-
|
179
|
-
shareScreen: async () => {
|
180
|
-
if (!currentRoom) return;
|
181
|
-
|
182
|
-
const enabled = currentRoom.localParticipant.isScreenShareEnabled;
|
183
|
-
appendLog(`${enabled ? 'stopping' : 'starting'} screen share`);
|
184
|
-
await currentRoom.localParticipant.setScreenShareEnabled(!enabled);
|
185
|
-
updateButtonsForPublishState();
|
186
|
-
},
|
187
|
-
|
188
|
-
startAudio: () => {
|
189
|
-
currentRoom?.startAudio();
|
190
|
-
},
|
191
|
-
|
192
|
-
enterText: () => {
|
193
|
-
if (!currentRoom) return;
|
194
|
-
const textField = <HTMLInputElement>$('entry');
|
195
|
-
if (textField.value) {
|
196
|
-
const msg = state.encoder.encode(textField.value);
|
197
|
-
currentRoom.localParticipant.publishData(msg, DataPacket_Kind.RELIABLE);
|
198
|
-
(<HTMLTextAreaElement>(
|
199
|
-
$('chat')
|
200
|
-
)).value += `${currentRoom.localParticipant.identity} (me): ${textField.value}\n`;
|
201
|
-
textField.value = '';
|
202
|
-
}
|
203
|
-
},
|
204
|
-
|
205
|
-
disconnectRoom: () => {
|
206
|
-
if (currentRoom) {
|
207
|
-
currentRoom.disconnect();
|
208
|
-
}
|
209
|
-
if (state.bitrateInterval) {
|
210
|
-
clearInterval(state.bitrateInterval);
|
211
|
-
}
|
212
|
-
},
|
213
|
-
|
214
|
-
disconnectSignal: () => {
|
215
|
-
if (!currentRoom) return;
|
216
|
-
currentRoom.engine.client.close();
|
217
|
-
if (currentRoom.engine.client.onClose) {
|
218
|
-
currentRoom.engine.client.onClose('manual disconnect');
|
219
|
-
}
|
220
|
-
},
|
221
|
-
|
222
|
-
handleDeviceSelected: async (e: Event) => {
|
223
|
-
const deviceId = (<HTMLSelectElement>e.target).value;
|
224
|
-
const elementId = (<HTMLSelectElement>e.target).id;
|
225
|
-
const kind = elementMapping[elementId];
|
226
|
-
if (!kind) {
|
227
|
-
return;
|
228
|
-
}
|
229
|
-
|
230
|
-
state.defaultDevices.set(kind, deviceId);
|
231
|
-
|
232
|
-
if (currentRoom) {
|
233
|
-
await currentRoom.switchActiveDevice(kind, deviceId);
|
234
|
-
}
|
235
|
-
},
|
236
|
-
};
|
237
|
-
|
238
|
-
declare global {
|
239
|
-
interface Window {
|
240
|
-
currentRoom: any;
|
241
|
-
appActions: typeof appActions;
|
242
|
-
}
|
243
|
-
}
|
244
|
-
|
245
|
-
window.appActions = appActions;
|
246
|
-
|
247
|
-
// --------------------------- event handlers ------------------------------- //
|
248
|
-
|
249
|
-
function handleData(msg: Uint8Array, participant?: RemoteParticipant) {
|
250
|
-
const str = state.decoder.decode(msg);
|
251
|
-
const chat = <HTMLTextAreaElement>$('chat');
|
252
|
-
let from = 'server';
|
253
|
-
if (participant) {
|
254
|
-
from = participant.identity;
|
255
|
-
}
|
256
|
-
chat.value += `${from}: ${str}\n`;
|
257
|
-
}
|
258
|
-
|
259
|
-
function participantConnected(participant: Participant) {
|
260
|
-
appendLog('participant', participant.identity, 'connected', participant.metadata);
|
261
|
-
participant
|
262
|
-
.on(ParticipantEvent.TrackSubscribed, (_, pub: TrackPublication) => {
|
263
|
-
appendLog('subscribed to track', pub.trackSid, participant.identity);
|
264
|
-
renderParticipant(participant);
|
265
|
-
renderScreenShare();
|
266
|
-
})
|
267
|
-
.on(ParticipantEvent.TrackUnsubscribed, (_, pub: TrackPublication) => {
|
268
|
-
appendLog('unsubscribed from track', pub.trackSid);
|
269
|
-
renderParticipant(participant);
|
270
|
-
renderScreenShare();
|
271
|
-
})
|
272
|
-
.on(ParticipantEvent.TrackMuted, (pub: TrackPublication) => {
|
273
|
-
appendLog('track was muted', pub.trackSid, participant.identity);
|
274
|
-
renderParticipant(participant);
|
275
|
-
})
|
276
|
-
.on(ParticipantEvent.TrackUnmuted, (pub: TrackPublication) => {
|
277
|
-
appendLog('track was unmuted', pub.trackSid, participant.identity);
|
278
|
-
renderParticipant(participant);
|
279
|
-
})
|
280
|
-
.on(ParticipantEvent.IsSpeakingChanged, () => {
|
281
|
-
renderParticipant(participant);
|
282
|
-
})
|
283
|
-
.on(ParticipantEvent.ConnectionQualityChanged, () => {
|
284
|
-
renderParticipant(participant);
|
285
|
-
});
|
286
|
-
}
|
287
|
-
|
288
|
-
function participantDisconnected(participant: RemoteParticipant) {
|
289
|
-
appendLog('participant', participant.sid, 'disconnected');
|
290
|
-
|
291
|
-
renderParticipant(participant, true);
|
292
|
-
}
|
293
|
-
|
294
|
-
function handleRoomDisconnect() {
|
295
|
-
if (!currentRoom) return;
|
296
|
-
appendLog('disconnected from room');
|
297
|
-
setButtonsForState(false);
|
298
|
-
renderParticipant(currentRoom.localParticipant, true);
|
299
|
-
currentRoom.participants.forEach((p) => {
|
300
|
-
renderParticipant(p, true);
|
301
|
-
});
|
302
|
-
renderScreenShare();
|
303
|
-
|
304
|
-
const container = $('participants-area');
|
305
|
-
if (container) {
|
306
|
-
container.innerHTML = '';
|
307
|
-
}
|
308
|
-
|
309
|
-
// clear the chat area on disconnect
|
310
|
-
const chat = <HTMLTextAreaElement>$('chat');
|
311
|
-
chat.value = '';
|
312
|
-
|
313
|
-
currentRoom = undefined;
|
314
|
-
window.currentRoom = undefined;
|
315
|
-
}
|
316
|
-
|
317
|
-
// -------------------------- rendering helpers ----------------------------- //
|
318
|
-
|
319
|
-
function appendLog(...args: any[]) {
|
320
|
-
const logger = $('log')!;
|
321
|
-
for (let i = 0; i < arguments.length; i += 1) {
|
322
|
-
if (typeof args[i] === 'object') {
|
323
|
-
logger.innerHTML
|
324
|
-
+= `${JSON && JSON.stringify
|
325
|
-
? JSON.stringify(args[i], undefined, 2)
|
326
|
-
: args[i]} `;
|
327
|
-
} else {
|
328
|
-
logger.innerHTML += `${args[i]} `;
|
329
|
-
}
|
330
|
-
}
|
331
|
-
logger.innerHTML += '\n';
|
332
|
-
(() => {
|
333
|
-
logger.scrollTop = logger.scrollHeight;
|
334
|
-
})();
|
335
|
-
}
|
336
|
-
|
337
|
-
// updates participant UI
|
338
|
-
function renderParticipant(participant: Participant, remove: boolean = false) {
|
339
|
-
const container = $('participants-area');
|
340
|
-
if (!container) return;
|
341
|
-
let div = $(`participant-${participant.sid}`);
|
342
|
-
if (!div && !remove) {
|
343
|
-
div = document.createElement('div');
|
344
|
-
div.id = `participant-${participant.sid}`;
|
345
|
-
div.className = 'participant';
|
346
|
-
div.innerHTML = `
|
347
|
-
<video id="video-${participant.sid}"></video>
|
348
|
-
<audio id="audio-${participant.sid}"></audio>
|
349
|
-
<div class="info-bar">
|
350
|
-
<div id="name-${participant.sid}" class="name">
|
351
|
-
</div>
|
352
|
-
<div style="text-align: center;">
|
353
|
-
<span id="size-${participant.sid}" class="size">
|
354
|
-
</span>
|
355
|
-
<span id="bitrate-${participant.sid}" class="bitrate">
|
356
|
-
</span>
|
357
|
-
</div>
|
358
|
-
<div class="right">
|
359
|
-
<span id="signal-${participant.sid}"></span>
|
360
|
-
<span id="mic-${participant.sid}" class="mic-on"></span>
|
361
|
-
</div>
|
362
|
-
</div>
|
363
|
-
`;
|
364
|
-
container.appendChild(div);
|
365
|
-
|
366
|
-
const sizeElm = $(`size-${participant.sid}`);
|
367
|
-
const videoElm = <HTMLVideoElement>$(`video-${participant.sid}`);
|
368
|
-
videoElm.onresize = () => {
|
369
|
-
updateVideoSize(videoElm!, sizeElm!);
|
370
|
-
};
|
371
|
-
}
|
372
|
-
const videoElm = <HTMLVideoElement>$(`video-${participant.sid}`);
|
373
|
-
const audioELm = <HTMLAudioElement>$(`audio-${participant.sid}`);
|
374
|
-
if (remove) {
|
375
|
-
div?.remove();
|
376
|
-
if (videoElm) {
|
377
|
-
videoElm.srcObject = null;
|
378
|
-
videoElm.src = '';
|
379
|
-
}
|
380
|
-
if (audioELm) {
|
381
|
-
audioELm.srcObject = null;
|
382
|
-
audioELm.src = '';
|
383
|
-
}
|
384
|
-
return;
|
385
|
-
}
|
386
|
-
|
387
|
-
// update properties
|
388
|
-
$(`name-${participant.sid}`)!.innerHTML = participant.identity;
|
389
|
-
const micElm = $(`mic-${participant.sid}`)!;
|
390
|
-
const signalElm = $(`signal-${participant.sid}`)!;
|
391
|
-
const cameraPub = participant.getTrack(Track.Source.Camera);
|
392
|
-
const micPub = participant.getTrack(Track.Source.Microphone);
|
393
|
-
if (participant.isSpeaking) {
|
394
|
-
div!.classList.add('speaking');
|
395
|
-
} else {
|
396
|
-
div!.classList.remove('speaking');
|
397
|
-
}
|
398
|
-
|
399
|
-
const cameraEnabled = cameraPub && cameraPub.isSubscribed && !cameraPub.isMuted;
|
400
|
-
if (cameraEnabled) {
|
401
|
-
if (participant instanceof LocalParticipant) {
|
402
|
-
// flip
|
403
|
-
videoElm.style.transform = 'scale(-1, 1)';
|
404
|
-
} else if (!cameraPub?.videoTrack?.attachedElements.includes(videoElm)) {
|
405
|
-
const startTime = Date.now();
|
406
|
-
// measure time to render
|
407
|
-
videoElm.addEventListener('loadeddata', () => {
|
408
|
-
const elapsed = Date.now() - startTime;
|
409
|
-
appendLog(`RemoteVideoTrack ${cameraPub?.trackSid} rendered in ${elapsed}ms`);
|
410
|
-
});
|
411
|
-
}
|
412
|
-
cameraPub?.videoTrack?.attach(videoElm);
|
413
|
-
} else if (cameraPub?.videoTrack) {
|
414
|
-
// detach manually whenever possible
|
415
|
-
cameraPub.videoTrack?.detach(videoElm);
|
416
|
-
} else {
|
417
|
-
videoElm.src = '';
|
418
|
-
videoElm.srcObject = null;
|
419
|
-
}
|
420
|
-
|
421
|
-
const micEnabled = micPub && micPub.isSubscribed && !micPub.isMuted;
|
422
|
-
if (micEnabled) {
|
423
|
-
if (!(participant instanceof LocalParticipant)) {
|
424
|
-
// don't attach local audio
|
425
|
-
micPub?.audioTrack?.attach(audioELm);
|
426
|
-
}
|
427
|
-
micElm.className = 'mic-on';
|
428
|
-
micElm.innerHTML = '<i class="fas fa-microphone"></i>';
|
429
|
-
} else {
|
430
|
-
micElm.className = 'mic-off';
|
431
|
-
micElm.innerHTML = '<i class="fas fa-microphone-slash"></i>';
|
432
|
-
}
|
433
|
-
|
434
|
-
switch (participant.connectionQuality) {
|
435
|
-
case ConnectionQuality.Excellent:
|
436
|
-
case ConnectionQuality.Good:
|
437
|
-
case ConnectionQuality.Poor:
|
438
|
-
signalElm.className = `connection-${participant.connectionQuality}`;
|
439
|
-
signalElm.innerHTML = '<i class="fas fa-circle"></i>';
|
440
|
-
break;
|
441
|
-
default:
|
442
|
-
signalElm.innerHTML = '';
|
443
|
-
// do nothing
|
444
|
-
}
|
445
|
-
}
|
446
|
-
|
447
|
-
function renderScreenShare() {
|
448
|
-
const div = $('screenshare-area')!;
|
449
|
-
if (!currentRoom || currentRoom.state !== RoomState.Connected) {
|
450
|
-
div.style.display = 'none';
|
451
|
-
return;
|
452
|
-
}
|
453
|
-
let participant: Participant | undefined;
|
454
|
-
let screenSharePub: TrackPublication | undefined = currentRoom.localParticipant.getTrack(
|
455
|
-
Track.Source.ScreenShare,
|
456
|
-
);
|
457
|
-
if (!screenSharePub) {
|
458
|
-
currentRoom.participants.forEach((p) => {
|
459
|
-
if (screenSharePub) {
|
460
|
-
return;
|
461
|
-
}
|
462
|
-
participant = p;
|
463
|
-
const pub = p.getTrack(Track.Source.ScreenShare);
|
464
|
-
if (pub?.isSubscribed) {
|
465
|
-
screenSharePub = pub;
|
466
|
-
}
|
467
|
-
});
|
468
|
-
} else {
|
469
|
-
participant = currentRoom.localParticipant;
|
470
|
-
}
|
471
|
-
|
472
|
-
if (screenSharePub && participant) {
|
473
|
-
div.style.display = 'block';
|
474
|
-
const videoElm = <HTMLVideoElement>$('screenshare-video');
|
475
|
-
screenSharePub.videoTrack?.attach(videoElm);
|
476
|
-
videoElm.onresize = () => {
|
477
|
-
updateVideoSize(videoElm, <HTMLSpanElement>$('screenshare-resolution'));
|
478
|
-
};
|
479
|
-
const infoElm = $('screenshare-info')!;
|
480
|
-
infoElm.innerHTML = `Screenshare from ${participant.identity}`;
|
481
|
-
} else {
|
482
|
-
div.style.display = 'none';
|
483
|
-
}
|
484
|
-
}
|
485
|
-
|
486
|
-
function renderBitrate() {
|
487
|
-
if (!currentRoom || currentRoom.state !== RoomState.Connected) {
|
488
|
-
return;
|
489
|
-
}
|
490
|
-
const participants: Participant[] = [...currentRoom.participants.values()];
|
491
|
-
participants.push(currentRoom.localParticipant);
|
492
|
-
|
493
|
-
for (const p of participants) {
|
494
|
-
const elm = $(`bitrate-${p.sid}`);
|
495
|
-
let totalBitrate = 0;
|
496
|
-
for (const t of p.tracks.values()) {
|
497
|
-
if (t.track instanceof RemoteAudioTrack || t.track instanceof RemoteVideoTrack
|
498
|
-
|| t.track instanceof LocalTrack) {
|
499
|
-
totalBitrate += t.track.currentBitrate;
|
500
|
-
}
|
501
|
-
}
|
502
|
-
let displayText = '';
|
503
|
-
if (totalBitrate > 0) {
|
504
|
-
displayText = `${Math.round(totalBitrate / 1024).toLocaleString()} kbps`;
|
505
|
-
}
|
506
|
-
if (elm) {
|
507
|
-
elm.innerHTML = displayText;
|
508
|
-
}
|
509
|
-
}
|
510
|
-
}
|
511
|
-
|
512
|
-
function updateVideoSize(element: HTMLVideoElement, target: HTMLElement) {
|
513
|
-
target.innerHTML = `(${element.videoWidth}x${element.videoHeight})`;
|
514
|
-
}
|
515
|
-
|
516
|
-
function setButtonState(buttonId: string, buttonText: string, isActive: boolean) {
|
517
|
-
const el = $(buttonId);
|
518
|
-
if (!el) return;
|
519
|
-
|
520
|
-
el.innerHTML = buttonText;
|
521
|
-
if (isActive) {
|
522
|
-
el.classList.add('active');
|
523
|
-
} else {
|
524
|
-
el.classList.remove('active');
|
525
|
-
}
|
526
|
-
}
|
527
|
-
|
528
|
-
setTimeout(handleDevicesChanged, 100);
|
529
|
-
|
530
|
-
function setButtonsForState(connected: boolean) {
|
531
|
-
const connectedSet = [
|
532
|
-
'toggle-video-button',
|
533
|
-
'toggle-audio-button',
|
534
|
-
'share-screen-button',
|
535
|
-
'disconnect-ws-button',
|
536
|
-
'disconnect-room-button',
|
537
|
-
'flip-video-button',
|
538
|
-
'send-button',
|
539
|
-
];
|
540
|
-
const disconnectedSet = ['connect-button'];
|
541
|
-
|
542
|
-
const toRemove = connected ? connectedSet : disconnectedSet;
|
543
|
-
const toAdd = connected ? disconnectedSet : connectedSet;
|
544
|
-
|
545
|
-
toRemove.forEach((id) => $(id)?.removeAttribute('disabled'));
|
546
|
-
toAdd.forEach((id) => $(id)?.setAttribute('disabled', 'true'));
|
547
|
-
}
|
548
|
-
|
549
|
-
const elementMapping: { [k: string]: MediaDeviceKind } = {
|
550
|
-
'video-input': 'videoinput',
|
551
|
-
'audio-input': 'audioinput',
|
552
|
-
'audio-output': 'audiooutput',
|
553
|
-
};
|
554
|
-
async function handleDevicesChanged() {
|
555
|
-
Promise.all(Object.keys(elementMapping).map(async (id) => {
|
556
|
-
const kind = elementMapping[id];
|
557
|
-
if (!kind) {
|
558
|
-
return;
|
559
|
-
}
|
560
|
-
const devices = await Room.getLocalDevices(kind);
|
561
|
-
const element = <HTMLSelectElement>$(id);
|
562
|
-
populateSelect(kind, element, devices, state.defaultDevices.get(kind));
|
563
|
-
}));
|
564
|
-
}
|
565
|
-
|
566
|
-
function populateSelect(
|
567
|
-
kind: MediaDeviceKind,
|
568
|
-
element: HTMLSelectElement,
|
569
|
-
devices: MediaDeviceInfo[],
|
570
|
-
selectedDeviceId?: string,
|
571
|
-
) {
|
572
|
-
// clear all elements
|
573
|
-
element.innerHTML = '';
|
574
|
-
const initialOption = document.createElement('option');
|
575
|
-
if (kind === 'audioinput') {
|
576
|
-
initialOption.text = 'Audio Input (default)';
|
577
|
-
} else if (kind === 'videoinput') {
|
578
|
-
initialOption.text = 'Video Input (default)';
|
579
|
-
} else if (kind === 'audiooutput') {
|
580
|
-
initialOption.text = 'Audio Output (default)';
|
581
|
-
}
|
582
|
-
element.appendChild(initialOption);
|
583
|
-
|
584
|
-
for (const device of devices) {
|
585
|
-
const option = document.createElement('option');
|
586
|
-
option.text = device.label;
|
587
|
-
option.value = device.deviceId;
|
588
|
-
if (device.deviceId === selectedDeviceId) {
|
589
|
-
option.selected = true;
|
590
|
-
}
|
591
|
-
element.appendChild(option);
|
592
|
-
}
|
593
|
-
}
|
594
|
-
|
595
|
-
function updateButtonsForPublishState() {
|
596
|
-
if (!currentRoom) {
|
597
|
-
return;
|
598
|
-
}
|
599
|
-
const lp = currentRoom.localParticipant;
|
600
|
-
|
601
|
-
// video
|
602
|
-
setButtonState(
|
603
|
-
'toggle-video-button',
|
604
|
-
`${lp.isCameraEnabled ? 'Disable' : 'Enable'} Video`,
|
605
|
-
lp.isCameraEnabled,
|
606
|
-
);
|
607
|
-
|
608
|
-
// audio
|
609
|
-
setButtonState(
|
610
|
-
'toggle-audio-button',
|
611
|
-
`${lp.isMicrophoneEnabled ? 'Disable' : 'Enable'} Audio`,
|
612
|
-
lp.isMicrophoneEnabled,
|
613
|
-
);
|
614
|
-
|
615
|
-
// screen share
|
616
|
-
setButtonState(
|
617
|
-
'share-screen-button',
|
618
|
-
lp.isScreenShareEnabled ? 'Stop Screen Share' : 'Share Screen',
|
619
|
-
lp.isScreenShareEnabled,
|
620
|
-
);
|
621
|
-
}
|