livekit-client 0.15.0 → 0.15.4

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