livekit-client 1.15.0 → 1.15.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 +5488 -5235
 - 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/room/PCTransport.d.ts +9 -4
 - package/dist/src/room/PCTransport.d.ts.map +1 -1
 - package/dist/src/room/PCTransportManager.d.ts +51 -0
 - package/dist/src/room/PCTransportManager.d.ts.map +1 -0
 - package/dist/src/room/RTCEngine.d.ts +8 -5
 - package/dist/src/room/RTCEngine.d.ts.map +1 -1
 - package/dist/src/room/Room.d.ts +9 -0
 - package/dist/src/room/Room.d.ts.map +1 -1
 - package/dist/src/room/events.d.ts +10 -0
 - package/dist/src/room/events.d.ts.map +1 -1
 - package/dist/src/room/participant/LocalParticipant.d.ts +0 -5
 - package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
 - package/dist/src/room/track/Track.d.ts +6 -2
 - package/dist/src/room/track/Track.d.ts.map +1 -1
 - package/dist/src/room/track/utils.d.ts +3 -0
 - package/dist/src/room/track/utils.d.ts.map +1 -1
 - package/dist/ts4.2/src/room/PCTransport.d.ts +9 -4
 - package/dist/ts4.2/src/room/PCTransportManager.d.ts +51 -0
 - package/dist/ts4.2/src/room/RTCEngine.d.ts +8 -5
 - package/dist/ts4.2/src/room/Room.d.ts +9 -0
 - package/dist/ts4.2/src/room/events.d.ts +10 -0
 - package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +0 -5
 - package/dist/ts4.2/src/room/track/Track.d.ts +6 -2
 - package/dist/ts4.2/src/room/track/utils.d.ts +3 -0
 - package/package.json +1 -1
 - package/src/connectionHelper/checks/webrtc.ts +2 -2
 - package/src/room/PCTransport.ts +62 -29
 - package/src/room/PCTransportManager.ts +336 -0
 - package/src/room/RTCEngine.ts +178 -246
 - package/src/room/Room.ts +49 -46
 - package/src/room/events.ts +11 -0
 - package/src/room/participant/LocalParticipant.ts +15 -52
 - package/src/room/track/Track.ts +30 -9
 - package/src/room/track/utils.ts +19 -0
 
    
        package/src/room/Room.ts
    CHANGED
    
    | 
         @@ -2,7 +2,6 @@ import { protoInt64 } from '@bufbuild/protobuf'; 
     | 
|
| 
       2 
2 
     | 
    
         
             
            import { EventEmitter } from 'events';
         
     | 
| 
       3 
3 
     | 
    
         
             
            import type TypedEmitter from 'typed-emitter';
         
     | 
| 
       4 
4 
     | 
    
         
             
            import 'webrtc-adapter';
         
     | 
| 
       5 
     | 
    
         
            -
            import { toProtoSessionDescription } from '../api/SignalClient';
         
     | 
| 
       6 
5 
     | 
    
         
             
            import { EncryptionEvent } from '../e2ee';
         
     | 
| 
       7 
6 
     | 
    
         
             
            import { E2EEManager } from '../e2ee/E2eeManager';
         
     | 
| 
       8 
7 
     | 
    
         
             
            import log from '../logger';
         
     | 
| 
         @@ -35,8 +34,6 @@ import { 
     | 
|
| 
       35 
34 
     | 
    
         
             
              StreamStateUpdate,
         
     | 
| 
       36 
35 
     | 
    
         
             
              SubscriptionPermissionUpdate,
         
     | 
| 
       37 
36 
     | 
    
         
             
              SubscriptionResponse,
         
     | 
| 
       38 
     | 
    
         
            -
              SyncState,
         
     | 
| 
       39 
     | 
    
         
            -
              UpdateSubscription,
         
     | 
| 
       40 
37 
     | 
    
         
             
            } from '../proto/livekit_rtc_pb';
         
     | 
| 
       41 
38 
     | 
    
         
             
            import { getBrowser } from '../utils/browserParser';
         
     | 
| 
       42 
39 
     | 
    
         
             
            import DeviceManager from './DeviceManager';
         
     | 
| 
         @@ -152,6 +149,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) 
     | 
|
| 
       152 
149 
     | 
    
         | 
| 
       153 
150 
     | 
    
         
             
              private regionUrl?: string;
         
     | 
| 
       154 
151 
     | 
    
         | 
| 
      
 152 
     | 
    
         
            +
              private isVideoPlaybackBlocked: boolean = false;
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
       155 
154 
     | 
    
         
             
              /**
         
     | 
| 
       156 
155 
     | 
    
         
             
               * Creates a new Room, the primary construct for a LiveKit session.
         
     | 
| 
       157 
156 
     | 
    
         
             
               * @param options
         
     | 
| 
         @@ -865,6 +864,22 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) 
     | 
|
| 
       865 
864 
     | 
    
         
             
                }
         
     | 
| 
       866 
865 
     | 
    
         
             
              };
         
     | 
| 
       867 
866 
     | 
    
         | 
| 
      
 867 
     | 
    
         
            +
              startVideo = async () => {
         
     | 
| 
      
 868 
     | 
    
         
            +
                for (const p of this.participants.values()) {
         
     | 
| 
      
 869 
     | 
    
         
            +
                  p.videoTracks.forEach((tr) => {
         
     | 
| 
      
 870 
     | 
    
         
            +
                    tr.track?.attachedElements.forEach((el) => {
         
     | 
| 
      
 871 
     | 
    
         
            +
                      el.play().catch((e) => {
         
     | 
| 
      
 872 
     | 
    
         
            +
                        if (e.name === 'NotAllowedError') {
         
     | 
| 
      
 873 
     | 
    
         
            +
                          log.warn(
         
     | 
| 
      
 874 
     | 
    
         
            +
                            'Resuming video playback failed, make sure you call `startVideo` directly in a user gesture handler',
         
     | 
| 
      
 875 
     | 
    
         
            +
                          );
         
     | 
| 
      
 876 
     | 
    
         
            +
                        }
         
     | 
| 
      
 877 
     | 
    
         
            +
                      });
         
     | 
| 
      
 878 
     | 
    
         
            +
                    });
         
     | 
| 
      
 879 
     | 
    
         
            +
                  });
         
     | 
| 
      
 880 
     | 
    
         
            +
                }
         
     | 
| 
      
 881 
     | 
    
         
            +
              };
         
     | 
| 
      
 882 
     | 
    
         
            +
             
     | 
| 
       868 
883 
     | 
    
         
             
              /**
         
     | 
| 
       869 
884 
     | 
    
         
             
               * Returns true if audio playback is enabled
         
     | 
| 
       870 
885 
     | 
    
         
             
               */
         
     | 
