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.
- package/.gitmodules +3 -0
- package/README.md +21 -4
- package/dist/api/SignalClient.d.ts +11 -2
- package/dist/api/SignalClient.js +92 -25
- package/dist/api/SignalClient.js.map +1 -1
- package/dist/connect.js +3 -0
- package/dist/connect.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/logger.js +1 -0
- package/dist/logger.js.map +1 -1
- package/dist/options.d.ts +28 -14
- package/dist/proto/livekit_models.d.ts +48 -0
- package/dist/proto/livekit_models.js +367 -5
- package/dist/proto/livekit_models.js.map +1 -1
- package/dist/proto/livekit_rtc.d.ts +100 -1
- package/dist/proto/livekit_rtc.js +745 -3
- 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 +4 -0
- package/dist/room/RTCEngine.js +73 -34
- package/dist/room/RTCEngine.js.map +1 -1
- package/dist/room/Room.d.ts +15 -0
- package/dist/room/Room.js +172 -59
- package/dist/room/Room.js.map +1 -1
- package/dist/room/events.d.ts +60 -24
- package/dist/room/events.js +58 -22
- package/dist/room/events.js.map +1 -1
- package/dist/room/participant/LocalParticipant.d.ts +26 -2
- package/dist/room/participant/LocalParticipant.js +69 -21
- package/dist/room/participant/LocalParticipant.js.map +1 -1
- package/dist/room/participant/Participant.d.ts +3 -1
- package/dist/room/participant/Participant.js +1 -0
- package/dist/room/participant/Participant.js.map +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 +9 -15
- package/dist/room/participant/RemoteParticipant.js.map +1 -1
- package/dist/room/participant/publishUtils.d.ts +1 -1
- package/dist/room/participant/publishUtils.js +4 -4
- package/dist/room/participant/publishUtils.js.map +1 -1
- package/dist/room/participant/publishUtils.test.js +10 -1
- package/dist/room/participant/publishUtils.test.js.map +1 -1
- package/dist/room/stats.d.ts +21 -6
- package/dist/room/stats.js +22 -1
- package/dist/room/stats.js.map +1 -1
- package/dist/room/track/LocalAudioTrack.d.ts +5 -1
- package/dist/room/track/LocalAudioTrack.js +45 -1
- package/dist/room/track/LocalAudioTrack.js.map +1 -1
- package/dist/room/track/LocalTrack.js +1 -1
- package/dist/room/track/LocalTrack.js.map +1 -1
- package/dist/room/track/LocalTrackPublication.d.ts +3 -1
- package/dist/room/track/LocalTrackPublication.js +15 -5
- package/dist/room/track/LocalTrackPublication.js.map +1 -1
- package/dist/room/track/LocalVideoTrack.d.ts +8 -1
- package/dist/room/track/LocalVideoTrack.js +117 -52
- package/dist/room/track/LocalVideoTrack.js.map +1 -1
- package/dist/room/track/RemoteAudioTrack.d.ts +6 -8
- package/dist/room/track/RemoteAudioTrack.js +55 -19
- 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 +49 -16
- package/dist/room/track/RemoteTrackPublication.js.map +1 -1
- package/dist/room/track/RemoteVideoTrack.d.ts +7 -7
- package/dist/room/track/RemoteVideoTrack.js +66 -22
- package/dist/room/track/RemoteVideoTrack.js.map +1 -1
- package/dist/room/track/Track.d.ts +12 -0
- package/dist/room/track/Track.js +33 -0
- package/dist/room/track/Track.js.map +1 -1
- package/dist/room/track/TrackPublication.d.ts +14 -1
- package/dist/room/track/TrackPublication.js +24 -7
- package/dist/room/track/TrackPublication.js.map +1 -1
- package/dist/room/track/create.d.ts +23 -0
- package/dist/room/track/create.js +130 -0
- package/dist/room/track/create.js.map +1 -0
- package/dist/room/track/defaults.d.ts +4 -0
- package/dist/room/track/defaults.js +21 -0
- package/dist/room/track/defaults.js.map +1 -0
- package/dist/room/utils.d.ts +3 -1
- package/dist/room/utils.js +36 -6
- package/dist/room/utils.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -3
- package/src/api/SignalClient.ts +434 -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 +3401 -0
- package/src/room/DeviceManager.ts +57 -0
- package/src/room/PCTransport.ts +86 -0
- package/src/room/RTCEngine.ts +484 -0
- package/src/room/Room.ts +785 -0
- package/src/room/errors.ts +65 -0
- package/src/room/events.ts +396 -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 +238 -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 +63 -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 +192 -0
- package/src/room/track/RemoteVideoTrack.ts +213 -0
- package/src/room/track/Track.ts +301 -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 +74 -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 -237
- package/example/sample.ts +0 -575
- package/example/styles.css +0 -144
- 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
|
-
}
|
package/example/styles.css
DELETED
@@ -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
|
-
}
|