livekit-client 0.17.1 → 0.17.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 (57) hide show
  1. package/dist/api/SignalClient.d.ts +2 -2
  2. package/dist/api/SignalClient.js +27 -1
  3. package/dist/api/SignalClient.js.map +1 -1
  4. package/dist/proto/google/protobuf/timestamp.d.ts +122 -0
  5. package/dist/proto/google/protobuf/timestamp.js +93 -0
  6. package/dist/proto/google/protobuf/timestamp.js.map +1 -0
  7. package/dist/proto/livekit_models.d.ts +78 -2
  8. package/dist/proto/livekit_models.js +830 -34
  9. package/dist/proto/livekit_models.js.map +1 -1
  10. package/dist/proto/livekit_rtc.d.ts +12 -0
  11. package/dist/proto/livekit_rtc.js +72 -1
  12. package/dist/proto/livekit_rtc.js.map +1 -1
  13. package/dist/room/RTCEngine.d.ts +1 -1
  14. package/dist/room/RTCEngine.js +22 -5
  15. package/dist/room/RTCEngine.js.map +1 -1
  16. package/dist/room/Room.d.ts +2 -1
  17. package/dist/room/Room.js +16 -5
  18. package/dist/room/Room.js.map +1 -1
  19. package/dist/room/events.d.ts +12 -2
  20. package/dist/room/events.js +10 -0
  21. package/dist/room/events.js.map +1 -1
  22. package/dist/room/participant/LocalParticipant.d.ts +4 -1
  23. package/dist/room/participant/LocalParticipant.js +25 -0
  24. package/dist/room/participant/LocalParticipant.js.map +1 -1
  25. package/dist/room/participant/Participant.d.ts +5 -1
  26. package/dist/room/participant/Participant.js +15 -1
  27. package/dist/room/participant/Participant.js.map +1 -1
  28. package/dist/room/track/LocalTrack.js +1 -0
  29. package/dist/room/track/LocalTrack.js.map +1 -1
  30. package/dist/room/track/RemoteTrack.js +1 -0
  31. package/dist/room/track/RemoteTrack.js.map +1 -1
  32. package/dist/room/track/Track.d.ts +1 -0
  33. package/dist/room/track/Track.js +10 -3
  34. package/dist/room/track/Track.js.map +1 -1
  35. package/dist/room/track/create.js +1 -0
  36. package/dist/room/track/create.js.map +1 -1
  37. package/dist/room/utils.d.ts +1 -0
  38. package/dist/room/utils.js +5 -1
  39. package/dist/room/utils.js.map +1 -1
  40. package/dist/version.d.ts +2 -2
  41. package/dist/version.js +2 -2
  42. package/package.json +2 -2
  43. package/src/api/SignalClient.ts +12 -2
  44. package/src/proto/google/protobuf/timestamp.ts +222 -0
  45. package/src/proto/livekit_models.ts +937 -30
  46. package/src/proto/livekit_rtc.ts +107 -0
  47. package/src/room/RTCEngine.ts +23 -7
  48. package/src/room/Room.ts +32 -15
  49. package/src/room/events.ts +12 -0
  50. package/src/room/participant/LocalParticipant.ts +33 -2
  51. package/src/room/participant/Participant.ts +22 -2
  52. package/src/room/track/LocalTrack.ts +1 -0
  53. package/src/room/track/RemoteTrack.ts +1 -0
  54. package/src/room/track/Track.ts +12 -4
  55. package/src/room/track/create.ts +1 -0
  56. package/src/room/utils.ts +4 -0
  57. package/src/version.ts +2 -2
@@ -152,6 +152,8 @@ export interface SignalResponse {
152
152
  subscriptionPermissionUpdate?: SubscriptionPermissionUpdate | undefined;
153
153
  /** update the token the client was using, to prevent an active client from using an expired token */
154
154
  refreshToken: string | undefined;
155
+ /** server initiated track unpublish */
156
+ trackUnpublished?: TrackUnpublishedResponse | undefined;
155
157
  }
156
158
 