| 
         @@ -872,6 +887,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) 
     | 
|
| 
       872 
887 
     | 
    
         
             
                return this.audioEnabled;
         
     | 
| 
       873 
888 
     | 
    
         
             
              }
         
     | 
| 
       874 
889 
     | 
    
         | 
| 
      
 890 
     | 
    
         
            +
              /**
         
     | 
| 
      
 891 
     | 
    
         
            +
               * Returns true if video playback is enabled
         
     | 
| 
      
 892 
     | 
    
         
            +
               */
         
     | 
| 
      
 893 
     | 
    
         
            +
              get canPlaybackVideo(): boolean {
         
     | 
| 
      
 894 
     | 
    
         
            +
                return !this.isVideoPlaybackBlocked;
         
     | 
| 
      
 895 
     | 
    
         
            +
              }
         
     | 
| 
      
 896 
     | 
    
         
            +
             
     | 
| 
       875 
897 
     | 
    
         
             
              /**
         
     | 
| 
       876 
898 
     | 
    
         
             
               * Returns the active audio output device used in this room.
         
     | 
| 
       877 
899 
     | 
    
         
             
               * @return the previously successfully set audio output device ID or an empty string if the default device is used.
         
     | 
| 
         @@ -1384,6 +1406,20 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) 
     | 
|
| 
       1384 
1406 
     | 
    
         
             
                this.emit(RoomEvent.AudioPlaybackStatusChanged, false);
         
     | 
| 
       1385 
1407 
     | 
    
         
             
              };
         
     | 
| 
       1386 
1408 
     | 
    
         | 
| 
      
 1409 
     | 
    
         
            +
              private handleVideoPlaybackStarted = () => {
         
     | 
| 
      
 1410 
     | 
    
         
            +
                if (this.isVideoPlaybackBlocked) {
         
     | 
| 
      
 1411 
     | 
    
         
            +
                  this.isVideoPlaybackBlocked = false;
         
     | 
| 
      
 1412 
     | 
    
         
            +
                  this.emit(RoomEvent.VideoPlaybackStatusChanged, true);
         
     | 
| 
      
 1413 
     | 
    
         
            +
                }
         
     | 
| 
      
 1414 
     | 
    
         
            +
              };
         
     | 
| 
      
 1415 
     | 
    
         
            +
             
     | 
| 
      
 1416 
     | 
    
         
            +
              private handleVideoPlaybackFailed = () => {
         
     | 
| 
      
 1417 
     | 
    
         
            +
                if (!this.isVideoPlaybackBlocked) {
         
     | 
| 
      
 1418 
     | 
    
         
            +
                  this.isVideoPlaybackBlocked = true;
         
     | 
| 
      
 1419 
     | 
    
         
            +
                  this.emit(RoomEvent.VideoPlaybackStatusChanged, false);
         
     | 
| 
      
 1420 
     | 
    
         
            +
                }
         
     | 
| 
      
 1421 
     | 
    
         
            +
              };
         
     | 
| 
      
 1422 
     | 
    
         
            +
             
     | 
| 
       1387 
1423 
     | 
    
         
             
              private handleDeviceChange = async () => {
         
     | 
| 
       1388 
1424 
     | 
    
         
             
                this.emit(RoomEvent.MediaDevicesChanged);
         
     | 
| 
       1389 
1425 
     | 
    
         
             
              };
         
     | 
| 
         @@ -1487,6 +1523,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) 
     | 
|
| 
       1487 
1523 
     | 
    
         
             
                      if (track.kind === Track.Kind.Audio) {
         
     | 
| 
       1488 
1524 
     | 
    
         
             
                        track.on(TrackEvent.AudioPlaybackStarted, this.handleAudioPlaybackStarted);
         
     | 
| 
       1489 
1525 
     | 
    
         
             
                        track.on(TrackEvent.AudioPlaybackFailed, this.handleAudioPlaybackFailed);
         
     | 
| 
      
 1526 
     | 
    
         
            +
                      } else if (track.kind === Track.Kind.Video) {
         
     | 
| 
      
 1527 
     | 
    
         
            +
                        track.on(TrackEvent.VideoPlaybackFailed, this.handleVideoPlaybackFailed);
         
     | 
| 
      
 1528 
     | 
    
         
            +
                        track.on(TrackEvent.VideoPlaybackStarted, this.handleVideoPlaybackStarted);
         
     | 
| 
       1490 
1529 
     | 
    
         
             
                      }
         
     | 
| 
       1491 
1530 
     | 
    
         
             
                      this.emit(RoomEvent.TrackSubscribed, track, publication, participant);
         
     | 
| 
       1492 
1531 
     | 
    
         
             
                    },
         
     | 
| 
         @@ -1551,49 +1590,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) 
     | 
|
| 
       1551 
1590 
     | 
    
         
             
              }
         
     | 
| 
       1552 
1591 
     | 
    
         | 
| 
       1553 
1592 
     | 
    
         
             
              private sendSyncState() {
         
     | 
| 
       1554 
     | 
    
         
            -
                const  
     | 
| 
       1555 
     | 
    
         
            -
             
     | 
| 
       1556 
     | 
    
         
            -
             
     | 
| 
       1557 
     | 
    
         
            -
                 
     | 
| 
       1558 
     | 
    
         
            -
             
     | 
| 
       1559 
     | 
    
         
            -
                 
     | 
| 
       1560 
     | 
    
         
            -
             
     | 
| 
       1561 
     | 
    
         
            -
                /* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks,
         
     | 
| 
       1562 
     | 
    
         
            -
                      in this case, we send unsub tracks, so server add all tracks to this
         
     | 
| 
       1563 
     | 
    
         
            -
                      subscribe pc and unsub special tracks from it.
         
     | 
| 
       1564 
     | 
    
         
            -
                   2. autosubscribe off, we send subscribed tracks.
         
     | 
| 
       1565 
     | 
    
         
            -
                */
         
     | 
