livekit-client 1.1.2 → 1.1.5

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.
@@ -23,9 +23,7 @@ export default class RemoteParticipant extends Participant {
23
23
 
24
24
  /** @internal */
25
25
  static fromParticipantInfo(signalClient: SignalClient, pi: ParticipantInfo): RemoteParticipant {
26
- const rp = new RemoteParticipant(signalClient, pi.sid, pi.identity);
27
- rp.updateInfo(pi);
28
- return rp;
26
+ return new RemoteParticipant(signalClient, pi.sid, pi.identity);
29
27
  }
30
28
 
31
29
  /** @internal */
@@ -182,8 +180,6 @@ export default class RemoteParticipant extends Participant {
182
180
 
183
181
  /** @internal */
184
182
  updateInfo(info: ParticipantInfo) {
185
- const alreadyHasMetadata = this.hasMetadata;
186
-
187
183
  super.updateInfo(info);
188
184
 
189
185
  // we are getting a list of all available tracks, reconcile in here
@@ -212,12 +208,10 @@ export default class RemoteParticipant extends Participant {
212
208
  validTracks.set(ti.sid, publication);
213
209
  });
214
210
 
215
- // send new tracks
216
- if (alreadyHasMetadata) {
217
- newTracks.forEach((publication) => {
218
- this.emit(ParticipantEvent.TrackPublished, publication);
219
- });
220
- }
211
+ // always emit events for new publications, Room will not forward them unless it's ready
212
+ newTracks.forEach((publication) => {
213
+ this.emit(ParticipantEvent.TrackPublished, publication);
214
+ });
221
215
 
222
216
  // detect removed tracks
223
217
  this.tracks.forEach((publication) => {
@@ -18,9 +18,9 @@ export function mediaTrackToLocalTrack(
18
18
  ): LocalVideoTrack | LocalAudioTrack {
19
19
  switch (mediaStreamTrack.kind) {
20
20
  case 'audio':
21
- return new LocalAudioTrack(mediaStreamTrack, constraints);
21
+ return new LocalAudioTrack(mediaStreamTrack, constraints, false);
22
22
  case 'video':
23
- return new LocalVideoTrack(mediaStreamTrack, constraints);
23
+ return new LocalVideoTrack(mediaStreamTrack, constraints, false);
24
24
  default:
25
25
  throw new TrackInvalidError(`unsupported track type: ${mediaStreamTrack.kind}`);
26
26
  }
@@ -15,8 +15,12 @@ export default class LocalAudioTrack extends LocalTrack {
15
15
 
16
16
  private prevStats?: AudioSenderStats;
17
17
 
18
- constructor(mediaTrack: MediaStreamTrack, constraints?: MediaTrackConstraints) {
19
- super(mediaTrack, Track.Kind.Audio, constraints);
18
+ constructor(
19
+ mediaTrack: MediaStreamTrack,
20
+ constraints?: MediaTrackConstraints,
21
+ userProvidedTrack = true,
22
+ ) {
23
+ super(mediaTrack, Track.Kind.Audio, constraints, userProvidedTrack);
20
24
  this.checkForSilence();
21
25
  }
22
26
 
@@ -42,7 +46,7 @@ export default class LocalAudioTrack extends LocalTrack {
42
46
  }
43
47
 
44
48
  async unmute(): Promise<LocalAudioTrack> {
45
- if (this.source === Track.Source.Microphone && this.stopOnMute) {
49
+ if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) {
46
50
  log.debug('reacquiring mic track');
47
51
  await this.restartTrack();
48
52
  }
@@ -19,16 +19,20 @@ export default class LocalTrack extends Track {
19
19
 
20
20
  protected reacquireTrack: boolean;
21
21
 
22
+ protected providedByUser: boolean;
23
+
22
24
  protected constructor(
23
25
  mediaTrack: MediaStreamTrack,
24
26
  kind: Track.Kind,
25
27
  constraints?: MediaTrackConstraints,
28
+ userProvidedTrack = false,
26
29
  ) {
27
30
  super(mediaTrack, kind);
28
31
  this._mediaStreamTrack.addEventListener('ended', this.handleEnded);
29
32
  this.constraints = constraints ?? mediaTrack.getConstraints();
30
33
  this.reacquireTrack = false;
31
34
  this.wasMuted = false;
35
+ this.providedByUser = userProvidedTrack;
32
36
  }
33
37
 
34
38
  get id(): string {
@@ -56,6 +60,10 @@ export default class LocalTrack extends Track {
56
60
  return this._isUpstreamPaused;
57
61
  }
58
62
 
63
+ get isUserProvided() {
64
+ return this.providedByUser;
65
+ }
66
+
59
67
  /**
60
68
  * @returns DeviceID of the device that is currently being used for this track
61
69
  */
@@ -80,7 +88,7 @@ export default class LocalTrack extends Track {
80
88
  return this;
81
89
  }
82
90
 
83
- async replaceTrack(track: MediaStreamTrack): Promise<LocalTrack> {
91
+ async replaceTrack(track: MediaStreamTrack, userProvidedTrack = true): Promise<LocalTrack> {
84
92
  if (!this.sender) {
85
93
  throw new TrackInvalidError('unable to replace an unpublished track');
86
94
  }
@@ -108,6 +116,7 @@ export default class LocalTrack extends Track {
108
116
  });
109
117
 
110
118
  this.mediaStream = new MediaStream([track]);
119
+ this.providedByUser = userProvidedTrack;
111
120
  return this;
112
121
  }
113
122
 
@@ -184,7 +193,7 @@ export default class LocalTrack extends Track {
184
193
  if (!isMobile()) return;
185
194
  log.debug(`visibility changed, is in Background: ${this.isInBackground}`);
186
195
 
187
- if (!this.isInBackground && this.needsReAcquisition) {
196
+ if (!this.isInBackground && this.needsReAcquisition && !this.isUserProvided) {
188
197
  log.debug(`track needs to be reaquired, restarting ${this.source}`);
189
198
  await this.restart();
190
199
  this.reacquireTrack = false;
@@ -41,8 +41,12 @@ export default class LocalVideoTrack extends LocalTrack {
41
41
 
42
42
  private subscribedCodecs?: SubscribedCodec[];
43
43
 
44
- constructor(mediaTrack: MediaStreamTrack, constraints?: MediaTrackConstraints) {
45
- super(mediaTrack, Track.Kind.Video, constraints);
44
+ constructor(
45
+ mediaTrack: MediaStreamTrack,
46
+ constraints?: MediaTrackConstraints,
47
+ userProvidedTrack = true,
48
+ ) {
49
+ super(mediaTrack, Track.Kind.Video, constraints, userProvidedTrack);
46
50
  }
47
51
 
48
52
  get isSimulcast(): boolean {
@@ -92,7 +96,7 @@ export default class LocalVideoTrack extends LocalTrack {
92
96
  }
93
97
 
94
98
  async unmute(): Promise<LocalVideoTrack> {
95
- if (this.source === Track.Source.Camera) {
99
+ if (this.source === Track.Source.Camera && !this.isUserProvided) {
96
100
  log.debug('reacquiring camera track');
97
101
  await this.restartTrack();
98
102
  }
@@ -110,6 +110,8 @@ export default class RemoteVideoTrack extends RemoteTrack {
110
110
  // the tab comes into focus for the first time.
111
111
  this.debouncedHandleResize();
112
112
  this.updateVisibility();
113
+ } else {
114
+ log.warn('visibility resize observer not triggered');
113
115
  }
114
116
  }
115
117
 
@@ -294,9 +296,9 @@ class HTMLElementInfo implements ElementInfo {
294
296
 
295
297
  handleVisibilityChanged?: () => void;
296
298
 
297
- constructor(element: HTMLMediaElement, visible: boolean = false) {
299
+ constructor(element: HTMLMediaElement, visible?: boolean) {
298
300
  this.element = element;
299
- this.visible = visible;
301
+ this.visible = visible ?? isElementInViewport(element);
300
302
  this.visibilityChangedAt = 0;
301
303
  }
302
304
 
@@ -332,3 +334,29 @@ class HTMLElementInfo implements ElementInfo {
332
334
  getResizeObserver()?.unobserve(this.element);
333
335
  }
334
336
  }
337
+
338
+ // does not account for occlusion by other elements
339
+ function isElementInViewport(el: HTMLElement) {
340
+ let top = el.offsetTop;
341
+ let left = el.offsetLeft;
342
+ const width = el.offsetWidth;
343
+ const height = el.offsetHeight;
344
+ const { hidden } = el;
345
+ const { opacity, display } = getComputedStyle(el);
346
+
347
+ while (el.offsetParent) {
348
+ el = el.offsetParent as HTMLElement;
349
+ top += el.offsetTop;
350
+ left += el.offsetLeft;
351
+ }
352
+
353
+ return (
354
+ top < window.pageYOffset + window.innerHeight &&
355
+ left < window.pageXOffset + window.innerWidth &&
356
+ top + height > window.pageYOffset &&
357
+ left + width > window.pageXOffset &&
358
+ !hidden &&
359
+ (opacity !== '' ? parseFloat(opacity) > 0 : true) &&
360
+ display !== 'none'
361
+ );
362
+ }
@@ -110,11 +110,11 @@ export async function createLocalScreenTracks(
110
110
  if (tracks.length === 0) {
111
111
  throw new TrackInvalidError('no video track found');
112
112
  }
113
- const screenVideo = new LocalVideoTrack(tracks[0]);
113
+ const screenVideo = new LocalVideoTrack(tracks[0], undefined, false);
114
114
  screenVideo.source = Track.Source.ScreenShare;
115
115
  const localTracks: Array<LocalTrack> = [screenVideo];
116
116
  if (stream.getAudioTracks().length > 0) {
117
- const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0]);
117
+ const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false);
118
118
  screenAudio.source = Track.Source.ScreenShareAudio;
119
119
  localTracks.push(screenAudio);
120
120
  }
package/src/room/utils.ts CHANGED
@@ -114,3 +114,18 @@ export function getEmptyAudioStreamTrack() {
114
114
  }
115
115
  return emptyAudioStreamTrack;
116
116
  }
117
+
118
+ export class Future<T> {
119
+ promise: Promise<T>;
120
+
121
+ resolve!: (arg: T) => void;
122
+
123
+ reject!: (e: any) => void;
124
+
125
+ constructor() {
126
+ this.promise = new Promise<T>((resolve, reject) => {
127
+ this.resolve = resolve;
128
+ this.reject = reject;
129
+ });
130
+ }
131
+ }