157
159
  export interface AddTrackRequest {
@@ -202,6 +204,10 @@ export interface TrackPublishedResponse {
202
204
  track?: TrackInfo;
203
205
  }
204
206
 
207
+ export interface TrackUnpublishedResponse {
208
+ trackSid: string;
209
+ }
210
+
205
211
  export interface SessionDescription {
206
212
  /** "answer" | "offer" | "pranswer" | "rollback" */
207
213
  type: string;
@@ -738,6 +744,12 @@ export const SignalResponse = {
738
744
  if (message.refreshToken !== undefined) {
739
745
  writer.uint32(130).string(message.refreshToken);
740
746
  }
747
+ if (message.trackUnpublished !== undefined) {
748
+ TrackUnpublishedResponse.encode(
749
+ message.trackUnpublished,
750
+ writer.uint32(138).fork()
751
+ ).ldelim();
752
+ }
741
753
  return writer;
742
754
  },
743
755
 
@@ -809,6 +821,12 @@ export const SignalResponse = {
809
821
  case 16:
810
822
  message.refreshToken = reader.string();
811
823
  break;
824
+ case 17:
825
+ message.trackUnpublished = TrackUnpublishedResponse.decode(
826
+ reader,
827
+ reader.uint32()
828
+ );
829
+ break;
812
830
  default:
813
831
  reader.skipType(tag & 7);
814
832
  break;
@@ -922,6 +940,16 @@ export const SignalResponse = {
922
940
  } else {
923
941
  message.refreshToken = undefined;
924
942
  }
943
+ if (
944
+ object.trackUnpublished !== undefined &&
945
+ object.trackUnpublished !== null
946
+ ) {
947
+ message.trackUnpublished = TrackUnpublishedResponse.fromJSON(
948
+ object.trackUnpublished
949
+ );
950
+ } else {
951
+ message.trackUnpublished = undefined;
952
+ }
925
953
  return message;
926
954
  },
927
955
 
@@ -985,6 +1013,10 @@ export const SignalResponse = {
985
1013
  : undefined);
986
1014
  message.refreshToken !== undefined &&
987
1015
  (obj.refreshToken = message.refreshToken);
1016
+ message.trackUnpublished !== undefined &&
1017
+ (obj.trackUnpublished = message.trackUnpublished
1018
+ ? TrackUnpublishedResponse.toJSON(message.trackUnpublished)
1019
+ : undefined);
988
1020
  return obj;
989
1021
  },
990
1022
 
@@ -1089,6 +1121,16 @@ export const SignalResponse = {
1089
1121
  message.subscriptionPermissionUpdate = undefined;
1090
1122
  }
1091
1123
  message.refreshToken = object.refreshToken ?? undefined;
1124
+ if (
1125
+ object.trackUnpublished !== undefined &&
1126
+ object.trackUnpublished !== null
1127
+ ) {
1128
+ message.trackUnpublished = TrackUnpublishedResponse.fromPartial(
1129
+ object.trackUnpublished
1130
+ );
1131
+ } else {
1132
+ message.trackUnpublished = undefined;
1133
+ }
1092
1134
  return message;
1093
1135
  },
1094
1136
  };
@@ -1729,6 +1771,71 @@ export const TrackPublishedResponse = {
1729
1771
  },
1730
1772
  };
1731
1773
 
1774
+ const baseTrackUnpublishedResponse: object = { trackSid: "" };
1775
+
1776
+ export const TrackUnpublishedResponse = {
1777
+ encode(
1778
+ message: TrackUnpublishedResponse,
1779
+ writer: _m0.Writer = _m0.Writer.create()
1780
+ ): _m0.Writer {
1781
+ if (message.trackSid !== "") {
1782
+ writer.uint32(10).string(message.trackSid);
1783
+ }
1784
+ return writer;
1785
+ },
1786
+
1787
+ decode(
1788
+ input: _m0.Reader | Uint8Array,
1789
+ length?: number
1790
+ ): TrackUnpublishedResponse {
1791
+ const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
1792
+ let end = length === undefined ? reader.len : reader.pos + length;
1793
+ const message = {
1794
+ ...baseTrackUnpublishedResponse,
1795
+ } as TrackUnpublishedResponse;
1796
+ while (reader.pos < end) {
1797
+ const tag = reader.uint32();
1798
+ switch (tag >>> 3) {
1799
+ case 1:
1800
+ message.trackSid = reader.string();
1801
+ break;
1802
+ default:
1803
+ reader.skipType(tag & 7);
1804
+ break;
1805
+ }
1806
+ }
1807
+ return message;
1808
+ },
1809
+
1810
+ fromJSON(object: any): TrackUnpublishedResponse {
1811
+ const message = {
1812
+ ...baseTrackUnpublishedResponse,
1813
+ } as TrackUnpublishedResponse;
1814
+ if (object.trackSid !== undefined && object.trackSid !== null) {
1815
+ message.trackSid = String(object.trackSid);
1816
+ } else {
1817
+ message.trackSid = "";
1818
+ }
1819
+ return message;
1820
+ },
1821
+
1822
+ toJSON(message: TrackUnpublishedResponse): unknown {
1823
+ const obj: any = {};
1824
+ message.trackSid !== undefined && (obj.trackSid = message.trackSid);
1825
+ return obj;
1826
+ },
1827
+
1828
+ fromPartial(
1829
+ object: DeepPartial<TrackUnpublishedResponse>
1830
+ ): TrackUnpublishedResponse {
1831
+ const message = {
1832
+ ...baseTrackUnpublishedResponse,
1833
+ } as TrackUnpublishedResponse;
1834
+ message.trackSid = object.trackSid ?? "";
1835
+ return message;
1836
+ },
1837
+ };
1838
+
1732
1839
  const baseSessionDescription: object = { type: "", sdp: "" };