| 
       1566 
     | 
    
         
            -
                const autoSubscribe = this.connOptions?.autoSubscribe ?? true;
         
     | 
| 
       1567 
     | 
    
         
            -
                const trackSids = new Array<string>();
         
     | 
| 
       1568 
     | 
    
         
            -
                this.participants.forEach((participant) => {
         
     | 
| 
       1569 
     | 
    
         
            -
                  participant.tracks.forEach((track) => {
         
     | 
| 
       1570 
     | 
    
         
            -
                    if (track.isDesired !== autoSubscribe) {
         
     | 
| 
       1571 
     | 
    
         
            -
                      trackSids.push(track.trackSid);
         
     | 
| 
       1572 
     | 
    
         
            -
                    }
         
     | 
| 
       1573 
     | 
    
         
            -
                  });
         
     | 
| 
       1574 
     | 
    
         
            -
                });
         
     | 
| 
       1575 
     | 
    
         
            -
             
     | 
| 
       1576 
     | 
    
         
            -
                this.engine.client.sendSyncState(
         
     | 
| 
       1577 
     | 
    
         
            -
                  new SyncState({
         
     | 
| 
       1578 
     | 
    
         
            -
                    answer: toProtoSessionDescription({
         
     | 
| 
       1579 
     | 
    
         
            -
                      sdp: previousAnswer.sdp,
         
     | 
| 
       1580 
     | 
    
         
            -
                      type: previousAnswer.type,
         
     | 
| 
       1581 
     | 
    
         
            -
                    }),
         
     | 
| 
       1582 
     | 
    
         
            -
                    offer: previousOffer
         
     | 
| 
       1583 
     | 
    
         
            -
                      ? toProtoSessionDescription({
         
     | 
| 
       1584 
     | 
    
         
            -
                          sdp: previousOffer.sdp,
         
     | 
| 
       1585 
     | 
    
         
            -
                          type: previousOffer.type,
         
     | 
| 
       1586 
     | 
    
         
            -
                        })
         
     | 
| 
       1587 
     | 
    
         
            -
                      : undefined,
         
     | 
| 
       1588 
     | 
    
         
            -
                    subscription: new UpdateSubscription({
         
     | 
| 
       1589 
     | 
    
         
            -
                      trackSids,
         
     | 
| 
       1590 
     | 
    
         
            -
                      subscribe: !autoSubscribe,
         
     | 
| 
       1591 
     | 
    
         
            -
                      participantTracks: [],
         
     | 
| 
       1592 
     | 
    
         
            -
                    }),
         
     | 
| 
       1593 
     | 
    
         
            -
                    publishTracks: this.localParticipant.publishedTracksInfo(),
         
     | 
| 
       1594 
     | 
    
         
            -
                    dataChannels: this.localParticipant.dataChannelsInfo(),
         
     | 
| 
       1595 
     | 
    
         
            -
                  }),
         
     | 
| 
       1596 
     | 
    
         
            -
                );
         
     | 
| 
      
 1593 
     | 
    
         
            +
                const remoteTracks = Array.from(this.participants.values()).reduce((acc, participant) => {
         
     | 
| 
      
 1594 
     | 
    
         
            +
                  acc.push(...(participant.getTracks() as RemoteTrackPublication[])); // FIXME would be nice to have this return RemoteTrackPublications directly instead of the type cast
         
     | 
| 
      
 1595 
     | 
    
         
            +
                  return acc;
         
     | 
| 
      
 1596 
     | 
    
         
            +
                }, [] as RemoteTrackPublication[]);
         
     | 
| 
      
 1597 
     | 
    
         
            +
                const localTracks = this.localParticipant.getTracks() as LocalTrackPublication[]; // FIXME would be nice to have this return LocalTrackPublications directly instead of the type cast
         
     | 
| 
      
 1598 
     | 
    
         
            +
                this.engine.sendSyncState(remoteTracks, localTracks);
         
     | 
| 
       1597 
1599 
     | 
    
         
             
              }
         
     | 
| 
       1598 
1600 
     | 
    
         | 
| 
       1599 
1601 
     | 
    
         
             
              /**
         
     | 
| 
         @@ -1929,6 +1931,7 @@ export type RoomEventCallbacks = { 
     | 
|
| 
       1929 
1931 
     | 
    
         
             
                participant: RemoteParticipant,
         
     | 
| 
       1930 
1932 
     | 
    
         
             
              ) => void;
         
     | 
| 
       1931 
1933 
     | 
    
         
             
              audioPlaybackChanged: (playing: boolean) => void;
         
     | 
| 
      
 1934 
     | 
    
         
            +
              videoPlaybackChanged: (playing: boolean) => void;
         
     | 
| 
       1932 
1935 
     | 
    
         
             
              signalConnected: () => void;
         
     | 
| 
       1933 
1936 
     | 
    
         
             
              recordingStatusChanged: (recording: boolean) => void;
         
     | 
| 
       1934 
1937 
     | 
    
         
             
              participantEncryptionStatusChanged: (encrypted: boolean, participant?: Participant) => void;
         
     | 
    
        package/src/room/events.ts
    CHANGED
    
    | 
         @@ -245,6 +245,13 @@ export enum RoomEvent { 
     | 
|
| 
       245 
245 
     | 
    
         
             
               */
         
     | 
