livekit-client 0.15.1 → 0.16.0

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.
Files changed (120) hide show
  1. package/dist/api/SignalClient.d.ts +11 -3
  2. package/dist/api/SignalClient.js +92 -28
  3. package/dist/api/SignalClient.js.map +1 -1
  4. package/dist/index.d.ts +4 -2
  5. package/dist/index.js +5 -3
  6. package/dist/index.js.map +1 -1
  7. package/dist/options.d.ts +5 -0
  8. package/dist/proto/livekit_models.d.ts +48 -0
  9. package/dist/proto/livekit_models.js +367 -5
  10. package/dist/proto/livekit_models.js.map +1 -1
  11. package/dist/proto/livekit_rtc.d.ts +50 -11
  12. package/dist/proto/livekit_rtc.js +300 -22
  13. package/dist/proto/livekit_rtc.js.map +1 -1
  14. package/dist/room/PCTransport.js +4 -0
  15. package/dist/room/PCTransport.js.map +1 -1
  16. package/dist/room/RTCEngine.d.ts +10 -2
  17. package/dist/room/RTCEngine.js +182 -42
  18. package/dist/room/RTCEngine.js.map +1 -1
  19. package/dist/room/Room.d.ts +15 -0
  20. package/dist/room/Room.js +165 -20
  21. package/dist/room/Room.js.map +1 -1
  22. package/dist/room/events.d.ts +42 -20
  23. package/dist/room/events.js +41 -19
  24. package/dist/room/events.js.map +1 -1
  25. package/dist/room/participant/LocalParticipant.d.ts +25 -4
  26. package/dist/room/participant/LocalParticipant.js +50 -23
  27. package/dist/room/participant/LocalParticipant.js.map +1 -1
  28. package/dist/room/participant/Participant.d.ts +3 -1
  29. package/dist/room/participant/Participant.js +1 -0
  30. package/dist/room/participant/Participant.js.map +1 -1
  31. package/dist/room/participant/ParticipantTrackPermission.d.ts +19 -0
  32. package/dist/room/participant/ParticipantTrackPermission.js +16 -0
  33. package/dist/room/participant/ParticipantTrackPermission.js.map +1 -0
  34. package/dist/room/participant/RemoteParticipant.d.ts +2 -2
  35. package/dist/room/participant/RemoteParticipant.js +11 -16
  36. package/dist/room/participant/RemoteParticipant.js.map +1 -1
  37. package/dist/room/participant/publishUtils.js +1 -1
  38. package/dist/room/participant/publishUtils.js.map +1 -1
  39. package/dist/room/participant/publishUtils.test.js +9 -0
  40. package/dist/room/participant/publishUtils.test.js.map +1 -1
  41. package/dist/room/track/LocalTrack.d.ts +0 -3
  42. package/dist/room/track/LocalTrack.js +1 -6
  43. package/dist/room/track/LocalTrack.js.map +1 -1
  44. package/dist/room/track/LocalTrackPublication.d.ts +5 -1
  45. package/dist/room/track/LocalTrackPublication.js +15 -5
  46. package/dist/room/track/LocalTrackPublication.js.map +1 -1
  47. package/dist/room/track/LocalVideoTrack.d.ts +1 -1
  48. package/dist/room/track/LocalVideoTrack.js +7 -6
  49. package/dist/room/track/LocalVideoTrack.js.map +1 -1
  50. package/dist/room/track/RemoteAudioTrack.d.ts +5 -14
  51. package/dist/room/track/RemoteAudioTrack.js +7 -32
  52. package/dist/room/track/RemoteAudioTrack.js.map +1 -1
  53. package/dist/room/track/RemoteTrack.d.ts +14 -0
  54. package/dist/room/track/RemoteTrack.js +47 -0
  55. package/dist/room/track/RemoteTrack.js.map +1 -0
  56. package/dist/room/track/RemoteTrackPublication.d.ts +10 -2
  57. package/dist/room/track/RemoteTrackPublication.js +51 -12
  58. package/dist/room/track/RemoteTrackPublication.js.map +1 -1
  59. package/dist/room/track/RemoteVideoTrack.d.ts +3 -9
  60. package/dist/room/track/RemoteVideoTrack.js +8 -29
  61. package/dist/room/track/RemoteVideoTrack.js.map +1 -1
  62. package/dist/room/track/Track.d.ts +3 -0
  63. package/dist/room/track/Track.js +14 -5
  64. package/dist/room/track/Track.js.map +1 -1
  65. package/dist/room/track/TrackPublication.d.ts +14 -1
  66. package/dist/room/track/TrackPublication.js +24 -7
  67. package/dist/room/track/TrackPublication.js.map +1 -1
  68. package/dist/room/track/create.js +5 -0
  69. package/dist/room/track/create.js.map +1 -1
  70. package/dist/room/utils.d.ts +2 -0
  71. package/dist/room/utils.js +32 -1
  72. package/dist/room/utils.js.map +1 -1
  73. package/dist/version.d.ts +2 -2
  74. package/dist/version.js +2 -2
  75. package/package.json +5 -3
  76. package/src/api/SignalClient.ts +444 -0
  77. package/src/connect.ts +100 -0
  78. package/src/index.ts +47 -0
  79. package/src/logger.ts +22 -0
  80. package/src/options.ts +152 -0
  81. package/src/proto/livekit_models.ts +1863 -0
  82. package/src/proto/livekit_rtc.ts +3415 -0
  83. package/src/room/DeviceManager.ts +57 -0
  84. package/src/room/PCTransport.ts +86 -0
  85. package/src/room/RTCEngine.ts +582 -0
  86. package/src/room/Room.ts +840 -0
  87. package/src/room/errors.ts +65 -0
  88. package/src/room/events.ts +398 -0
  89. package/src/room/participant/LocalParticipant.ts +685 -0
  90. package/src/room/participant/Participant.ts +214 -0
  91. package/src/room/participant/ParticipantTrackPermission.ts +32 -0
  92. package/src/room/participant/RemoteParticipant.ts +241 -0
  93. package/src/room/participant/publishUtils.test.ts +105 -0
  94. package/src/room/participant/publishUtils.ts +180 -0
  95. package/src/room/stats.ts +130 -0
  96. package/src/room/track/LocalAudioTrack.ts +112 -0
  97. package/src/room/track/LocalTrack.ts +124 -0
  98. package/src/room/track/LocalTrackPublication.ts +66 -0
  99. package/src/room/track/LocalVideoTrack.test.ts +70 -0
  100. package/src/room/track/LocalVideoTrack.ts +416 -0
  101. package/src/room/track/RemoteAudioTrack.ts +58 -0
  102. package/src/room/track/RemoteTrack.ts +59 -0
  103. package/src/room/track/RemoteTrackPublication.ts +198 -0
  104. package/src/room/track/RemoteVideoTrack.ts +213 -0
  105. package/src/room/track/Track.ts +307 -0
  106. package/src/room/track/TrackPublication.ts +120 -0
  107. package/src/room/track/create.ts +120 -0
  108. package/src/room/track/defaults.ts +23 -0
  109. package/src/room/track/options.ts +229 -0
  110. package/src/room/track/types.ts +8 -0
  111. package/src/room/track/utils.test.ts +93 -0
  112. package/src/room/track/utils.ts +76 -0
  113. package/src/room/utils.ts +74 -0
  114. package/src/version.ts +2 -0
  115. package/.github/workflows/publish.yaml +0 -55
  116. package/.github/workflows/test.yaml +0 -36
  117. package/example/index.html +0 -248
  118. package/example/sample.ts +0 -621
  119. package/example/styles.css +0 -144
  120. 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
- }