livekit-client 1.1.9 → 1.2.2
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/dist/livekit-client.esm.mjs +218 -70
- 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/src/api/SignalClient.d.ts +3 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/options.d.ts +1 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +9 -0
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +1 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +8 -1
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +3 -3
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts +4 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/SignalClient.ts +3 -1
- package/src/options.ts +1 -0
- package/src/room/PCTransport.ts +39 -0
- package/src/room/RTCEngine.ts +41 -16
- package/src/room/Room.ts +20 -16
- package/src/room/events.ts +7 -0
- package/src/room/participant/LocalParticipant.ts +70 -10
- package/src/room/participant/RemoteParticipant.ts +12 -10
- package/src/room/participant/publishUtils.ts +1 -1
- package/src/room/track/LocalTrack.ts +4 -0
- package/src/room/track/RemoteTrackPublication.ts +37 -11
| @@ -32,7 +32,7 @@ import { | |
| 32 32 | 
             
            } from '../track/options';
         | 
| 33 33 | 
             
            import { Track } from '../track/Track';
         | 
| 34 34 | 
             
            import { constraintsForOptions, mergeDefaultOptions } from '../track/utils';
         | 
| 35 | 
            -
            import { isFireFox } from '../utils';
         | 
| 35 | 
            +
            import { isFireFox, isWeb } from '../utils';
         | 
| 36 36 | 
             
            import Participant from './Participant';
         | 
| 37 37 | 
             
            import { ParticipantTrackPermission, trackPermissionToProto } from './ParticipantTrackPermission';
         | 
| 38 38 | 
             
            import { computeVideoEncodings, mediaTrackToLocalTrack } from './publishUtils';
         | 