| 
       246 
246 
     | 
    
         
             
              AudioPlaybackStatusChanged = 'audioPlaybackChanged',
         
     | 
| 
       247 
247 
     | 
    
         | 
| 
      
 248 
     | 
    
         
            +
              /**
         
     | 
| 
      
 249 
     | 
    
         
            +
               * LiveKit will attempt to autoplay all video tracks when you attach them to
         
     | 
| 
      
 250 
     | 
    
         
            +
               * a video element. However, if that fails, we'll notify you via VideoPlaybackStatusChanged.
         
     | 
| 
      
 251 
     | 
    
         
            +
               * Calling `room.startVideo()` in a user gesture event handler will resume the video playback.
         
     | 
| 
      
 252 
     | 
    
         
            +
               */
         
     | 
| 
      
 253 
     | 
    
         
            +
              VideoPlaybackStatusChanged = 'videoPlaybackChanged',
         
     | 
| 
      
 254 
     | 
    
         
            +
             
     | 
| 
       248 
255 
     | 
    
         
             
              /**
         
     | 
| 
       249 
256 
     | 
    
         
             
               * When we have encountered an error while attempting to create a track.
         
     | 
| 
       250 
257 
     | 
    
         
             
               * The errors take place in getUserMedia().
         
     | 
| 
         @@ -510,6 +517,10 @@ export enum TrackEvent { 
     | 
|
| 
       510 
517 
     | 
    
         
             
              /** @internal */
         
     | 
| 
       511 
518 
     | 
    
         
             
              VideoDimensionsChanged = 'videoDimensionsChanged',
         
     | 
| 
       512 
519 
     | 
    
         
             
              /** @internal */
         
     | 
| 
      
 520 
     | 
    
         
            +
              VideoPlaybackStarted = 'videoPlaybackStarted',
         
     | 
| 
      
 521 
     | 
    
         
            +
              /** @internal */
         
     | 
| 
      
 522 
     | 
    
         
            +
              VideoPlaybackFailed = 'videoPlaybackFailed',
         
     | 
| 
      
 523 
     | 
    
         
            +
              /** @internal */
         
     | 
| 
       513 
524 
     | 
    
         
             
              ElementAttached = 'elementAttached',
         
     | 
| 
       514 
525 
     | 
    
         
             
              /** @internal */
         
     | 
| 
       515 
526 
     | 
    
         
             
              ElementDetached = 'elementDetached',
         
     | 
| 
         @@ -10,13 +10,11 @@ import { 
     | 
|
| 
       10 
10 
     | 
    
         
             
            } from '../../proto/livekit_models_pb';
         
     | 
| 
       11 
11 
     | 
    
         
             
            import {
         
     | 
| 
       12 
12 
     | 
    
         
             
              AddTrackRequest,
         
     | 
| 
       13 
     | 
    
         
            -
              DataChannelInfo,
         
     | 
| 
       14 
     | 
    
         
            -
              SignalTarget,
         
     | 
| 
       15 
13 
     | 
    
         
             
              SimulcastCodec,
         
     | 
| 
       16 
14 
     | 
    
         
             
              SubscribedQualityUpdate,
         
     | 
| 
       17 
     | 
    
         
            -
              TrackPublishedResponse,
         
     | 
| 
       18 
15 
     | 
    
         
             
              TrackUnpublishedResponse,
         
     | 
| 
       19 
16 
     | 
    
         
             
            } from '../../proto/livekit_rtc_pb';
         
     | 
| 
      
 17 
     | 
    
         
            +
            import { PCTransportState } from '../PCTransportManager';
         
     | 
| 
       20 
18 
     | 
    
         
             
            import type RTCEngine from '../RTCEngine';
         
     | 
| 
       21 
19 
     | 
    
         
             
            import { defaultVideoCodec } from '../defaults';
         
     | 
| 
       22 
20 
     | 
    
         
             
            import { DeviceUnsupportedError, TrackInvalidError, UnexpectedConnectionState } from '../errors';
         
     | 
