livekit-client 0.18.4-RC8 → 0.18.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/dist/api/RequestQueue.d.ts +13 -12
- package/dist/api/RequestQueue.d.ts.map +1 -0
- package/dist/api/SignalClient.d.ts +67 -66
- package/dist/api/SignalClient.d.ts.map +1 -0
- package/dist/connect.d.ts +24 -23
- package/dist/connect.d.ts.map +1 -0
- package/dist/index.d.ts +27 -26
- package/dist/index.d.ts.map +1 -0
- package/dist/livekit-client.esm.mjs +593 -507
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/logger.d.ts +26 -25
- package/dist/logger.d.ts.map +1 -0
- package/dist/options.d.ts +128 -127
- package/dist/options.d.ts.map +1 -0
- package/dist/proto/google/protobuf/timestamp.d.ts +133 -132
- package/dist/proto/google/protobuf/timestamp.d.ts.map +1 -0
- package/dist/proto/livekit_models.d.ts +876 -875
- package/dist/proto/livekit_models.d.ts.map +1 -0
- package/dist/proto/livekit_rtc.d.ts +3904 -3903
- package/dist/proto/livekit_rtc.d.ts.map +1 -0
- package/dist/room/DeviceManager.d.ts +8 -7
- package/dist/room/DeviceManager.d.ts.map +1 -0
- package/dist/room/PCTransport.d.ts +16 -15
- package/dist/room/PCTransport.d.ts.map +1 -0
- package/dist/room/RTCEngine.d.ts +67 -66
- package/dist/room/RTCEngine.d.ts.map +1 -0
- package/dist/room/Room.d.ts +166 -165
- package/dist/room/Room.d.ts.map +1 -0
- package/dist/room/errors.d.ts +29 -28
- package/dist/room/errors.d.ts.map +1 -0
- package/dist/room/events.d.ts +391 -390
- package/dist/room/events.d.ts.map +1 -0
- package/dist/room/participant/LocalParticipant.d.ts +126 -125
- package/dist/room/participant/LocalParticipant.d.ts.map +1 -0
- package/dist/room/participant/Participant.d.ts +94 -93
- package/dist/room/participant/Participant.d.ts.map +1 -0
- package/dist/room/participant/ParticipantTrackPermission.d.ts +26 -25
- package/dist/room/participant/ParticipantTrackPermission.d.ts.map +1 -0
- package/dist/room/participant/RemoteParticipant.d.ts +40 -39
- package/dist/room/participant/RemoteParticipant.d.ts.map +1 -0
- package/dist/room/participant/publishUtils.d.ts +18 -17
- package/dist/room/participant/publishUtils.d.ts.map +1 -0
- package/dist/room/stats.d.ts +66 -65
- package/dist/room/stats.d.ts.map +1 -0
- package/dist/room/track/LocalAudioTrack.d.ts +20 -19
- package/dist/room/track/LocalAudioTrack.d.ts.map +1 -0
- package/dist/room/track/LocalTrack.d.ts +28 -27
- package/dist/room/track/LocalTrack.d.ts.map +1 -0
- package/dist/room/track/LocalTrackPublication.d.ts +38 -37
- package/dist/room/track/LocalTrackPublication.d.ts.map +1 -0
- package/dist/room/track/LocalVideoTrack.d.ts +31 -30
- package/dist/room/track/LocalVideoTrack.d.ts.map +1 -0
- package/dist/room/track/RemoteAudioTrack.d.ts +20 -19
- package/dist/room/track/RemoteAudioTrack.d.ts.map +1 -0
- package/dist/room/track/RemoteTrack.d.ts +16 -15
- package/dist/room/track/RemoteTrack.d.ts.map +1 -0
- package/dist/room/track/RemoteTrackPublication.d.ts +51 -50
- package/dist/room/track/RemoteTrackPublication.d.ts.map +1 -0
- package/dist/room/track/RemoteVideoTrack.d.ts +30 -27
- package/dist/room/track/RemoteVideoTrack.d.ts.map +1 -0
- package/dist/room/track/Track.d.ts +105 -100
- package/dist/room/track/Track.d.ts.map +1 -0
- package/dist/room/track/TrackPublication.d.ts +50 -49
- package/dist/room/track/TrackPublication.d.ts.map +1 -0
- package/dist/room/track/create.d.ts +24 -23
- package/dist/room/track/create.d.ts.map +1 -0
- package/dist/room/track/defaults.d.ts +5 -4
- package/dist/room/track/defaults.d.ts.map +1 -0
- package/dist/room/track/options.d.ts +232 -222
- package/dist/room/track/options.d.ts.map +1 -0
- package/dist/room/track/types.d.ts +19 -18
- package/dist/room/track/types.d.ts.map +1 -0
- package/dist/room/track/utils.d.ts +14 -13
- package/dist/room/track/utils.d.ts.map +1 -0
- package/dist/room/utils.d.ts +17 -16
- package/dist/room/utils.d.ts.map +1 -0
- package/dist/test/mocks.d.ts +12 -11
- package/dist/test/mocks.d.ts.map +1 -0
- package/dist/version.d.ts +3 -2
- package/dist/version.d.ts.map +1 -0
- package/package.json +6 -6
- package/src/api/RequestQueue.ts +53 -0
- package/src/api/SignalClient.ts +497 -0
- package/src/connect.ts +98 -0
- package/src/index.ts +49 -0
- package/src/logger.ts +56 -0
- package/src/options.ts +156 -0
- package/src/proto/google/protobuf/timestamp.ts +216 -0
- package/src/proto/livekit_models.ts +2456 -0
- package/src/proto/livekit_rtc.ts +2859 -0
- package/src/room/DeviceManager.ts +80 -0
- package/src/room/PCTransport.ts +88 -0
- package/src/room/RTCEngine.ts +695 -0
- package/src/room/Room.ts +970 -0
- package/src/room/errors.ts +65 -0
- package/src/room/events.ts +438 -0
- package/src/room/participant/LocalParticipant.ts +779 -0
- package/src/room/participant/Participant.ts +287 -0
- package/src/room/participant/ParticipantTrackPermission.ts +42 -0
- package/src/room/participant/RemoteParticipant.ts +263 -0
- package/src/room/participant/publishUtils.test.ts +144 -0
- package/src/room/participant/publishUtils.ts +258 -0
- package/src/room/stats.ts +134 -0
- package/src/room/track/LocalAudioTrack.ts +134 -0
- package/src/room/track/LocalTrack.ts +229 -0
- package/src/room/track/LocalTrackPublication.ts +87 -0
- package/src/room/track/LocalVideoTrack.test.ts +72 -0
- package/src/room/track/LocalVideoTrack.ts +295 -0
- package/src/room/track/RemoteAudioTrack.ts +86 -0
- package/src/room/track/RemoteTrack.ts +62 -0
- package/src/room/track/RemoteTrackPublication.ts +207 -0
- package/src/room/track/RemoteVideoTrack.ts +253 -0
- package/src/room/track/Track.ts +365 -0
- package/src/room/track/TrackPublication.ts +120 -0
- package/src/room/track/create.ts +122 -0
- package/src/room/track/defaults.ts +26 -0
- package/src/room/track/options.ts +292 -0
- package/src/room/track/types.ts +20 -0
- package/src/room/track/utils.test.ts +110 -0
- package/src/room/track/utils.ts +113 -0
- package/src/room/utils.ts +115 -0
- package/src/test/mocks.ts +17 -0
- package/src/version.ts +2 -0
- package/CHANGELOG.md +0 -5
@@ -0,0 +1,365 @@
|
|
1
|
+
import { EventEmitter } from 'events';
|
2
|
+
import type TypedEventEmitter from 'typed-emitter';
|
3
|
+
import { TrackSource, TrackType } from '../../proto/livekit_models';
|
4
|
+
import { StreamState as ProtoStreamState } from '../../proto/livekit_rtc';
|
5
|
+
import { TrackEvent } from '../events';
|
6
|
+
import { isFireFox, isSafari, isWeb } from '../utils';
|
7
|
+
|
8
|
+
// keep old audio elements when detached, we would re-use them since on iOS
|
9
|
+
// Safari tracks which audio elements have been "blessed" by the user.
|
10
|
+
const recycledElements: Array<HTMLAudioElement> = [];
|
11
|
+
|
12
|
+
export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEventCallbacks>) {
|
13
|
+
kind: Track.Kind;
|
14
|
+
|
15
|
+
attachedElements: HTMLMediaElement[] = [];
|
16
|
+
|
17
|
+
isMuted: boolean = false;
|
18
|
+
|
19
|
+
source: Track.Source;
|
20
|
+
|
21
|
+
/**
|
22
|
+
* sid is set after track is published to server, or if it's a remote track
|
23
|
+
*/
|
24
|
+
sid?: Track.SID;
|
25
|
+
|
26
|
+
/**
|
27
|
+
* @internal
|
28
|
+
*/
|
29
|
+
mediaStream?: MediaStream;
|
30
|
+
|
31
|
+
protected _mediaStreamTrack: MediaStreamTrack;
|
32
|
+
|
33
|
+
protected isInBackground: boolean;
|
34
|
+
|
35
|
+
protected _currentBitrate: number = 0;
|
36
|
+
|
37
|
+
protected constructor(mediaTrack: MediaStreamTrack, kind: Track.Kind) {
|
38
|
+
super();
|
39
|
+
this.kind = kind;
|
40
|
+
this._mediaStreamTrack = mediaTrack;
|
41
|
+
this.source = Track.Source.Unknown;
|
42
|
+
if (isWeb()) {
|
43
|
+
this.isInBackground = document.visibilityState === 'hidden';
|
44
|
+
document.addEventListener('visibilitychange', this.appVisibilityChangedListener);
|
45
|
+
} else {
|
46
|
+
this.isInBackground = false;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
/** current receive bits per second */
|
51
|
+
get currentBitrate(): number {
|
52
|
+
return this._currentBitrate;
|
53
|
+
}
|
54
|
+
|
55
|
+
get mediaStreamTrack() {
|
56
|
+
return this._mediaStreamTrack;
|
57
|
+
}
|
58
|
+
|
59
|
+
/**
|
60
|
+
* creates a new HTMLAudioElement or HTMLVideoElement, attaches to it, and returns it
|
61
|
+
*/
|
62
|
+
attach(): HTMLMediaElement;
|
63
|
+
|
64
|
+
/**
|
65
|
+
* attaches track to an existing HTMLAudioElement or HTMLVideoElement
|
66
|
+
*/
|
67
|
+
attach(element: HTMLMediaElement): HTMLMediaElement;
|
68
|
+
attach(element?: HTMLMediaElement): HTMLMediaElement {
|
69
|
+
let elementType = 'audio';
|
70
|
+
if (this.kind === Track.Kind.Video) {
|
71
|
+
elementType = 'video';
|
72
|
+
}
|
73
|
+
if (!element) {
|
74
|
+
if (elementType === 'audio') {
|
75
|
+
recycledElements.forEach((e) => {
|
76
|
+
if (e.parentElement === null && !element) {
|
77
|
+
element = e;
|
78
|
+
}
|
79
|
+
});
|
80
|
+
if (element) {
|
81
|
+
// remove it from pool
|
82
|
+
recycledElements.splice(recycledElements.indexOf(element), 1);
|
83
|
+
}
|
84
|
+
}
|
85
|
+
if (!element) {
|
86
|
+
element = <HTMLMediaElement>document.createElement(elementType);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
if (!this.attachedElements.includes(element)) {
|
91
|
+
this.attachedElements.push(element);
|
92
|
+
}
|
93
|
+
|
94
|
+
// even if we believe it's already attached to the element, it's possible
|
95
|
+
// the element's srcObject was set to something else out of band.
|
96
|
+
// we'll want to re-attach it in that case
|
97
|
+
attachToElement(this._mediaStreamTrack, element);
|
98
|
+
|
99
|
+
if (element instanceof HTMLAudioElement) {
|
100
|
+
// manually play audio to detect audio playback status
|
101
|
+
element
|
102
|
+
.play()
|
103
|
+
.then(() => {
|
104
|
+
this.emit(TrackEvent.AudioPlaybackStarted);
|
105
|
+
})
|
106
|
+
.catch((e) => {
|
107
|
+
this.emit(TrackEvent.AudioPlaybackFailed, e);
|
108
|
+
});
|
109
|
+
}
|
110
|
+
|
111
|
+
this.emit(TrackEvent.ElementAttached, element);
|
112
|
+
return element;
|
113
|
+
}
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Detaches from all attached elements
|
117
|
+
*/
|
118
|
+
detach(): HTMLMediaElement[];
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Detach from a single element
|
122
|
+
* @param element
|
123
|
+
*/
|
124
|
+
detach(element: HTMLMediaElement): HTMLMediaElement;
|
125
|
+
detach(element?: HTMLMediaElement): HTMLMediaElement | HTMLMediaElement[] {
|
126
|
+
// detach from a single element
|
127
|
+
if (element) {
|
128
|
+
detachTrack(this._mediaStreamTrack, element);
|
129
|
+
const idx = this.attachedElements.indexOf(element);
|
130
|
+
if (idx >= 0) {
|
131
|
+
this.attachedElements.splice(idx, 1);
|
132
|
+
this.recycleElement(element);
|
133
|
+
this.emit(TrackEvent.ElementDetached, element);
|
134
|
+
}
|
135
|
+
return element;
|
136
|
+
}
|
137
|
+
|
138
|
+
const detached: HTMLMediaElement[] = [];
|
139
|
+
this.attachedElements.forEach((elm) => {
|
140
|
+
detachTrack(this._mediaStreamTrack, elm);
|
141
|
+
detached.push(elm);
|
142
|
+
this.recycleElement(elm);
|
143
|
+
this.emit(TrackEvent.ElementDetached, elm);
|
144
|
+
});
|
145
|
+
|
146
|
+
// remove all tracks
|
147
|
+
this.attachedElements = [];
|
148
|
+
return detached;
|
149
|
+
}
|
150
|
+
|
151
|
+
stop() {
|
152
|
+
this._mediaStreamTrack.stop();
|
153
|
+
if (isWeb()) {
|
154
|
+
document.removeEventListener('visibilitychange', this.appVisibilityChangedListener);
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
protected enable() {
|
159
|
+
this._mediaStreamTrack.enabled = true;
|
160
|
+
}
|
161
|
+
|
162
|
+
protected disable() {
|
163
|
+
this._mediaStreamTrack.enabled = false;
|
164
|
+
}
|
165
|
+
|
166
|
+
private recycleElement(element: HTMLMediaElement) {
|
167
|
+
if (element instanceof HTMLAudioElement) {
|
168
|
+
// we only need to re-use a single element
|
169
|
+
let shouldCache = true;
|
170
|
+
element.pause();
|
171
|
+
recycledElements.forEach((e) => {
|
172
|
+
if (!e.parentElement) {
|
173
|
+
shouldCache = false;
|
174
|
+
}
|
175
|
+
});
|
176
|
+
if (shouldCache) {
|
177
|
+
recycledElements.push(element);
|
178
|
+
}
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
appVisibilityChangedListener = () => {
|
183
|
+
this.handleAppVisibilityChanged();
|
184
|
+
};
|
185
|
+
|
186
|
+
protected async handleAppVisibilityChanged() {
|
187
|
+
this.isInBackground = document.visibilityState === 'hidden';
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
/** @internal */
|
192
|
+
export function attachToElement(track: MediaStreamTrack, element: HTMLMediaElement) {
|
193
|
+
let mediaStream: MediaStream;
|
194
|
+
if (element.srcObject instanceof MediaStream) {
|
195
|
+
mediaStream = element.srcObject;
|
196
|
+
} else {
|
197
|
+
mediaStream = new MediaStream();
|
198
|
+
}
|
199
|
+
|
200
|
+
// check if track matches existing track
|
201
|
+
let existingTracks: MediaStreamTrack[];
|
202
|
+
if (track.kind === 'audio') {
|
203
|
+
existingTracks = mediaStream.getAudioTracks();
|
204
|
+
} else {
|
205
|
+
existingTracks = mediaStream.getVideoTracks();
|
206
|
+
}
|
207
|
+
if (!existingTracks.includes(track)) {
|
208
|
+
existingTracks.forEach((et) => {
|
209
|
+
mediaStream.removeTrack(et);
|
210
|
+
});
|
211
|
+
mediaStream.addTrack(track);
|
212
|
+
}
|
213
|
+
|
214
|
+
// avoid flicker
|
215
|
+
if (element.srcObject !== mediaStream) {
|
216
|
+
element.srcObject = mediaStream;
|
217
|
+
if ((isSafari() || isFireFox()) && element instanceof HTMLVideoElement) {
|
218
|
+
// Firefox also has a timing issue where video doesn't actually get attached unless
|
219
|
+
// performed out-of-band
|
220
|
+
// Safari 15 has a bug where in certain layouts, video element renders
|
221
|
+
// black until the page is resized or other changes take place.
|
222
|
+
// Resetting the src triggers it to render.
|
223
|
+
// https://developer.apple.com/forums/thread/690523
|
224
|
+
setTimeout(() => {
|
225
|
+
element.srcObject = mediaStream;
|
226
|
+
// Safari 15 sometimes fails to start a video
|
227
|
+
// when the window is backgrounded before the first frame is drawn
|
228
|
+
// manually calling play here seems to fix that
|
229
|
+
element.play().catch(() => {
|
230
|
+
/* do nothing */
|
231
|
+
});
|
232
|
+
}, 0);
|
233
|
+
}
|
234
|
+
}
|
235
|
+
element.autoplay = true;
|
236
|
+
if (element instanceof HTMLVideoElement) {
|
237
|
+
element.playsInline = true;
|
238
|
+
}
|
239
|
+
}
|
240
|
+
|
241
|
+
/** @internal */
|
242
|
+
export function detachTrack(track: MediaStreamTrack, element: HTMLMediaElement) {
|
243
|
+
if (element.srcObject instanceof MediaStream) {
|
244
|
+
const mediaStream = element.srcObject;
|
245
|
+
mediaStream.removeTrack(track);
|
246
|
+
if (mediaStream.getTracks().length > 0) {
|
247
|
+
element.srcObject = mediaStream;
|
248
|
+
} else {
|
249
|
+
element.srcObject = null;
|
250
|
+
}
|
251
|
+
}
|
252
|
+
}
|
253
|
+
|
254
|
+
export namespace Track {
|
255
|
+
export enum Kind {
|
256
|
+
Audio = 'audio',
|
257
|
+
Video = 'video',
|
258
|
+
Unknown = 'unknown',
|
259
|
+
}
|
260
|
+
export type SID = string;
|
261
|
+
export enum Source {
|
262
|
+
Camera = 'camera',
|
263
|
+
Microphone = 'microphone',
|
264
|
+
ScreenShare = 'screen_share',
|
265
|
+
ScreenShareAudio = 'screen_share_audio',
|
266
|
+
Unknown = 'unknown',
|
267
|
+
}
|
268
|
+
|
269
|
+
export enum StreamState {
|
270
|
+
Active = 'active',
|
271
|
+
Paused = 'paused',
|
272
|
+
Unknown = 'unknown',
|
273
|
+
}
|
274
|
+
|
275
|
+
export interface Dimensions {
|
276
|
+
width: number;
|
277
|
+
height: number;
|
278
|
+
}
|
279
|
+
|
280
|
+
/** @internal */
|
281
|
+
export function kindToProto(k: Kind): TrackType {
|
282
|
+
switch (k) {
|
283
|
+
case Kind.Audio:
|
284
|
+
return TrackType.AUDIO;
|
285
|
+
case Kind.Video:
|
286
|
+
return TrackType.VIDEO;
|
287
|
+
default:
|
288
|
+
return TrackType.UNRECOGNIZED;
|
289
|
+
}
|
290
|
+
}
|
291
|
+
|
292
|
+
/** @internal */
|
293
|
+
export function kindFromProto(t: TrackType): Kind | undefined {
|
294
|
+
switch (t) {
|
295
|
+
case TrackType.AUDIO:
|
296
|
+
return Kind.Audio;
|
297
|
+
case TrackType.VIDEO:
|
298
|
+
return Kind.Video;
|
299
|
+
default:
|
300
|
+
return Kind.Unknown;
|
301
|
+
}
|
302
|
+
}
|
303
|
+
|
304
|
+
/** @internal */
|
305
|
+
export function sourceToProto(s: Source): TrackSource {
|
306
|
+
switch (s) {
|
307
|
+
case Source.Camera:
|
308
|
+
return TrackSource.CAMERA;
|
309
|
+
case Source.Microphone:
|
310
|
+
return TrackSource.MICROPHONE;
|
311
|
+
case Source.ScreenShare:
|
312
|
+
return TrackSource.SCREEN_SHARE;
|
313
|
+
case Source.ScreenShareAudio:
|
314
|
+
return TrackSource.SCREEN_SHARE_AUDIO;
|
315
|
+
default:
|
316
|
+
return TrackSource.UNRECOGNIZED;
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
/** @internal */
|
321
|
+
export function sourceFromProto(s: TrackSource): Source {
|
322
|
+
switch (s) {
|
323
|
+
case TrackSource.CAMERA:
|
324
|
+
return Source.Camera;
|
325
|
+
case TrackSource.MICROPHONE:
|
326
|
+
return Source.Microphone;
|
327
|
+
case TrackSource.SCREEN_SHARE:
|
328
|
+
return Source.ScreenShare;
|
329
|
+
case TrackSource.SCREEN_SHARE_AUDIO:
|
330
|
+
return Source.ScreenShareAudio;
|
331
|
+
default:
|
332
|
+
return Source.Unknown;
|
333
|
+
}
|
334
|
+
}
|
335
|
+
|
336
|
+
/** @internal */
|
337
|
+
export function streamStateFromProto(s: ProtoStreamState): StreamState {
|
338
|
+
switch (s) {
|
339
|
+
case ProtoStreamState.ACTIVE:
|
340
|
+
return StreamState.Active;
|
341
|
+
case ProtoStreamState.PAUSED:
|
342
|
+
return StreamState.Paused;
|
343
|
+
default:
|
344
|
+
return StreamState.Unknown;
|
345
|
+
}
|
346
|
+
}
|
347
|
+
}
|
348
|
+
|
349
|
+
export type TrackEventCallbacks = {
|
350
|
+
message: () => void;
|
351
|
+
muted: (track?: any) => void;
|
352
|
+
unmuted: (track?: any) => void;
|
353
|
+
ended: (track?: any) => void;
|
354
|
+
updateSettings: () => void;
|
355
|
+
updateSubscription: () => void;
|
356
|
+
audioPlaybackStarted: () => void;
|
357
|
+
audioPlaybackFailed: (error: Error) => void;
|
358
|
+
audioSilenceDetected: () => void;
|
359
|
+
visibilityChanged: (visible: boolean, track?: any) => void;
|
360
|
+
videoDimensionsChanged: (dimensions: Track.Dimensions, track?: any) => void;
|
361
|
+
elementAttached: (element: HTMLMediaElement) => void;
|
362
|
+
elementDetached: (element: HTMLMediaElement) => void;
|
363
|
+
upstreamPaused: (track: any) => void;
|
364
|
+
upstreamResumed: (track: any) => void;
|
365
|
+
};
|
@@ -0,0 +1,120 @@
|
|
1
|
+
import { EventEmitter } from 'events';
|
2
|
+
import { TrackInfo } from '../../proto/livekit_models';
|
3
|
+
import { TrackEvent } from '../events';
|
4
|
+
import LocalAudioTrack from './LocalAudioTrack';
|
5
|
+
import LocalVideoTrack from './LocalVideoTrack';
|
6
|
+
import RemoteAudioTrack from './RemoteAudioTrack';
|
7
|
+
import RemoteVideoTrack from './RemoteVideoTrack';
|
8
|
+
import { Track } from './Track';
|
9
|
+
|
10
|
+
export class TrackPublication extends EventEmitter {
|
11
|
+
kind: Track.Kind;
|
12
|
+
|
13
|
+
trackName: string;
|
14
|
+
|
15
|
+
trackSid: Track.SID;
|
16
|
+
|
17
|
+
track?: Track;
|
18
|
+
|
19
|
+
source: Track.Source;
|
20
|
+
|
21
|
+
/** MimeType of the published track */
|
22
|
+
mimeType?: string;
|
23
|
+
|
24
|
+
/** dimension of the original published stream, video-only */
|
25
|
+
dimensions?: Track.Dimensions;
|
26
|
+
|
27
|
+
/** true if track was simulcasted to server, video-only */
|
28
|
+
simulcasted?: boolean;
|
29
|
+
|
30
|
+
/** @internal */
|
31
|
+
trackInfo?: TrackInfo;
|
32
|
+
|
33
|
+
protected metadataMuted: boolean = false;
|
34
|
+
|
35
|
+
constructor(kind: Track.Kind, id: string, name: string) {
|
36
|
+
super();
|
37
|
+
this.kind = kind;
|
38
|
+
this.trackSid = id;
|
39
|
+
this.trackName = name;
|
40
|
+
this.source = Track.Source.Unknown;
|
41
|
+
}
|
42
|
+
|
43
|
+
/** @internal */
|
44
|
+
setTrack(track?: Track) {
|
45
|
+
if (this.track) {
|
46
|
+
this.track.off(TrackEvent.Muted, this.handleMuted);
|
47
|
+
this.track.off(TrackEvent.Unmuted, this.handleUnmuted);
|
48
|
+
}
|
49
|
+
|
50
|
+
this.track = track;
|
51
|
+
|
52
|
+
if (track) {
|
53
|
+
// forward events
|
54
|
+
track.on(TrackEvent.Muted, this.handleMuted);
|
55
|
+
track.on(TrackEvent.Unmuted, this.handleUnmuted);
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
get isMuted(): boolean {
|
60
|
+
return this.metadataMuted;
|
61
|
+
}
|
62
|
+
|
63
|
+
get isEnabled(): boolean {
|
64
|
+
return true;
|
65
|
+
}
|
66
|
+
|
67
|
+
get isSubscribed(): boolean {
|
68
|
+
return this.track !== undefined;
|
69
|
+
}
|
70
|
+
|
71
|
+
/**
|
72
|
+
* an [AudioTrack] if this publication holds an audio track
|
73
|
+
*/
|
74
|
+
get audioTrack(): LocalAudioTrack | RemoteAudioTrack | undefined {
|
75
|
+
if (this.track instanceof LocalAudioTrack || this.track instanceof RemoteAudioTrack) {
|
76
|
+
return this.track;
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
/**
|
81
|
+
* an [VideoTrack] if this publication holds a video track
|
82
|
+
*/
|
83
|
+
get videoTrack(): LocalVideoTrack | RemoteVideoTrack | undefined {
|
84
|
+
if (this.track instanceof LocalVideoTrack || this.track instanceof RemoteVideoTrack) {
|
85
|
+
return this.track;
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
handleMuted = () => {
|
90
|
+
this.emit(TrackEvent.Muted);
|
91
|
+
};
|
92
|
+
|
93
|
+
handleUnmuted = () => {
|
94
|
+
this.emit(TrackEvent.Unmuted);
|
95
|
+
};
|
96
|
+
|
97
|
+
/** @internal */
|
98
|
+
updateInfo(info: TrackInfo) {
|
99
|
+
this.trackSid = info.sid;
|
100
|
+
this.trackName = info.name;
|
101
|
+
this.source = Track.sourceFromProto(info.source);
|
102
|
+
this.mimeType = info.mimeType;
|
103
|
+
if (this.kind === Track.Kind.Video && info.width > 0) {
|
104
|
+
this.dimensions = {
|
105
|
+
width: info.width,
|
106
|
+
height: info.height,
|
107
|
+
};
|
108
|
+
this.simulcasted = info.simulcast;
|
109
|
+
}
|
110
|
+
this.trackInfo = info;
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
export namespace TrackPublication {
|
115
|
+
export enum SubscriptionStatus {
|
116
|
+
Subscribed = 'subscribed',
|
117
|
+
NotAllowed = 'not_allowed',
|
118
|
+
Unsubscribed = 'unsubscribed',
|
119
|
+
}
|
120
|
+
}
|
@@ -0,0 +1,122 @@
|
|
1
|
+
import { TrackInvalidError } from '../errors';
|
2
|
+
import { mediaTrackToLocalTrack } from '../participant/publishUtils';
|
3
|
+
import { audioDefaults, videoDefaults } from './defaults';
|
4
|
+
import LocalAudioTrack from './LocalAudioTrack';
|
5
|
+
import LocalTrack from './LocalTrack';
|
6
|
+
import LocalVideoTrack from './LocalVideoTrack';
|
7
|
+
import {
|
8
|
+
AudioCaptureOptions,
|
9
|
+
CreateLocalTracksOptions,
|
10
|
+
ScreenShareCaptureOptions,
|
11
|
+
VideoCaptureOptions,
|
12
|
+
VideoPresets,
|
13
|
+
} from './options';
|
14
|
+
import { Track } from './Track';
|
15
|
+
import { constraintsForOptions, mergeDefaultOptions } from './utils';
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Creates a local video and audio track at the same time. When acquiring both
|
19
|
+
* audio and video tracks together, it'll display a single permission prompt to
|
20
|
+
* the user instead of two separate ones.
|
21
|
+
* @param options
|
22
|
+
*/
|
23
|
+
export async function createLocalTracks(
|
24
|
+
options?: CreateLocalTracksOptions,
|
25
|
+
): Promise<Array<LocalTrack>> {
|
26
|
+
// set default options to true
|
27
|
+
options ??= {};
|
28
|
+
options.audio ??= true;
|
29
|
+
options.video ??= true;
|
30
|
+
|
31
|
+
const opts = mergeDefaultOptions(options, audioDefaults, videoDefaults);
|
32
|
+
const constraints = constraintsForOptions(opts);
|
33
|
+
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
34
|
+
return stream.getTracks().map((mediaStreamTrack) => {
|
35
|
+
const isAudio = mediaStreamTrack.kind === 'audio';
|
36
|
+
let trackOptions = isAudio ? options!.audio : options!.video;
|
37
|
+
if (typeof trackOptions === 'boolean' || !trackOptions) {
|
38
|
+
trackOptions = {};
|
39
|
+
}
|
40
|
+
let trackConstraints: MediaTrackConstraints | undefined;
|
41
|
+
const conOrBool = isAudio ? constraints.audio : constraints.video;
|
42
|
+
if (typeof conOrBool !== 'boolean') {
|
43
|
+
trackConstraints = conOrBool;
|
44
|
+
}
|
45
|
+
const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints);
|
46
|
+
if (track.kind === Track.Kind.Video) {
|
47
|
+
track.source = Track.Source.Camera;
|
48
|
+
} else if (track.kind === Track.Kind.Audio) {
|
49
|
+
track.source = Track.Source.Microphone;
|
50
|
+
}
|
51
|
+
track.mediaStream = stream;
|
52
|
+
return track;
|
53
|
+
});
|
54
|
+
}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Creates a [[LocalVideoTrack]] with getUserMedia()
|
58
|
+
* @param options
|
59
|
+
*/
|
60
|
+
export async function createLocalVideoTrack(
|
61
|
+
options?: VideoCaptureOptions,
|
62
|
+
): Promise<LocalVideoTrack> {
|
63
|
+
const tracks = await createLocalTracks({
|
64
|
+
audio: false,
|
65
|
+
video: options,
|
66
|
+
});
|
67
|
+
return <LocalVideoTrack>tracks[0];
|
68
|
+
}
|
69
|
+
|
70
|
+
export async function createLocalAudioTrack(
|
71
|
+
options?: AudioCaptureOptions,
|
72
|
+
): Promise<LocalAudioTrack> {
|
73
|
+
const tracks = await createLocalTracks({
|
74
|
+
audio: options,
|
75
|
+
video: false,
|
76
|
+
});
|
77
|
+
return <LocalAudioTrack>tracks[0];
|
78
|
+
}
|
79
|
+
|
80
|
+
/**
|
81
|
+
* Creates a screen capture tracks with getDisplayMedia().
|
82
|
+
* A LocalVideoTrack is always created and returned.
|
83
|
+
* If { audio: true }, and the browser supports audio capture, a LocalAudioTrack is also created.
|
84
|
+
*/
|
85
|
+
export async function createLocalScreenTracks(
|
86
|
+
options?: ScreenShareCaptureOptions,
|
87
|
+
): Promise<Array<LocalTrack>> {
|
88
|
+
if (options === undefined) {
|
89
|
+
options = {};
|
90
|
+
}
|
91
|
+
if (options.resolution === undefined) {
|
92
|
+
options.resolution = VideoPresets.fhd.resolution;
|
93
|
+
}
|
94
|
+
|
95
|
+
let videoConstraints: MediaTrackConstraints | boolean = true;
|
96
|
+
if (options.resolution) {
|
97
|
+
videoConstraints = {
|
98
|
+
width: options.resolution.width,
|
99
|
+
height: options.resolution.height,
|
100
|
+
};
|
101
|
+
}
|
102
|
+
// typescript definition is missing getDisplayMedia: https://github.com/microsoft/TypeScript/issues/33232
|
103
|
+
// @ts-ignore
|
104
|
+
const stream: MediaStream = await navigator.mediaDevices.getDisplayMedia({
|
105
|
+
audio: options.audio ?? false,
|
106
|
+
video: videoConstraints,
|
107
|
+
});
|
108
|
+
|
109
|
+
const tracks = stream.getVideoTracks();
|
110
|
+
if (tracks.length === 0) {
|
111
|
+
throw new TrackInvalidError('no video track found');
|
112
|
+
}
|
113
|
+
const screenVideo = new LocalVideoTrack(tracks[0]);
|
114
|
+
screenVideo.source = Track.Source.ScreenShare;
|
115
|
+
const localTracks: Array<LocalTrack> = [screenVideo];
|
116
|
+
if (stream.getAudioTracks().length > 0) {
|
117
|
+
const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0]);
|
118
|
+
screenAudio.source = Track.Source.ScreenShareAudio;
|
119
|
+
localTracks.push(screenAudio);
|
120
|
+
}
|
121
|
+
return localTracks;
|
122
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import {
|
2
|
+
AudioCaptureOptions,
|
3
|
+
AudioPresets,
|
4
|
+
ScreenSharePresets,
|
5
|
+
TrackPublishDefaults,
|
6
|
+
VideoCaptureOptions,
|
7
|
+
VideoPresets,
|
8
|
+
} from './options';
|
9
|
+
|
10
|
+
export const publishDefaults: TrackPublishDefaults = {
|
11
|
+
audioBitrate: AudioPresets.speech.maxBitrate,
|
12
|
+
dtx: true,
|
13
|
+
simulcast: true,
|
14
|
+
screenShareEncoding: ScreenSharePresets.h1080fps15.encoding,
|
15
|
+
stopMicTrackOnMute: false,
|
16
|
+
};
|
17
|
+
|
18
|
+
export const audioDefaults: AudioCaptureOptions = {
|
19
|
+
autoGainControl: true,
|
20
|
+
echoCancellation: true,
|
21
|
+
noiseSuppression: true,
|
22
|
+
};
|
23
|
+
|
24
|
+
export const videoDefaults: VideoCaptureOptions = {
|
25
|
+
resolution: VideoPresets.h720.resolution,
|
26
|
+
};
|