| @@ -125,8 +125,9 @@ export default class LocalParticipant extends Participant { | |
| 125 125 | 
             
              setCameraEnabled(
         | 
| 126 126 | 
             
                enabled: boolean,
         | 
| 127 127 | 
             
                options?: VideoCaptureOptions,
         | 
| 128 | 
            +
                publishOptions?: TrackPublishOptions,
         | 
| 128 129 | 
             
              ): Promise<LocalTrackPublication | undefined> {
         | 
| 129 | 
            -
                return this.setTrackEnabled(Track.Source.Camera, enabled, options);
         | 
| 130 | 
            +
                return this.setTrackEnabled(Track.Source.Camera, enabled, options, publishOptions);
         | 
| 130 131 | 
             
              }
         | 
| 131 132 |  | 
| 132 133 | 
             
              /**
         | 
| @@ -138,8 +139,9 @@ export default class LocalParticipant extends Participant { | |
| 138 139 | 
             
              setMicrophoneEnabled(
         | 
| 139 140 | 
             
                enabled: boolean,
         | 
| 140 141 | 
             
                options?: AudioCaptureOptions,
         | 
| 142 | 
            +
                publishOptions?: TrackPublishOptions,
         | 
| 141 143 | 
             
              ): Promise<LocalTrackPublication | undefined> {
         | 
| 142 | 
            -
                return this.setTrackEnabled(Track.Source.Microphone, enabled, options);
         | 
| 144 | 
            +
                return this.setTrackEnabled(Track.Source.Microphone, enabled, options, publishOptions);
         | 
| 143 145 | 
             
              }
         | 
| 144 146 |  | 
| 145 147 | 
             
              /**
         | 
| @@ -149,8 +151,9 @@ export default class LocalParticipant extends Participant { | |
| 149 151 | 
             
              setScreenShareEnabled(
         | 
| 150 152 | 
             
                enabled: boolean,
         | 
| 151 153 | 
             
                options?: ScreenShareCaptureOptions,
         | 
| 154 | 
            +
                publishOptions?: TrackPublishOptions,
         | 
| 152 155 | 
             
              ): Promise<LocalTrackPublication | undefined> {
         | 
| 153 | 
            -
                return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options);
         | 
| 156 | 
            +
                return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options, publishOptions);
         | 
| 154 157 | 
             
              }
         | 
| 155 158 |  | 
| 156 159 | 
             
              /** @internal */
         | 
| @@ -172,21 +175,25 @@ export default class LocalParticipant extends Participant { | |
| 172 175 | 
             
                source: Extract<Track.Source, Track.Source.Camera>,
         | 
| 173 176 | 
             
                enabled: boolean,
         | 
| 174 177 | 
             
                options?: VideoCaptureOptions,
         | 
| 178 | 
            +
                publishOptions?: TrackPublishOptions,
         | 
| 175 179 | 
             
              ): Promise<LocalTrackPublication | undefined>;
         | 
| 176 180 | 
             
              private async setTrackEnabled(
         | 
| 177 181 | 
             
                source: Extract<Track.Source, Track.Source.Microphone>,
         | 
| 178 182 | 
             
                enabled: boolean,
         | 
| 179 183 | 
             
                options?: AudioCaptureOptions,
         | 
| 184 | 
            +
                publishOptions?: TrackPublishOptions,
         | 
| 180 185 | 
             
              ): Promise<LocalTrackPublication | undefined>;
         | 
| 181 186 | 
             
              private async setTrackEnabled(
         | 
| 182 187 | 
             
                source: Extract<Track.Source, Track.Source.ScreenShare>,
         | 
| 183 188 | 
             
                enabled: boolean,
         | 
| 184 189 | 
             
                options?: ScreenShareCaptureOptions,
         | 
| 190 | 
            +
                publishOptions?: TrackPublishOptions,
         | 
| 185 191 | 
             
              ): Promise<LocalTrackPublication | undefined>;
         | 
| 186 192 | 
             
              private async setTrackEnabled(
         | 
| 187 193 | 
             
                source: Track.Source,
         | 
| 188 194 | 
             
                enabled: true,
         | 
| 189 195 | 
             
                options?: VideoCaptureOptions | AudioCaptureOptions | ScreenShareCaptureOptions,
         | 
| 196 | 
            +
                publishOptions?: TrackPublishOptions,
         | 
| 190 197 | 
             
              ) {
         | 
| 191 198 | 
             
                log.debug('setTrackEnabled', { source, enabled });
         | 
| 192 199 | 
             
                let track = this.getTrack(source);
         | 
| @@ -224,7 +231,7 @@ export default class LocalParticipant extends Participant { | |
| 224 231 | 
             
                      }
         | 
| 225 232 | 
             
                      const publishPromises: Array<Promise<LocalTrackPublication>> = [];
         | 
| 226 233 | 
             
                      for (const localTrack of localTracks) {
         | 
| 227 | 
            -
                        publishPromises.push(this.publishTrack(localTrack));
         | 
| 234 | 
            +
                        publishPromises.push(this.publishTrack(localTrack, publishOptions));
         | 
| 228 235 | 
             
                      }
         | 
| 229 236 | 
             
                      const publishedTracks = await Promise.all(publishPromises);
         | 
| 230 237 | 
             
                      // for screen share publications including audio, this will only return the screen share publication, not the screen share audio one
         | 
| @@ -544,6 +551,14 @@ export default class LocalParticipant extends Participant { | |
| 544 551 | 
             
                  track.codec = opts.videoCodec;
         | 
| 545 552 | 
             
                }
         | 
| 546 553 |  | 
| 554 | 
            +
                if (track.codec === 'av1' && encodings && encodings[0]?.maxBitrate) {
         | 
| 555 | 
            +
                  this.engine.publisher.setTrackCodecBitrate(
         | 
| 556 | 
            +
                    req.cid,
         | 
| 557 | 
            +
                    track.codec,
         | 
| 558 | 
            +
                    encodings[0].maxBitrate / 1000,
         | 
| 559 | 
            +
                  );
         | 
| 560 | 
            +
                }
         | 
| 561 | 
            +
             | 
| 547 562 | 
             
                this.engine.negotiate();
         | 
| 548 563 |  | 
| 549 564 | 
             
                // store RTPSender
         | 
| @@ -642,6 +657,13 @@ export default class LocalParticipant extends Participant { | |
| 642 657 | 
             
                this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
         | 
| 643 658 | 
             
                track.setSimulcastTrackSender(opts.videoCodec, transceiver.sender);
         | 
| 644 659 |  | 
| 660 | 
            +
                if (videoCodec === 'av1' && encodings[0]?.maxBitrate) {
         | 
| 661 | 
            +
                  this.engine.publisher.setTrackCodecBitrate(
         | 
| 662 | 
            +
                    req.cid,
         | 
| 663 | 
            +
                    videoCodec,
         | 
| 664 | 
            +
                    encodings[0].maxBitrate / 1000,
         | 
| 665 | 
            +
                  );
         | 
| 666 | 
            +
                }
         | 
| 645 667 | 
             
                this.engine.negotiate();
         | 
| 646 668 | 
             
                log.debug(`published ${opts.videoCodec} for track ${track.sid}`, { encodings, trackInfo: ti });
         | 
| 647 669 | 
             
              }
         | 
| @@ -894,11 +916,49 @@ export default class LocalParticipant extends Participant { | |
| 894 916 | 
             
                this.unpublishTrack(track.track!);
         | 
| 895 917 | 
             
              };
         | 
| 896 918 |  | 
| 897 | 
            -
              private handleTrackEnded = (track: LocalTrack) => {
         | 
| 898 | 
            -
                 | 
| 899 | 
            -
                  track | 
| 900 | 
            -
             | 
| 901 | 
            -
                 | 
| 919 | 
            +
              private handleTrackEnded = async (track: LocalTrack) => {
         | 
| 920 | 
            +
                if (
         | 
| 921 | 
            +
                  track.source === Track.Source.ScreenShare ||
         | 
| 922 | 
            +
                  track.source === Track.Source.ScreenShareAudio
         | 
| 923 | 
            +
                ) {
         | 
| 924 | 
            +
                  log.debug('unpublishing local track due to TrackEnded', {
         | 
| 925 | 
            +
                    track: track.sid,
         | 
| 926 | 
            +
                  });
         | 
| 927 | 
            +
                  this.unpublishTrack(track);
         | 
| 928 | 
            +
                } else if (track.isUserProvided) {
         | 
| 929 | 
            +
                  await track.pauseUpstream();
         | 
| 930 | 
            +
                } else if (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) {
         | 
| 931 | 
            +
                  try {
         | 
| 932 | 
            +
                    if (isWeb()) {
         | 
| 933 | 
            +
                      try {
         | 
| 934 | 
            +
                        const currentPermissions = await navigator?.permissions.query({
         | 
| 935 | 
            +
                          // the permission query for camera and microphone currently not supported in Safari and Firefox
         | 
| 936 | 
            +
                          // @ts-ignore
         | 
| 937 | 
            +
                          name: track.source === Track.Source.Camera ? 'camera' : 'microphone',
         | 
| 938 | 
            +
                        });
         | 
| 939 | 
            +
                        if (currentPermissions && currentPermissions.state === 'denied') {
         | 
| 940 | 
            +
                          log.warn(`user has revoked access to ${track.source}`);
         | 
| 941 | 
            +
             | 
| 942 | 
            +
                          // detect granted change after permissions were denied to try and resume then
         | 
| 943 | 
            +
                          currentPermissions.onchange = () => {
         | 
| 944 | 
            +
                            if (currentPermissions.state !== 'denied') {
         | 
| 945 | 
            +
                              track.restartTrack();
         | 
| 946 | 
            +
                              currentPermissions.onchange = null;
         | 
| 947 | 
            +
                            }
         | 
| 948 | 
            +
                          };
         | 
| 949 | 
            +
                          throw new Error('GetUserMedia Permission denied');
         | 
| 950 | 
            +
                        }
         | 
| 951 | 
            +
                      } catch (e: any) {
         | 
| 952 | 
            +
                        // permissions query fails for firefox, we continue and try to restart the track
         | 
| 953 | 
            +
                      }
         | 
| 954 | 
            +
                    }
         | 
| 955 | 
            +
                    log.debug('track ended, attempting to use a different device');
         | 
| 956 | 
            +
                    await track.restartTrack();
         | 
| 957 | 
            +
                  } catch (e) {
         | 
| 958 | 
            +
                    log.warn(`could not restart track, pausing upstream instead`);
         | 
| 959 | 
            +
                    await track.pauseUpstream();
         | 
| 960 | 
            +
                  }
         | 
| 961 | 
            +
                }
         | 
| 902 962 | 
             
              };
         | 
| 903 963 |  | 
| 904 964 | 
             
              private getPublicationForTrack(
         | 
| @@ -7,6 +7,7 @@ import RemoteAudioTrack from '../track/RemoteAudioTrack'; | |
| 7 7 | 
             
            import RemoteTrackPublication from '../track/RemoteTrackPublication';
         | 
| 8 8 | 
             
            import RemoteVideoTrack from '../track/RemoteVideoTrack';
         | 
| 9 9 | 
             
            import { Track } from '../track/Track';
         | 
| 10 | 
            +
            import { TrackPublication } from '../track/TrackPublication';
         | 
| 10 11 | 
             
            import { AdaptiveStreamSettings, RemoteTrack } from '../track/types';
         | 
| 11 12 | 
             
            import Participant, { ParticipantEventCallbacks } from './Participant';
         | 
| 12 13 |  | 
| @@ -49,8 +50,17 @@ export default class RemoteParticipant extends Participant { | |
| 49 50 | 
             
                  });
         | 
| 50 51 | 
             
                  this.signalClient.sendUpdateSubscription(sub);
         | 
| 51 52 | 
             
                });
         | 
| 52 | 
            -
                publication.on( | 
| 53 | 
            -
                   | 
| 53 | 
            +
                publication.on(
         | 
| 54 | 
            +
                  TrackEvent.SubscriptionPermissionChanged,
         | 
| 55 | 
            +
                  (status: TrackPublication.SubscriptionStatus) => {
         | 
| 56 | 
            +
                    this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status);
         | 
| 57 | 
            +
                  },
         | 
| 58 | 
            +
                );
         | 
| 59 | 
            +
                publication.on(TrackEvent.Subscribed, (track: RemoteTrack) => {
         | 
| 60 | 
            +
                  this.emit(ParticipantEvent.TrackSubscribed, track, publication);
         | 
| 61 | 
            +
                });
         | 
| 62 | 
            +
                publication.on(TrackEvent.Unsubscribed, (previousTrack: RemoteTrack) => {
         | 
| 63 | 
            +
                  this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication);
         | 
| 54 64 | 
             
                });
         | 