| 
         @@ -695,7 +693,12 @@ export default class LocalParticipant extends Participant { 
     | 
|
| 
       695 
693 
     | 
    
         
             
                    if (opts.backupCodec === true) {
         
     | 
| 
       696 
694 
     | 
    
         
             
                      opts.backupCodec = { codec: defaultVideoCodec };
         
     | 
| 
       697 
695 
     | 
    
         
             
                    }
         
     | 
| 
       698 
     | 
    
         
            -
                    if ( 
     | 
| 
      
 696 
     | 
    
         
            +
                    if (
         
     | 
| 
      
 697 
     | 
    
         
            +
                      opts.backupCodec &&
         
     | 
| 
      
 698 
     | 
    
         
            +
                      videoCodec !== opts.backupCodec.codec &&
         
     | 
| 
      
 699 
     | 
    
         
            +
                      // TODO remove this once e2ee is supported for backup codecs
         
     | 
| 
      
 700 
     | 
    
         
            +
                      req.encryption === Encryption_Type.NONE
         
     | 
| 
      
 701 
     | 
    
         
            +
                    ) {
         
     | 
| 
       699 
702 
     | 
    
         
             
                      // multi-codec simulcast requires dynacast
         
     | 
| 
       700 
703 
     | 
    
         
             
                      if (!this.roomOptions.dynacast) {
         
     | 
| 
       701 
704 
     | 
    
         
             
                        this.roomOptions.dynacast = true;
         
     | 
| 
         @@ -766,8 +769,8 @@ export default class LocalParticipant extends Participant { 
     | 
|
| 
       766 
769 
     | 
    
         
             
                publication.options = opts;
         
     | 
| 
       767 
770 
     | 
    
         
             
                track.sid = ti.sid;
         
     | 
| 
       768 
771 
     | 
    
         | 
| 
       769 
     | 
    
         
            -
                if (!this.engine. 
     | 
| 
       770 
     | 
    
         
            -
                  throw new UnexpectedConnectionState(' 
     | 
| 
      
 772 
     | 
    
         
            +
                if (!this.engine.pcManager) {
         
     | 
| 
      
 773 
     | 
    
         
            +
                  throw new UnexpectedConnectionState('pcManager is not ready');
         
     | 
| 
       771 
774 
     | 
    
         
             
                }
         
     | 
| 
       772 
775 
     | 
    
         
             
                log.debug(`publishing ${track.kind} with encodings`, { encodings, trackInfo: ti });
         
     | 
| 
       773 
776 
     | 
    
         | 
| 
         @@ -783,21 +786,21 @@ export default class LocalParticipant extends Participant { 
     | 
|
| 
       783 
786 
     | 
    
         
             
                       fix the issue.
         
     | 
| 
       784 
787 
     | 
    
         
             
                     */
         
     | 
| 
       785 
788 
     | 
    
         
             
                    let trackTransceiver: RTCRtpTransceiver | undefined = undefined;
         
     | 
| 
       786 
     | 
    
         
            -
                    for (const transceiver of this.engine.publisher.getTransceivers()) {
         
     | 
| 
      
 789 
     | 
    
         
            +
                    for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
         
     | 
| 
       787 
790 
     | 
    
         
             
                      if (transceiver.sender === track.sender) {
         
     | 
| 
       788 
791 
     | 
    
         
             
                        trackTransceiver = transceiver;
         
     | 
| 
       789 
792 
     | 
    
         
             
                        break;
         
     | 
| 
       790 
793 
     | 
    
         
             
                      }
         
     | 
| 
       791 
794 
     | 
    
         
             
                    }
         
     | 
| 
       792 
795 
     | 
    
         
             
                    if (trackTransceiver) {
         
     | 
| 
       793 
     | 
    
         
            -
                      this.engine.publisher.setTrackCodecBitrate({
         
     | 
| 
      
 796 
     | 
    
         
            +
                      this.engine.pcManager.publisher.setTrackCodecBitrate({
         
     | 
| 
       794 
797 
     | 
    
         
             
                        transceiver: trackTransceiver,
         
     | 
| 
       795 
798 
     | 
    
         
             
                        codec: 'opus',
         
     | 
| 
       796 
799 
     | 
    
         
             
                        maxbr: encodings[0]?.maxBitrate ? encodings[0].maxBitrate / 1000 : 0,
         
     | 
| 
       797 
800 
     | 
    
         
             
                      });
         
     | 
| 
       798 
801 
     | 
    
         
             
                    }
         
     | 
| 
       799 
802 
     | 
    
         
             
                  } else if (track.codec && isSVCCodec(track.codec) && encodings[0]?.maxBitrate) {
         
     | 
| 
       800 
     | 
    
         
            -
                    this.engine.publisher.setTrackCodecBitrate({
         
     | 
| 
      
 803 
     | 
    
         
            +
                    this.engine.pcManager.publisher.setTrackCodecBitrate({
         
     | 
| 
       801 
804 
     | 
    
         
             
                      cid: req.cid,
         
     | 
| 
       802 
805 
     | 
    
         
             
                      codec: track.codec,
         
     | 
| 
       803 
806 
     | 
    
         
             
                      maxbr: encodings[0].maxBitrate / 1000,
         
     | 
| 
         @@ -929,12 +932,12 @@ export default class LocalParticipant extends Participant { 
     | 
|
| 
       929 
932 
     | 
    
         
             
                const trackSender = track.sender;
         
     | 
| 
       930 
933 
     | 
    
         
             
                track.sender = undefined;
         
     | 
| 
       931 
934 
     | 
    
         
             
                if (
         
     | 
| 
       932 
     | 
    
         
            -
                  this.engine. 
     | 
| 
       933 
     | 
    
         
            -
                  this.engine. 
     | 
| 
      
 935 
     | 
    
         
            +
                  this.engine.pcManager &&
         
     | 
| 
      
 936 
     | 
    
         
            +
                  this.engine.pcManager.currentState < PCTransportState.FAILED &&
         
     | 
| 
       934 
937 
     | 
    
         
             
                  trackSender
         
     | 
| 
       935 
938 
     | 
    
         
             
                ) {
         
     | 
| 
       936 
939 
     | 
    
         
             
                  try {
         
     | 
| 
       937 
     | 
    
         
            -
                    for (const transceiver of this.engine.publisher.getTransceivers()) {
         
     | 
| 
      
 940 
     | 
    
         
            +
                    for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
         
     | 
| 
       938 
941 
     | 
    
         
             
                      // if sender is not currently sending (after replaceTrack(null))
         
     | 
| 
       939 
942 
     | 
    
         
             
                      // removeTrack would have no effect.
         
     | 
| 
       940 
943 
     | 
    
         
             
                      // to ensure we end up successfully removing the track, manually set
         
     | 
| 
         @@ -1310,44 +1313,4 @@ export default class LocalParticipant extends Participant { 
     | 
|
| 
       1310 
1313 
     | 
    
         
             
                });
         
     | 
| 
       1311 
1314 
     | 
    
         
             
                return publication;
         
     | 
| 
       1312 
1315 
     | 
    
         
             
              }
         
     | 
| 
       1313 
     | 
    
         
            -
             
     | 
| 
       1314 
     | 
    
         
            -
              /** @internal */
         
     | 
| 
       1315 
     | 
    
         
            -
              publishedTracksInfo(): TrackPublishedResponse[] {
         
     | 
| 
       1316 
     | 
    
         
            -
                const infos: TrackPublishedResponse[] = [];
         
     | 
| 
       1317 
     | 
    
         
            -
                this.tracks.forEach((track: LocalTrackPublication) => {
         
     | 
| 
       1318 
     | 
    
         
            -
                  if (track.track !== undefined) {
         
     | 
| 
       1319 
     | 
    
         
            -
                    infos.push(
         
     | 
| 
       1320 
     | 
    
         
            -
                      new TrackPublishedResponse({
         
     | 
| 
       1321 
     | 
    
         
            -
                        cid: track.track.mediaStreamID,
         
     | 
| 
       1322 
     | 
    
         
            -
                        track: track.trackInfo,
         
     | 
| 
       1323 
     | 
    
         
            -
                      }),
         
     | 
| 
       1324 
     | 
    
         
            -
                    );
         
     | 
| 
       1325 
     | 
    
         
            -
                  }
         
     | 
| 
       1326 
     | 
    
         
            -
                });
         
     | 
| 
       1327 
     | 
    
         
            -
                return infos;
         
     | 
| 
       1328 
     | 
    
         
            -
              }
         
     | 
| 
       1329 
     | 
    
         
            -
             
     | 
| 
       1330 
     | 
    
         
            -
              /** @internal */
         
     | 
| 
       1331 
     | 
    
         
            -
              dataChannelsInfo(): DataChannelInfo[] {
         
     | 
| 
       1332 
     | 
    
         
            -
                const infos: DataChannelInfo[] = [];
         
     | 
| 
       1333 
     | 
    
         
            -
                const getInfo = (dc: RTCDataChannel | undefined, target: SignalTarget) => {
         
     | 
| 
       1334 
     | 
    
         
            -
                  if (dc?.id !== undefined && dc.id !== null) {
         
     | 
| 
       1335 
     | 
    
         
            -
                    infos.push(
         
     | 
| 
       1336 
     | 
    
         
            -
                      new DataChannelInfo({
         
     | 
| 
       1337 
     | 
    
         
            -
                        label: dc.label,
         
     | 
| 
       1338 
     | 
    
         
            -
                        id: dc.id,
         
     | 
| 
       1339 
     | 
    
         
            -
                        target,
         
     | 
| 
       1340 
     | 
    
         
            -
                      }),
         
     | 
| 
       1341 
     | 
    
         
            -
                    );
         
     | 
| 
       1342 
     | 
    
         
            -
                  }
         
     | 
| 
       1343 
     | 
    
         
            -
                };
         
     | 
| 
       1344 
     | 
    
         
            -
                getInfo(this.engine.dataChannelForKind(DataPacket_Kind.LOSSY), SignalTarget.PUBLISHER);
         
     | 
| 
       1345 
     | 
    
         
            -
                getInfo(this.engine.dataChannelForKind(DataPacket_Kind.RELIABLE), SignalTarget.PUBLISHER);
         
     | 
| 
       1346 
     | 
    
         
            -
                getInfo(this.engine.dataChannelForKind(DataPacket_Kind.LOSSY, true), SignalTarget.SUBSCRIBER);
         
     | 
| 
       1347 
     | 
    
         
            -
                getInfo(
         
     | 
| 
       1348 
     | 
    
         
            -
                  this.engine.dataChannelForKind(DataPacket_Kind.RELIABLE, true),
         
     | 
| 
       1349 
     | 
    
         
            -
                  SignalTarget.SUBSCRIBER,
         
     | 
| 
       1350 
     | 
    
         
            -
                );
         
     | 
| 
       1351 
     | 
    
         
            -
                return infos;
         
     | 
| 
       1352 
     | 
    
         
            -
              }
         
     | 
| 
       1353 
1316 
     | 
    
         
             
            }
         
     | 
    
        package/src/room/track/Track.ts
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import { EventEmitter } from 'events';
         
     | 
| 
      
 2 
     | 
    
         
            +
            import { debounce } from 'ts-debounce';
         
     | 
| 
       2 
3 
     | 
    
         
             
            import type TypedEventEmitter from 'typed-emitter';
         
     | 
| 
       3 
4 
     | 
    
         
             
            import type { SignalClient } from '../../api/SignalClient';
         
     | 
| 
       4 
     | 
    
         
            -
            import log from '../../logger';
         
     | 
| 
       5 
5 
     | 
    
         
             
            import { TrackSource, TrackType } from '../../proto/livekit_models_pb';
         
     | 
| 
       6 
6 
     | 
    
         
             
            import { StreamState as ProtoStreamState } from '../../proto/livekit_rtc_pb';
         
     | 
| 
       7 
7 
     | 
    
         
             
            import { TrackEvent } from '../events';
         
     | 
| 
         @@ -113,6 +113,9 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter 
     | 
|
| 
       113 
113 
     | 
    
         | 
| 
       114 
114 
     | 
    
         
             
                if (!this.attachedElements.includes(element)) {
         
     | 
| 
       115 
115 
     | 
    
         
             
                  this.attachedElements.push(element);
         
     | 
| 
      
 116 
     | 
    
         
            +
                  // listen to suspend events in order to detect auto playback issues
         
     | 
| 
      
 117 
     | 
    
         
            +
                  element.addEventListener('suspend', this.handleElementSuspended);
         
     | 
| 
      
 118 
     | 
    
         
            +
                  element.addEventListener('playing', this.handleElementPlay);
         
     | 
| 
       116 
119 
     | 
    
         
             
                }
         
     | 
| 
       117 
120 
     | 
    
         | 
| 
       118 
121 
     | 
    
         
             
                // even if we believe it's already attached to the element, it's possible
         
     | 
| 
         @@ -130,11 +133,6 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter 
     | 
|
| 
       130 
133 
     | 
    
         
             
                      this.emit(TrackEvent.AudioPlaybackStarted);
         
     | 
| 
       131 
134 
     | 
    
         
             
                    })
         
     | 
| 
       132 
135 
     | 
    
         
             
                    .catch((e) => {
         
     | 
| 
       133 
     | 
    
         
            -
                      if (e.name === 'NotAllowedError') {
         
     | 
| 
       134 
     | 
    
         
            -
                        this.emit(TrackEvent.AudioPlaybackFailed, e);
         
     | 
| 
       135 
     | 
    
         
            -
                      } else {
         
     | 
| 
       136 
     | 
    
         
            -
                        log.warn('could not playback audio', e);
         
     | 
| 
       137 
     | 
    
         
            -
                      }
         
     | 
| 
       138 
136 
     | 
    
         
             
                      // If audio playback isn't allowed make sure we still play back the video
         
     | 
| 
       139 
137 
     | 
    
         
             
                      if (
         
     | 
| 
       140 
138 
     | 
    
         
             
                        element &&
         
     | 
| 
         @@ -172,6 +170,8 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter 
     | 
|
| 
       172 
170 
     | 
    
         
             
                    if (idx >= 0) {
         
     | 
| 
       173 
171 
     | 
    
         
             
                      this.attachedElements.splice(idx, 1);
         
     | 
| 
       174 
172 
     | 
    
         
             
                      this.recycleElement(element);
         
     | 
| 
      
 173 
     | 
    
         
            +
                      element.removeEventListener('suspend', this.handleElementSuspended);
         
     | 
| 
      
 174 
     | 
    
         
            +
                      element.removeEventListener('playing', this.handleElementPlay);
         
     | 
| 
       175 
175 
     | 
    
         
             
                      this.emit(TrackEvent.ElementDetached, element);
         
     | 
| 
       176 
176 
     | 
    
         
             
                    }
         
     | 
| 
       177 
177 
     | 
    
         
             
                    return element;
         
     | 
| 
         @@ -182,6 +182,8 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter 
     | 
|
| 
       182 
182 
     | 
    
         
             
                    detachTrack(this.mediaStreamTrack, elm);
         
     | 
| 
       183 
183 
     | 
    
         
             
                    detached.push(elm);
         
     | 
| 
       184 
184 
     | 
    
         
             
                    this.recycleElement(elm);
         
     | 
| 
      
 185 
     | 
    
         
            +
                    elm.removeEventListener('suspend', this.handleElementSuspended);
         
     | 
| 
      
 186 
     | 
    
         
            +
                    elm.removeEventListener('playing', this.handleElementPlay);
         
     | 
| 
       185 
187 
     | 
    
         
             
                    this.emit(TrackEvent.ElementDetached, elm);
         
     | 
| 
       186 
188 
     | 
    
         
             
                  });
         
     | 
| 
       187 
189 
     | 
    
         | 
| 
         @@ -268,9 +270,26 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter 
     | 
|
| 
       268 
270 
     | 
    
         
             
                  document.removeEventListener('visibilitychange', this.appVisibilityChangedListener);
         
     | 
| 
       269 
271 
     | 
    
         
             
                }
         
     | 
| 
       270 
272 
     | 
    
         
             
              }
         
     | 
| 
      
 273 
     | 
    
         
            +
             
     | 
| 
      
 274 
     | 
    
         
            +
              private handleElementSuspended = () => {
         
     | 
| 
      
 275 
     | 
    
         
            +
                this.debouncedPlaybackStateChange(false);
         
     | 
| 
      
 276 
     | 
    
         
            +
              };
         
     | 
| 
      
 277 
     | 
    
         
            +
             
     | 
| 
      
 278 
     | 
    
         
            +
              private handleElementPlay = () => {
         
     | 
| 
      
 279 
     | 
    
         
            +
                this.debouncedPlaybackStateChange(true);
         
     | 
| 
      
 280 
     | 
    
         
            +
              };
         
     | 
| 
      
 281 
     | 
    
         
            +
             
     | 
| 
      
 282 
     | 
    
         
            +
              private debouncedPlaybackStateChange = debounce((allowed: boolean) => {
         
     | 
| 
      
 283 
     | 
    
         
            +
                // we debounce this as Safari triggers both `playing` and `suspend` shortly after one another
         
     | 
| 
      
 284 
     | 
    
         
            +
                // in order not to raise the wrong event, we debounce the call to make sure we only emit the correct status
         
     | 
| 
      
 285 
     | 
    
         
            +
                if (this.kind === Track.Kind.Audio) {
         
     | 
| 
      
 286 
     | 
    
         
            +
                  this.emit(allowed ? TrackEvent.AudioPlaybackStarted : TrackEvent.AudioPlaybackFailed);
         
     | 
| 
      
 287 
     | 
    
         
            +
                } else if (this.kind === Track.Kind.Video) {
         
     | 
| 
      
 288 
     | 
    
         
            +
                  this.emit(allowed ? TrackEvent.VideoPlaybackStarted : TrackEvent.VideoPlaybackFailed);
         
     | 
| 
      
 289 
     | 
    
         
            +
                }
         
     | 
| 
      
 290 
     | 
    
         
            +
              }, 300);
         
     | 
| 
       271 
291 
     | 
    
         
             
            }
         
     | 
| 
       272 
292 
     | 
    
         | 
| 
       273 
     | 
    
         
            -
            /** @internal */
         
     | 
| 
       274 
293 
     | 
    
         
             
            export function attachToElement(track: MediaStreamTrack, element: HTMLMediaElement) {
         
     | 
| 
       275 
294 
     | 
    
         
             
              let mediaStream: MediaStream;
         
     | 
| 
       276 
295 
     | 
    
         
             
              if (element.srcObject instanceof MediaStream) {
         
     | 
| 
         @@ -321,7 +340,7 @@ export function attachToElement(track: MediaStreamTrack, element: HTMLMediaEleme 
     | 
|
| 
       321 
340 
     | 
    
         
             
                    // when the window is backgrounded before the first frame is drawn
         
     | 
| 
       322 
341 
     | 
    
         
             
                    // manually calling play here seems to fix that
         
     | 
| 
       323 
342 
     | 
    
         
             
                    element.play().catch(() => {
         
     | 
| 
       324 
     | 
    
         
            -
                       
     | 
| 
      
 343 
     | 
    
         
            +
                      /** do nothing, we watch the `suspended` event do deal with these failures */
         
     | 
| 
       325 
344 
     | 
    
         
             
                    });
         
     | 
| 
       326 
345 
     | 
    
         
             
                  }, 0);
         
     | 
| 
       327 
346 
     | 
    
         
             
                }
         
     | 
| 
         @@ -446,10 +465,12 @@ export type TrackEventCallbacks = { 
     | 
|
| 
       446 
465 
     | 
    
         
             
              updateSettings: () => void;
         
     | 
| 
       447 
466 
     | 
    
         
             
              updateSubscription: () => void;
         
     | 
| 
       448 
467 
     | 
    
         
             
              audioPlaybackStarted: () => void;
         
     | 
| 
       449 
     | 
    
         
            -
              audioPlaybackFailed: (error 
     | 
| 
      
 468 
     | 
    
         
            +
              audioPlaybackFailed: (error?: Error) => void;
         
     | 
| 
       450 
469 
     | 
    
         
             
              audioSilenceDetected: () => void;
         
     | 
| 
       451 
470 
     | 
    
         
             
              visibilityChanged: (visible: boolean, track?: any) => void;
         
     | 
| 
       452 
471 
     | 
    
         
             
              videoDimensionsChanged: (dimensions: Track.Dimensions, track?: any) => void;
         
     | 
| 
      
 472 
     | 
    
         
            +
              videoPlaybackStarted: () => void;
         
     | 
| 
      
 473 
     | 
    
         
            +
              videoPlaybackFailed: (error?: Error) => void;
         
     | 
| 
       453 
474 
     | 
    
         
             
              elementAttached: (element: HTMLMediaElement) => void;
         
     | 
| 
       454 
475 
     | 
    
         
             
              elementDetached: (element: HTMLMediaElement) => void;
         
     | 
| 
       455 
476 
     | 
    
         
             
              upstreamPaused: (track: any) => void;
         
     | 
    
        package/src/room/track/utils.ts
    CHANGED
    
    | 
         @@ -1,6 +1,8 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            import { TrackPublishedResponse } from '../../proto/livekit_rtc_pb';
         
     | 
| 
       1 
2 
     | 
    
         
             
            import { cloneDeep } from '../../utils/cloneDeep';
         
     | 
| 
       2 
3 
     | 
    
         
             
            import { isSafari, sleep } from '../utils';
         
     | 
| 
       3 
4 
     | 
    
         
             
            import { Track } from './Track';
         
     | 
| 
      
 5 
     | 
    
         
            +
            import type { TrackPublication } from './TrackPublication';
         
     | 
| 
       4 
6 
     | 
    
         
             
            import {
         
     | 
| 
       5 
7 
     | 
    
         
             
              type AudioCaptureOptions,
         
     | 
| 
       6 
8 
     | 
    
         
             
              type CreateLocalTracksOptions,
         
     | 
| 
         @@ -190,3 +192,20 @@ export function mimeTypeToVideoCodecString(mimeType: string) { 
     | 
|
| 
       190 
192 
     | 
    
         
             
              }
         
     | 
| 
       191 
193 
     | 
    
         
             
              return codec;
         
     | 
| 
       192 
194 
     | 
    
         
             
            }
         
     | 
| 
      
 195 
     | 
    
         
            +
             
     | 
| 
      
 196 
     | 
    
         
            +
            export function getTrackPublicationInfo<T extends TrackPublication>(
         
     | 
| 
      
 197 
     | 
    
         
            +
              tracks: T[],
         
     | 
| 
      
 198 
     | 
    
         
            +
            ): TrackPublishedResponse[] {
         
     | 
| 
      
 199 
     | 
    
         
            +
              const infos: TrackPublishedResponse[] = [];
         
     | 
| 
      
 200 
     | 
    
         
            +
              tracks.forEach((track: TrackPublication) => {
         
     | 
| 
      
 201 
     | 
    
         
            +
                if (track.track !== undefined) {
         
     | 
| 
      
 202 
     | 
    
         
            +
                  infos.push(
         
     | 
| 
      
 203 
     | 
    
         
            +
                    new TrackPublishedResponse({
         
     | 
| 
      
 204 
     | 
    
         
            +
                      cid: track.track.mediaStreamID,
         
     | 
| 
      
 205 
     | 
    
         
            +
                      track: track.trackInfo,
         
     | 
| 
      
 206 
     | 
    
         
            +
                    }),
         
     | 
| 
      
 207 
     | 
    
         
            +
                  );
         
     | 
| 
      
 208 
     | 
    
         
            +
                }
         
     | 
| 
      
 209 
     | 
    
         
            +
              });
         
     | 
| 
      
 210 
     | 
    
         
            +
              return infos;
         
     | 
| 
      
 211 
     | 
    
         
            +
            }
         
     |