1733
1840
 
1734
1841
  export const SessionDescription = {
@@ -16,7 +16,7 @@ import {
16
16
  import { ConnectionError, TrackInvalidError, UnexpectedConnectionState } from './errors';
17
17
  import { EngineEvent } from './events';
18
18
  import PCTransport from './PCTransport';
19
- import { isFireFox, sleep } from './utils';
19
+ import { isFireFox, isWeb, sleep } from './utils';
20
20
 
21
21
  const lossyDataChannel = '_lossy';
22
22
  const reliableDataChannel = '_reliable';
@@ -112,7 +112,10 @@ export default class RTCEngine extends (
112
112
  if (this.publisher && this.publisher.pc.signalingState !== 'closed') {
113
113
  this.publisher.pc.getSenders().forEach((sender) => {
114
114
  try {
115
- this.publisher?.pc.removeTrack(sender);
115
+ // TODO: react-native-webrtc doesn't have removeTrack yet.
116
+ if (this.publisher?.pc.removeTrack) {
117
+ this.publisher?.pc.removeTrack(sender);
118
+ }
116
119
  } catch (e) {
117
120
  log.warn('could not removeTrack', e);
118
121
  }
@@ -171,6 +174,11 @@ export default class RTCEngine extends (
171
174
  this.rtcConfig.iceServers = rtcIceServers;
172
175
  }
173
176
 
177
+ // @ts-ignore
178
+ this.rtcConfig.sdpSemantics = 'unified-plan';
179
+ // @ts-ignore
180
+ this.rtcConfig.continualGatheringPolicy = 'gather_continually';
181
+
174
182
  this.publisher = new PCTransport(this.rtcConfig);
175
183
  this.subscriber = new PCTransport(this.rtcConfig);
176
184
 
@@ -221,10 +229,18 @@ export default class RTCEngine extends (
221
229
  }
222
230
  };
223
231
 
224
- this.subscriber.pc.ontrack = (ev: RTCTrackEvent) => {
225
- this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
226
- };
227
-
232
+ if (isWeb()) {
233
+ this.subscriber.pc.ontrack = (ev: RTCTrackEvent) => {
234
+ this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
235
+ };
236
+ } else {
237
+ // TODO: react-native-webrtc doesn't have ontrack yet, replace when ready.
238
+ // @ts-ignore
239
+ this.subscriber.pc.onaddstream = (ev: { stream: MediaStream }) => {
240
+ const track = ev.stream.getTracks()[0];
241
+ this.emit(EngineEvent.MediaTrackAdded, track, ev.stream);
242
+ };
243
+ }
228
244
  // data channels
229
245
  this.lossyDC = this.publisher.pc.createDataChannel(lossyDataChannel, {
230
246
  // will drop older packets that arrive
@@ -628,7 +644,7 @@ export type EngineEventCallbacks = {
628
644
  mediaTrackAdded: (
629
645
  track: MediaStreamTrack,
630
646
  streams: MediaStream,
631
- receiver: RTCRtpReceiver
647
+ receiver?: RTCRtpReceiver
632
648
  ) => void,
633
649
  activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void,
634
650
  dataPacketReceived: (userPacket: UserPacket, kind: DataPacket_Kind) => void,
package/src/room/Room.ts CHANGED
@@ -5,7 +5,7 @@ import log from '../logger';
5
5
  import { RoomConnectOptions, RoomOptions } from '../options';
6
6
  import {
7
7
  DataPacket_Kind, ParticipantInfo,
8
- ParticipantInfo_State, Room as RoomModel, SpeakerInfo, UserPacket,
8
+ ParticipantInfo_State, ParticipantPermission, Room as RoomModel, SpeakerInfo, UserPacket,
9
9
  } from '../proto/livekit_models';
10
10
  import {
11
11
  ConnectionQualityUpdate,
@@ -30,7 +30,7 @@ import { Track } from './track/Track';
30
30
  import { TrackPublication } from './track/TrackPublication';
31
31
  import { AdaptiveStreamSettings, RemoteTrack } from './track/types';
32
32
  import { getNewAudioContext } from './track/utils';
33
- import { unpackStreamId } from './utils';
33
+ import { isWeb, unpackStreamId } from './utils';
34
34
 
35
35
  export enum RoomState {
36
36
  Disconnected = 'disconnected',
@@ -211,12 +211,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
211
211
  this.state = RoomState.Connected;
212
212
  this.emit(RoomEvent.StateChanged, this.state);
213
213
  const pi = joinResponse.participant!;
214
- this.localParticipant = new LocalParticipant(
215
- pi.sid,
216
- pi.identity,
217
- this.engine,
218
- this.options,
219
- );
214
+
215
+ this.localParticipant.sid = pi.sid;
216
+ this.localParticipant.identity = pi.identity;
220
217
 
221
218
  this.localParticipant.updateInfo(pi);
222
219
  // forward metadata changed for the local participant
@@ -244,7 +241,15 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
244
241
  })
245
242
  .on(ParticipantEvent.MediaDevicesError, (e: Error) => {
246
243
  this.emit(RoomEvent.MediaDevicesError, e);
247
- });
244
+ })
245
+ .on(ParticipantEvent.ParticipantPermissionsChanged,
246
+ (prevPermissions: ParticipantPermission) => {
247
+ this.emit(
248
+ RoomEvent.ParticipantPermissionsChanged,
249
+ prevPermissions,
250
+ this.localParticipant,
251
+ );
252
+ });
248
253
 
249
254
  // populate remote participants, these should not trigger new events
250
255
  joinResponse.otherParticipants.forEach((info) => {
@@ -271,8 +276,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
271
276
  clearTimeout(connectTimeout);
272
277
 
273
278
  // also hook unload event
274
- window.addEventListener('beforeunload', this.onBeforeUnload);
275
- navigator.mediaDevices.addEventListener('devicechange', this.handleDeviceChange);
279
+ if (isWeb()) {
280
+ window.addEventListener('beforeunload', this.onBeforeUnload);
281
+ navigator.mediaDevices.addEventListener('devicechange', this.handleDeviceChange);
282
+ }
276
283
 
277
284
  resolve(this);
278
285
  });
@@ -521,8 +528,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
521
528
  this.audioContext.close();
522
529
  this.audioContext = undefined;
523
530
  }
524
- window.removeEventListener('beforeunload', this.onBeforeUnload);
525
- navigator.mediaDevices.removeEventListener('devicechange', this.handleDeviceChange);
531
+ if (isWeb()) {
532
+ window.removeEventListener('beforeunload', this.onBeforeUnload);
533
+ navigator.mediaDevices.removeEventListener('devicechange', this.handleDeviceChange);
534
+ }
526
535
  this.state = RoomState.Disconnected;
527
536
  this.emit(RoomEvent.Disconnected);
528
537
  this.emit(RoomEvent.StateChanged, this.state);
@@ -532,7 +541,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
532
541
  // handle changes to participant state, and send events
533
542
  participantInfos.forEach((info) => {
534
543
  if (info.sid === this.localParticipant.sid
535
- || info.identity === this.localParticipant.identity) {
544
+ || info.identity === this.localParticipant.identity) {
536
545
  this.localParticipant.updateInfo(info);
537
546
  return;
538
547
  }
@@ -798,7 +807,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
798
807
  })
799
808
  .on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
800
809
  this.emit(RoomEvent.ConnectionQualityChanged, quality, participant);
801
- });
810
+ })
811
+ .on(ParticipantEvent.ParticipantPermissionsChanged,
812
+ (prevPermissions: ParticipantPermission) => {
813
+ this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, participant);
814
+ });
802
815
  return participant;