| 55 65 | 
             
              }
         | 
| 56 66 |  | 
| @@ -156,8 +166,6 @@ export default class RemoteParticipant extends Participant { | |
| 156 166 | 
             
                track.start();
         | 
| 157 167 |  | 
| 158 168 | 
             
                publication.setTrack(track);
         | 
| 159 | 
            -
                // subscription means participant has permissions to subscribe
         | 
| 160 | 
            -
                publication._allowed = true;
         | 
| 161 169 | 
             
                // set participant volume on new microphone tracks
         | 
| 162 170 | 
             
                if (
         | 
| 163 171 | 
             
                  this.volume !== undefined &&
         | 
| @@ -166,7 +174,6 @@ export default class RemoteParticipant extends Participant { | |
| 166 174 | 
             
                ) {
         | 
| 167 175 | 
             
                  track.setVolume(this.volume);
         | 
| 168 176 | 
             
                }
         | 
| 169 | 
            -
                this.emit(ParticipantEvent.TrackSubscribed, track, publication);
         | 
| 170 177 |  | 
| 171 178 | 
             
                return publication;
         | 
| 172 179 | 
             
              }
         | 
| @@ -251,13 +258,8 @@ export default class RemoteParticipant extends Participant { | |
| 251 258 | 
             
                // also send unsubscribe, if track is actively subscribed
         | 
| 252 259 | 
             
                const { track } = publication;
         | 
| 253 260 | 
             
                if (track) {
         | 
| 254 | 
            -
                  const { isSubscribed } = publication;
         | 
| 255 261 | 
             
                  track.stop();
         | 
| 256 262 | 
             
                  publication.setTrack(undefined);
         | 
| 257 | 
            -
                  // always send unsubscribed, since apps may rely on this
         | 
| 258 | 
            -
                  if (isSubscribed) {
         | 
| 259 | 
            -
                    this.emit(ParticipantEvent.TrackUnsubscribed, track, publication);
         | 
| 260 | 
            -
                  }
         | 
| 261 263 | 
             
                }
         | 
| 262 264 | 
             
                if (sendUnpublish) {
         | 
| 263 265 | 
             
                  this.emit(ParticipantEvent.TrackUnpublished, publication);
         | 
| @@ -106,7 +106,7 @@ export function computeVideoEncodings( | |
| 106 106 | 
             
                      encodings.push({
         | 
| 107 107 | 
             
                        rid: videoRids[2 - i],
         | 
| 108 108 | 
             
                        scaleResolutionDownBy: 2 ** i,
         | 
| 109 | 
            -
                        maxBitrate: videoEncoding ? videoEncoding.maxBitrate /  | 
| 109 | 
            +
                        maxBitrate: videoEncoding ? videoEncoding.maxBitrate / 3 ** i : 0,
         | 
| 110 110 | 
             
                        /* @ts-ignore */
         | 
| 111 111 | 
             
                        maxFramerate: original.encoding.maxFramerate,
         | 
| 112 112 | 
             
                        /* @ts-ignore */
         | 
| @@ -117,6 +117,8 @@ export default class LocalTrack extends Track { | |
| 117 117 | 
             
                }
         | 
| 118 118 | 
             
                this._mediaStreamTrack = track;
         | 
| 119 119 |  | 
| 120 | 
            +
                await this.resumeUpstream();
         | 
| 121 | 
            +
             | 
| 120 122 | 
             
                this.attachedElements.forEach((el) => {
         | 
| 121 123 | 
             
                  attachToElement(track, el);
         | 
| 122 124 | 
             
                });
         | 
| @@ -166,6 +168,8 @@ export default class LocalTrack extends Track { | |
| 166 168 |  | 
| 167 169 | 
             
                this._mediaStreamTrack = newTrack;
         | 
| 168 170 |  | 
| 171 | 
            +
                await this.resumeUpstream();
         | 
| 172 | 
            +
             | 
| 169 173 | 
             
                this.attachedElements.forEach((el) => {
         | 
| 170 174 | 
             
                  attachToElement(newTrack, el);
         | 
| 171 175 | 
             
                });
         | 
| @@ -11,7 +11,7 @@ export default class RemoteTrackPublication extends TrackPublication { | |
| 11 11 | 
             
              track?: RemoteTrack;
         | 
| 12 12 |  | 
| 13 13 | 
             
              /** @internal */
         | 
| 14 | 
            -
               | 
| 14 | 
            +
              protected allowed = true;
         | 
| 15 15 |  | 
| 16 16 | 
             
              // keeps track of client's desire to subscribe to a track
         | 
| 17 17 | 
             
              protected subscribed?: boolean;
         | 
| @@ -28,6 +28,9 @@ export default class RemoteTrackPublication extends TrackPublication { | |
| 28 28 | 
             
               */
         | 
| 29 29 | 
             
              setSubscribed(subscribed: boolean) {
         | 
| 30 30 | 
             
                this.subscribed = subscribed;
         | 
| 31 | 
            +
                // reset allowed status when desired subscription state changes
         | 
| 32 | 
            +
                // server will notify client via signal message if it's not allowed
         | 
| 33 | 
            +
                this.allowed = true;
         | 
| 31 34 |  | 
| 32 35 | 
             
                const sub: UpdateSubscription = {
         | 
| 33 36 | 
             
                  trackSids: [this.trackSid],
         | 
| @@ -46,11 +49,11 @@ export default class RemoteTrackPublication extends TrackPublication { | |
| 46 49 |  | 
| 47 50 | 
             
              get subscriptionStatus(): TrackPublication.SubscriptionStatus {
         | 
| 48 51 | 
             
                if (this.subscribed === false || !super.isSubscribed) {
         | 
| 52 | 
            +
                  if (!this.allowed) {
         | 
| 53 | 
            +
                    return TrackPublication.SubscriptionStatus.NotAllowed;
         | 
| 54 | 
            +
                  }
         | 
| 49 55 | 
             
                  return TrackPublication.SubscriptionStatus.Unsubscribed;
         | 
| 50 56 | 
             
                }
         | 
| 51 | 
            -
                if (!this._allowed) {
         | 
| 52 | 
            -
                  return TrackPublication.SubscriptionStatus.NotAllowed;
         | 
| 53 | 
            -
                }
         | 
| 54 57 | 
             
                return TrackPublication.SubscriptionStatus.Subscribed;
         | 
| 55 58 | 
             
              }
         | 
| 56 59 |  | 
| @@ -61,9 +64,6 @@ export default class RemoteTrackPublication extends TrackPublication { | |
| 61 64 | 
             
                if (this.subscribed === false) {
         | 
| 62 65 | 
             
                  return false;
         | 
| 63 66 | 
             
                }
         | 
| 64 | 
            -
                if (!this._allowed) {
         | 
| 65 | 
            -
                  return false;
         | 
| 66 | 
            -
                }
         | 
| 67 67 | 
             
                return super.isSubscribed;
         | 
| 68 68 | 
             
              }
         | 
| 69 69 |  | 
| @@ -127,11 +127,13 @@ export default class RemoteTrackPublication extends TrackPublication { | |
| 127 127 |  | 
| 128 128 | 
             
              /** @internal */
         | 
| 129 129 | 
             
              setTrack(track?: Track) {
         | 
| 130 | 
            -
                 | 
| 130 | 
            +
                const prevStatus = this.subscriptionStatus;
         | 
| 131 | 
            +
                const prevTrack = this.track;
         | 
| 132 | 
            +
                if (prevTrack) {
         | 
| 131 133 | 
             
                  // unregister listener
         | 
| 132 | 
            -
                   | 
| 133 | 
            -
                   | 
| 134 | 
            -
                   | 
| 134 | 
            +
                  prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
         | 
| 135 | 
            +
                  prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
         | 
| 136 | 
            +
                  prevTrack.off(TrackEvent.Ended, this.handleEnded);
         | 
| 135 137 | 
             
                }
         | 
| 136 138 | 
             
                super.setTrack(track);
         | 
| 137 139 | 
             
                if (track) {
         | 
| @@ -140,6 +142,22 @@ export default class RemoteTrackPublication extends TrackPublication { | |
| 140 142 | 
             
                  track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
         | 
| 141 143 | 
             
                  track.on(TrackEvent.Ended, this.handleEnded);
         | 
| 142 144 | 
             
                }
         | 
| 145 | 
            +
                this.emitSubscriptionUpdateIfChanged(prevStatus);
         | 
| 146 | 
            +
                if (!!track !== !!prevTrack) {
         | 
| 147 | 
            +
                  // when undefined status changes, there's a subscription changed event
         | 
| 148 | 
            +
                  if (track) {
         | 
| 149 | 
            +
                    this.emit(TrackEvent.Subscribed, track);
         | 
| 150 | 
            +
                  } else {
         | 
| 151 | 
            +
                    this.emit(TrackEvent.Unsubscribed, prevTrack);
         | 
| 152 | 
            +
                  }
         | 
| 153 | 
            +
                }
         | 
| 154 | 
            +
              }
         | 
| 155 | 
            +
             | 
| 156 | 
            +
              /** @internal */
         | 
| 157 | 
            +
              setAllowed(allowed: boolean) {
         | 
| 158 | 
            +
                const prevStatus = this.subscriptionStatus;
         | 
| 159 | 
            +
                this.allowed = allowed;
         | 
| 160 | 
            +
                this.emitSubscriptionUpdateIfChanged(prevStatus);
         | 
| 143 161 | 
             
              }
         | 
| 144 162 |  | 
| 145 163 | 
             
              /** @internal */
         | 
| @@ -149,6 +167,14 @@ export default class RemoteTrackPublication extends TrackPublication { | |
| 149 167 | 
             
                this.track?.setMuted(info.muted);
         | 
| 150 168 | 
             
              }
         | 
| 151 169 |  | 
| 170 | 
            +
              private emitSubscriptionUpdateIfChanged(previousStatus: TrackPublication.SubscriptionStatus) {
         | 
| 171 | 
            +
                const currentStatus = this.subscriptionStatus;
         | 
| 172 | 
            +
                if (previousStatus === currentStatus) {
         | 
| 173 | 
            +
                  return;
         | 
| 174 | 
            +
                }
         | 
| 175 | 
            +
                this.emit(TrackEvent.SubscriptionPermissionChanged, currentStatus, previousStatus);
         | 
| 176 | 
            +
              }
         | 
| 177 | 
            +
             | 
| 152 178 | 
             
              private isManualOperationAllowed(): boolean {
         | 
| 153 179 | 
             
                if (this.isAdaptiveStream) {
         | 
| 154 180 | 
             
                  log.warn('adaptive stream is enabled, cannot change track settings', {
         |