803
816
  }
804
817
 
@@ -903,6 +916,10 @@ export type RoomEventCallbacks = {
903
916
  metadata: string | undefined,
904
917
  participant: RemoteParticipant | LocalParticipant
905
918
  ) => void,
919
+ participantPermissionsChanged: (
920
+ prevPermissions: ParticipantPermission,
921
+ participant: RemoteParticipant | LocalParticipant
922
+ ) => void,
906
923
  activeSpeakersChanged: (speakers: Array<Participant>) => void,
907
924
  roomMetadataChanged: (metadata: string) => void,
908
925
  dataReceived: (
@@ -225,6 +225,12 @@ export enum RoomEvent {
225
225
  * args: (error: Error)
226
226
  */
227
227
  MediaDevicesError = 'mediaDevicesError',
228
+
229
+ /**
230
+ * A participant's permission has changed. Currently only fired on LocalParticipant.
231
+ * args: (prevPermissions: [[ParticipantPermission]], participant: [[Participant]])
232
+ */
233
+ ParticipantPermissionsChanged = 'participantPermissionsChanged',
228
234
  }
229
235
 
230
236
  export enum ParticipantEvent {
@@ -370,6 +376,12 @@ export enum ParticipantEvent {
370
376
  // fired only on LocalParticipant
371
377
  /** @internal */
372
378
  MediaDevicesError = 'mediaDevicesError',
379
+
380
+ /**
381
+ * A participant's permission has changed. Currently only fired on LocalParticipant.
382
+ * args: (prevPermissions: [[ParticipantPermission]])
383
+ */
384
+ ParticipantPermissionsChanged = 'participantPermissionsChanged',
373
385
  }
374
386
 
375
387
  /** @internal */
@@ -1,10 +1,11 @@
1
1
  import log from '../../logger';
2
2
  import { RoomOptions } from '../../options';
3
3
  import {
4
- DataPacket, DataPacket_Kind,
4
+ DataPacket, DataPacket_Kind, ParticipantPermission,
5
5
  } from '../../proto/livekit_models';
6
6
  import {
7
- AddTrackRequest, DataChannelInfo, SubscribedQualityUpdate, TrackPublishedResponse,
7
+ AddTrackRequest, DataChannelInfo,
8
+ SubscribedQualityUpdate, TrackPublishedResponse, TrackUnpublishedResponse,
8
9
  } from '../../proto/livekit_rtc';
9
10
  import {
10
11
  TrackInvalidError,
@@ -24,6 +25,7 @@ import {
24
25
  } from '../track/options';
25
26
  import { Track } from '../track/Track';
26
27
  import { constraintsForOptions, mergeDefaultOptions } from '../track/utils';
28
+ import { isFireFox } from '../utils';
27
29
  import Participant from './Participant';
28
30
  import { ParticipantTrackPermission, trackPermissionToProto } from './ParticipantTrackPermission';
29
31
  import { computeVideoEncodings, mediaTrackToLocalTrack } from './publishUtils';
@@ -70,6 +72,8 @@ export default class LocalParticipant extends Participant {
70
72
  };
71
73
 
72
74
  this.engine.client.onSubscribedQualityUpdate = this.handleSubscribedQualityUpdate;
75
+
76
+ this.engine.client.onLocalTrackUnpublished = this.handleLocalTrackUnpublished;
73
77
  }
74
78
 
75
79
  get lastCameraError(): Error | undefined {
@@ -119,6 +123,16 @@ export default class LocalParticipant extends Participant {
119
123
  return this.setTrackEnabled(Track.Source.ScreenShare, enabled);
120
124
  }
121
125
 
126
+ /** @internal */
127
+ setPermissions(permissions: ParticipantPermission): boolean {
128
+ const prevPermissions = this.permissions;
129
+ const changed = super.setPermissions(permissions);
130
+ if (changed && prevPermissions) {
131
+ this.emit(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions);
132
+ }
133
+ return changed;
134
+ }
135
+
122
136
  /**
123
137
  * Enable or disable publishing for a track by source. This serves as a simple
124
138
  * way to manage the common tracks (camera, mic, or screen share)
@@ -259,6 +273,7 @@ export default class LocalParticipant extends Participant {
259
273
  } else if (track.kind === Track.Kind.Audio) {
260
274
  track.source = Track.Source.Microphone;
261
275
  }
276
+ track.mediaStream = stream;
262
277
  return track;
263
278
  });
264
279
  }
@@ -358,6 +373,12 @@ export default class LocalParticipant extends Participant {
358
373
  track.stopOnMute = true;
359
374
  }
360
375
 
376
+ if (track.source === Track.Source.ScreenShare && isFireFox()) {
377
+ // Firefox does not work well with simulcasted screen share
378
+ // we frequently get no data on layer 0 when enabled
379
+ opts.simulcast = false;
380
+ }
381
+
361
382
  // handle track actions
362
383
  track.on(TrackEvent.Muted, this.onTrackMuted);
363
384
  track.on(TrackEvent.Unmuted, this.onTrackUnmuted);
@@ -612,6 +633,16 @@ export default class LocalParticipant extends Participant {
612
633
  pub.videoTrack?.setPublishingLayers(update.subscribedQualities);
613
634
  };
614
635
 
636
+ private handleLocalTrackUnpublished = (unpublished: TrackUnpublishedResponse) => {
637
+ const track = this.tracks.get(unpublished.trackSid);
638
+ if (!track) {
639
+ log.warn('handleLocalTrackUnpublished',
640
+ 'received unpublished event for unknown track', unpublished.trackSid);
641
+ return;
642
+ }
643
+ this.unpublishTrack(track.track!);
644
+ };
645
+
615
646
  private onTrackUnpublish = (track: LocalTrack) => {
616
647
  this.unpublishTrack(track);
617
648
  };
@@ -1,6 +1,8 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import type TypedEmitter from 'typed-emitter';
3
- import { ConnectionQuality as ProtoQuality, DataPacket_Kind, ParticipantInfo } from '../../proto/livekit_models';
3
+ import {
4
+ ConnectionQuality as ProtoQuality, DataPacket_Kind, ParticipantInfo, ParticipantPermission,
5
+ } from '../../proto/livekit_models';
4
6
  import { ParticipantEvent, TrackEvent } from '../events';
5
7
  import LocalTrackPublication from '../track/LocalTrackPublication';
6
8
  import RemoteTrackPublication from '../track/RemoteTrackPublication';
@@ -60,6 +62,8 @@ export default class Participant extends (
60
62
 
61
63
  lastSpokeAt?: Date | undefined;
62
64
 
65
+ permissions?: ParticipantPermission;
66
+
63
67
  private _connectionQuality: ConnectionQuality = ConnectionQuality.Unknown;
64
68
 
65
69
  /** @internal */
@@ -153,13 +157,16 @@ export default class Participant extends (
153
157
  this.sid = info.sid;
154
158
  this.name = info.name;
155
159
  this.setMetadata(info.metadata);
160
+ if (info.permission) {
161
+ this.setPermissions(info.permission);
162
+ }
156
163
  // set this last so setMetadata can detect changes
157
164
  this.participantInfo = info;
158
165
  }
159
166
 
160
167
  /** @internal */
161
168
  setMetadata(md: string) {
162
- const changed = !this.participantInfo || this.participantInfo.metadata !== md;
169
+ const changed = this.metadata !== md;
163
170
  const prevMetadata = this.metadata;
164
171
  this.metadata = md;
165
172
 
@@ -169,6 +176,18 @@ export default class Participant extends (
169
176
  }
170
177
  }
171
178
 
179
+ /** @internal */
180
+ setPermissions(permissions: ParticipantPermission): boolean {
181
+ const changed = permissions.canPublish !== this.permissions?.canPublish
182
+ || permissions.canSubscribe !== this.permissions?.canSubscribe
183
+ || permissions.canPublishData !== this.permissions?.canPublishData
184
+ || permissions.hidden !== this.permissions?.hidden
185
+ || permissions.recorder !== this.permissions?.recorder;
186
+ this.permissions = permissions;
187
+
188
+ return changed;
189
+ }
190
+
172
191
  /** @internal */
173
192
  setIsSpeaking(speaking: boolean) {
174
193
  if (speaking === this.isSpeaking) {
@@ -246,4 +265,5 @@ export type ParticipantEventCallbacks = {
246
265
  status: TrackPublication.SubscriptionStatus
247
266
  ) => void,
248
267
  mediaDevicesError: (error: Error) => void,
268
+ participantPermissionsChanged: (prevPermissions: ParticipantPermission) => void,
249
269
  };
@@ -111,6 +111,7 @@ export default class LocalTrack extends Track {
111
111
  attachToElement(newTrack, el);
112
112
  });
113
113
 
114
+ this.mediaStream = mediaStream;
114
115
  this.constraints = constraints;
115
116
  return this;
116
117
  }
@@ -32,6 +32,7 @@ export default abstract class RemoteTrack extends Track {
32
32
  // this is needed to determine when the track is finished
33
33
  // we send each track down in its own MediaStream, so we can assume the
34
34
  // current track is the only one that can be removed.
35
+ this.mediaStream = stream;
35
36
  stream.onremovetrack = () => {
36
37
  this.receiver = undefined;
37
38
  this._currentBitrate = 0;
@@ -3,7 +3,7 @@ import type TypedEventEmitter from 'typed-emitter';
3
3
  import { TrackSource, TrackType } from '../../proto/livekit_models';
4
4
  import { StreamState as ProtoStreamState } from '../../proto/livekit_rtc';
5
5
  import { TrackEvent } from '../events';
6
- import { isFireFox, isSafari } from '../utils';
6
+ import { isFireFox, isSafari, isWeb } from '../utils';
7
7
 
8
8
  // keep old audio elements when detached, we would re-use them since on iOS
9
9
  // Safari tracks which audio elements have been "blessed" by the user.
@@ -12,6 +12,8 @@ const recycledElements: Array<HTMLAudioElement> = [];
12
12
  export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEventCallbacks>) {
13
13
  kind: Track.Kind;
14
14
 
15
+ mediaStream?: MediaStream;
16
+
15
17
  mediaStreamTrack: MediaStreamTrack;
16
18
 
17
19
  attachedElements: HTMLMediaElement[] = [];
@@ -34,8 +36,12 @@ export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEve
34
36
  this.kind = kind;
35
37
  this.mediaStreamTrack = mediaTrack;
36
38
  this.source = Track.Source.Unknown;
37
- this.isInBackground = document.visibilityState === 'hidden';
38
- document.addEventListener('visibilitychange', this.appVisibilityChangedListener);
39
+ if (isWeb()) {
40
+ this.isInBackground = document.visibilityState === 'hidden';
41
+ document.addEventListener('visibilitychange', this.appVisibilityChangedListener);
42
+ } else {
43
+ this.isInBackground = false;
44
+ }
39
45
  }
40
46
 
41
47
  /** current receive bits per second */
@@ -133,7 +139,9 @@ export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEve
133
139
 
134
140
  stop() {
135
141
  this.mediaStreamTrack.stop();
136
- document.removeEventListener('visibilitychange', this.appVisibilityChangedListener);
142
+ if (isWeb()) {
143
+ document.removeEventListener('visibilitychange', this.appVisibilityChangedListener);
144
+ }
137
145
  }
138
146
 
139
147
  protected enable() {
@@ -47,6 +47,7 @@ export async function createLocalTracks(
47
47
  } else if (track.kind === Track.Kind.Audio) {
48
48
  track.source = Track.Source.Microphone;
49
49
  }
50
+ track.mediaStream = stream;
50
51
  return track;
51
52
  });
52
53
  }
package/src/room/utils.ts CHANGED
@@ -27,6 +27,10 @@ export function isMobile(): boolean {
27
27
  return /Tablet|iPad|Mobile|Android|BlackBerry/.test(navigator.userAgent);
28
28
  }
29
29
 
30
+ export function isWeb(): boolean {
31
+ return typeof document !== 'undefined';
32
+ }
33
+
30
34
  function roDispatchCallback(entries: ResizeObserverEntry[]) {
31
35
  for (const entry of entries) {
32
36
  (entry.target as ObservableMediaElement).handleResize(entry);
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const version = '0.17.1';
2
- export const protocolVersion = 6;
1
+ export const version = '0.17.4';
2
+ export const protocolVersion = 7;