livekit-client 1.2.2 → 1.2.3
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 +972 -109
 - 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/index.d.ts +2 -1
 - package/dist/src/index.d.ts.map +1 -1
 - package/dist/src/options.d.ts +5 -0
 - package/dist/src/options.d.ts.map +1 -1
 - package/dist/src/room/DefaultReconnectPolicy.d.ts +8 -0
 - package/dist/src/room/DefaultReconnectPolicy.d.ts.map +1 -0
 - package/dist/src/room/PCTransport.d.ts +1 -0
 - package/dist/src/room/PCTransport.d.ts.map +1 -1
 - package/dist/src/room/RTCEngine.d.ts +6 -1
 - package/dist/src/room/RTCEngine.d.ts.map +1 -1
 - package/dist/src/room/ReconnectPolicy.d.ts +23 -0
 - package/dist/src/room/ReconnectPolicy.d.ts.map +1 -0
 - package/dist/src/room/Room.d.ts +8 -0
 - package/dist/src/room/Room.d.ts.map +1 -1
 - package/dist/src/room/events.d.ts +2 -2
 - 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/LocalVideoTrack.d.ts.map +1 -1
 - package/package.json +3 -1
 - package/src/index.ts +2 -0
 - package/src/options.ts +6 -0
 - package/src/room/DefaultReconnectPolicy.ts +35 -0
 - package/src/room/PCTransport.ts +91 -17
 - package/src/room/RTCEngine.ts +79 -31
 - package/src/room/ReconnectPolicy.ts +25 -0
 - package/src/room/Room.ts +55 -30
 - package/src/room/events.ts +2 -2
 - package/src/room/participant/LocalParticipant.ts +30 -16
 - package/src/room/participant/RemoteParticipant.ts +13 -0
 - package/src/room/track/LocalVideoTrack.ts +0 -1
 
    
        package/src/room/RTCEngine.ts
    CHANGED
    
    | 
         @@ -2,6 +2,7 @@ import { EventEmitter } from 'events'; 
     | 
|
| 
       2 
2 
     | 
    
         
             
            import type TypedEventEmitter from 'typed-emitter';
         
     | 
| 
       3 
3 
     | 
    
         
             
            import { SignalClient, SignalOptions } from '../api/SignalClient';
         
     | 
| 
       4 
4 
     | 
    
         
             
            import log from '../logger';
         
     | 
| 
      
 5 
     | 
    
         
            +
            import { RoomOptions } from '../options';
         
     | 
| 
       5 
6 
     | 
    
         
             
            import {
         
     | 
| 
       6 
7 
     | 
    
         
             
              ClientConfigSetting,
         
     | 
| 
       7 
8 
     | 
    
         
             
              ClientConfiguration,
         
     | 
| 
         @@ -19,16 +20,16 @@ import { 
     | 
|
| 
       19 
20 
     | 
    
         
             
              SignalTarget,
         
     | 
| 
       20 
21 
     | 
    
         
             
              TrackPublishedResponse,
         
     | 
| 
       21 
22 
     | 
    
         
             
            } from '../proto/livekit_rtc';
         
     | 
| 
      
 23 
     | 
    
         
            +
            import DefaultReconnectPolicy from './DefaultReconnectPolicy';
         
     | 
| 
       22 
24 
     | 
    
         
             
            import { ConnectionError, TrackInvalidError, UnexpectedConnectionState } from './errors';
         
     | 
| 
       23 
25 
     | 
    
         
             
            import { EngineEvent } from './events';
         
     | 
| 
       24 
26 
     | 
    
         
             
            import PCTransport from './PCTransport';
         
     | 
| 
      
 27 
     | 
    
         
            +
            import { ReconnectContext, ReconnectPolicy } from './ReconnectPolicy';
         
     | 
| 
       25 
28 
     | 
    
         
             
            import { isFireFox, isWeb, sleep } from './utils';
         
     | 
| 
       26 
29 
     | 
    
         | 
| 
       27 
30 
     | 
    
         
             
            const lossyDataChannel = '_lossy';
         
     | 
| 
       28 
31 
     | 
    
         
             
            const reliableDataChannel = '_reliable';
         
     | 
| 
       29 
     | 
    
         
            -
            const maxReconnectRetries = 10;
         
     | 
| 
       30 
32 
     | 
    
         
             
            const minReconnectWait = 2 * 1000;
         
     | 
| 
       31 
     | 
    
         
            -
            const maxReconnectDuration = 60 * 1000;
         
     | 
| 
       32 
33 
     | 
    
         
             
            export const maxICEConnectTimeout = 15 * 1000;
         
     | 
| 
       33 
34 
     | 
    
         | 
| 
       34 
35 
     | 
    
         
             
            enum PCState {
         
     | 
| 
         @@ -96,9 +97,15 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit 
     | 
|
| 
       96 
97 
     | 
    
         | 
| 
       97 
98 
     | 
    
         
             
              private attemptingReconnect: boolean = false;
         
     | 
| 
       98 
99 
     | 
    
         | 
| 
       99 
     | 
    
         
            -
               
     | 
| 
      
 100 
     | 
    
         
            +
              private reconnectPolicy: ReconnectPolicy;
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
              private reconnectTimeout?: ReturnType<typeof setTimeout>;
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
              constructor(private options: RoomOptions) {
         
     | 
| 
       100 
105 
     | 
    
         
             
                super();
         
     | 
| 
       101 
106 
     | 
    
         
             
                this.client = new SignalClient();
         
     | 
| 
      
 107 
     | 
    
         
            +
                this.client.signalLatency = this.options.expSignalLatency;
         
     | 
| 
      
 108 
     | 
    
         
            +
                this.reconnectPolicy = this.options.reconnectPolicy ?? new DefaultReconnectPolicy();
         
     | 
| 
       102 
109 
     | 
    
         
             
              }
         
     | 
| 
       103 
110 
     | 
    
         | 
| 
       104 
111 
     | 
    
         
             
              async join(
         
     | 
| 
         @@ -157,8 +164,16 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit 
     | 
|
| 
       157 
164 
     | 
    
         
             
                if (this.pendingTrackResolvers[req.cid]) {
         
     | 
| 
       158 
165 
     | 
    
         
             
                  throw new TrackInvalidError('a track with the same ID has already been published');
         
     | 
| 
       159 
166 
     | 
    
         
             
                }
         
     | 
| 
       160 
     | 
    
         
            -
                return new Promise<TrackInfo>((resolve) => {
         
     | 
| 
       161 
     | 
    
         
            -
                   
     | 
| 
      
 167 
     | 
    
         
            +
                return new Promise<TrackInfo>((resolve, reject) => {
         
     | 
| 
      
 168 
     | 
    
         
            +
                  const publicationTimeout = setTimeout(() => {
         
     | 
| 
      
 169 
     | 
    
         
            +
                    reject(
         
     | 
| 
      
 170 
     | 
    
         
            +
                      new ConnectionError('publication of local track timed out, no response from server'),
         
     | 
| 
      
 171 
     | 
    
         
            +
                    );
         
     | 
| 
      
 172 
     | 
    
         
            +
                  }, 15_000);
         
     | 
| 
      
 173 
     | 
    
         
            +
                  this.pendingTrackResolvers[req.cid] = (info: TrackInfo) => {
         
     | 
| 
      
 174 
     | 
    
         
            +
                    clearTimeout(publicationTimeout);
         
     | 
| 
      
 175 
     | 
    
         
            +
                    resolve(info);
         
     | 
| 
      
 176 
     | 
    
         
            +
                  };
         
     | 
| 
       162 
177 
     | 
    
         
             
                  this.client.sendAddTrack(req);
         
     | 
| 
       163 
178 
     | 
    
         
             
                });
         
     | 
| 
       164 
179 
     | 
    
         
             
              }
         
     | 
| 
         @@ -317,8 +332,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit 
     | 
|
| 
       317 
332 
     | 
    
         
             
                  await this.subscriber.setRemoteDescription(sd);
         
     | 
| 
       318 
333 
     | 
    
         | 
| 
       319 
334 
     | 
    
         
             
                  // answer the offer
         
     | 
| 
       320 
     | 
    
         
            -
                  const answer = await this.subscriber. 
     | 
| 
       321 
     | 
    
         
            -
                  await this.subscriber.pc.setLocalDescription(answer);
         
     | 
| 
      
 335 
     | 
    
         
            +
                  const answer = await this.subscriber.createAndSetAnswer();
         
     | 
| 
       322 
336 
     | 
    
         
             
                  this.client.sendAnswer(answer);
         
     | 
| 
       323 
337 
     | 
    
         
             
                };
         
     | 
| 
       324 
338 
     | 
    
         | 
| 
         @@ -437,18 +451,42 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit 
     | 
|
| 
       437 
451 
     | 
    
         
             
              // websocket reconnect behavior. if websocket is interrupted, and the PeerConnection
         
     | 
| 
       438 
452 
     | 
    
         
             
              // continues to work, we can reconnect to websocket to continue the session
         
     | 
| 
       439 
453 
     | 
    
         
             
              // after a number of retries, we'll close and give up permanently
         
     | 
| 
       440 
     | 
    
         
            -
              private handleDisconnect = (connection: string) => {
         
     | 
| 
      
 454 
     | 
    
         
            +
              private handleDisconnect = (connection: string, signalEvents: boolean = false) => {
         
     | 
| 
       441 
455 
     | 
    
         
             
                if (this._isClosed) {
         
     | 
| 
       442 
456 
     | 
    
         
             
                  return;
         
     | 
| 
       443 
457 
     | 
    
         
             
                }
         
     | 
| 
      
 458 
     | 
    
         
            +
             
     | 
| 
       444 
459 
     | 
    
         
             
                log.debug(`${connection} disconnected`);
         
     | 
| 
       445 
460 
     | 
    
         
             
                if (this.reconnectAttempts === 0) {
         
     | 
| 
       446 
461 
     | 
    
         
             
                  // only reset start time on the first try
         
     | 
| 
       447 
462 
     | 
    
         
             
                  this.reconnectStart = Date.now();
         
     | 
| 
       448 
463 
     | 
    
         
             
                }
         
     | 
| 
       449 
464 
     | 
    
         | 
| 
       450 
     | 
    
         
            -
                const  
     | 
| 
       451 
     | 
    
         
            -
             
     | 
| 
      
 465 
     | 
    
         
            +
                const disconnect = (duration: number) => {
         
     | 
| 
      
 466 
     | 
    
         
            +
                  log.info(
         
     | 
| 
      
 467 
     | 
    
         
            +
                    `could not recover connection after ${this.reconnectAttempts} attempts, ${duration}ms. giving up`,
         
     | 
| 
      
 468 
     | 
    
         
            +
                  );
         
     | 
| 
      
 469 
     | 
    
         
            +
                  this.emit(EngineEvent.Disconnected);
         
     | 
| 
      
 470 
     | 
    
         
            +
                  this.close();
         
     | 
| 
      
 471 
     | 
    
         
            +
                };
         
     | 
| 
      
 472 
     | 
    
         
            +
             
     | 
| 
      
 473 
     | 
    
         
            +
                const duration = Date.now() - this.reconnectStart;
         
     | 
| 
      
 474 
     | 
    
         
            +
                const delay = this.getNextRetryDelay({
         
     | 
| 
      
 475 
     | 
    
         
            +
                  elapsedMs: duration,
         
     | 
| 
      
 476 
     | 
    
         
            +
                  retryCount: this.reconnectAttempts,
         
     | 
| 
      
 477 
     | 
    
         
            +
                });
         
     | 
| 
      
 478 
     | 
    
         
            +
             
     | 
| 
      
 479 
     | 
    
         
            +
                if (delay === null) {
         
     | 
| 
      
 480 
     | 
    
         
            +
                  disconnect(duration);
         
     | 
| 
      
 481 
     | 
    
         
            +
                  return;
         
     | 
| 
      
 482 
     | 
    
         
            +
                }
         
     | 
| 
      
 483 
     | 
    
         
            +
             
     | 
| 
      
 484 
     | 
    
         
            +
                log.debug(`reconnecting in ${delay}ms`);
         
     | 
| 
      
 485 
     | 
    
         
            +
             
     | 
| 
      
 486 
     | 
    
         
            +
                if (this.reconnectTimeout) {
         
     | 
| 
      
 487 
     | 
    
         
            +
                  clearTimeout(this.reconnectTimeout);
         
     | 
| 
      
 488 
     | 
    
         
            +
                }
         
     | 
| 
      
 489 
     | 
    
         
            +
                this.reconnectTimeout = setTimeout(async () => {
         
     | 
| 
       452 
490 
     | 
    
         
             
                  if (this._isClosed) {
         
     | 
| 
       453 
491 
     | 
    
         
             
                    return;
         
     | 
| 
       454 
492 
     | 
    
         
             
                  }
         
     | 
| 
         @@ -469,16 +507,20 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit 
     | 
|
| 
       469 
507 
     | 
    
         
             
                  try {
         
     | 
| 
       470 
508 
     | 
    
         
             
                    this.attemptingReconnect = true;
         
     | 
| 
       471 
509 
     | 
    
         
             
                    if (this.fullReconnectOnNext) {
         
     | 
| 
       472 
     | 
    
         
            -
                      await this.restartConnection();
         
     | 
| 
      
 510 
     | 
    
         
            +
                      await this.restartConnection(signalEvents);
         
     | 
| 
       473 
511 
     | 
    
         
             
                    } else {
         
     | 
| 
       474 
     | 
    
         
            -
                      await this.resumeConnection();
         
     | 
| 
      
 512 
     | 
    
         
            +
                      await this.resumeConnection(signalEvents);
         
     | 
| 
       475 
513 
     | 
    
         
             
                    }
         
     | 
| 
       476 
514 
     | 
    
         
             
                    this.reconnectAttempts = 0;
         
     | 
| 
       477 
515 
     | 
    
         
             
                    this.fullReconnectOnNext = false;
         
     | 
| 
      
 516 
     | 
    
         
            +
                    if (this.reconnectTimeout) {
         
     | 
| 
      
 517 
     | 
    
         
            +
                      clearTimeout(this.reconnectTimeout);
         
     | 
| 
      
 518 
     | 
    
         
            +
                    }
         
     | 
| 
       478 
519 
     | 
    
         
             
                  } catch (e) {
         
     | 
| 
       479 
520 
     | 
    
         
             
                    this.reconnectAttempts += 1;
         
     | 
| 
       480 
521 
     | 
    
         
             
                    let reconnectRequired = false;
         
     | 
| 
       481 
522 
     | 
    
         
             
                    let recoverable = true;
         
     | 
| 
      
 523 
     | 
    
         
            +
                    let requireSignalEvents = false;
         
     | 
| 
       482 
524 
     | 
    
         
             
                    if (e instanceof UnexpectedConnectionState) {
         
     | 
| 
       483 
525 
     | 
    
         
             
                      log.debug('received unrecoverable error', { error: e });
         
     | 
| 
       484 
526 
     | 
    
         
             
                      // unrecoverable
         
     | 
| 
         @@ -488,26 +530,17 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit 
     | 
|
| 
       488 
530 
     | 
    
         
             
                      reconnectRequired = true;
         
     | 
| 
       489 
531 
     | 
    
         
             
                    }
         
     | 
| 
       490 
532 
     | 
    
         | 
| 
       491 
     | 
    
         
            -
                    // when we flip from resume to reconnect 
     | 
| 
       492 
     | 
    
         
            -
                    //  
     | 
| 
      
 533 
     | 
    
         
            +
                    // when we flip from resume to reconnect
         
     | 
| 
      
 534 
     | 
    
         
            +
                    // we need to fire the right reconnecting events
         
     | 
| 
       493 
535 
     | 
    
         
             
                    if (reconnectRequired && !this.fullReconnectOnNext) {
         
     | 
| 
       494 
536 
     | 
    
         
             
                      this.fullReconnectOnNext = true;
         
     | 
| 
       495 
     | 
    
         
            -
                       
     | 
| 
       496 
     | 
    
         
            -
                    }
         
     | 
| 
       497 
     | 
    
         
            -
             
     | 
| 
       498 
     | 
    
         
            -
                    const duration = Date.now() - this.reconnectStart;
         
     | 
| 
       499 
     | 
    
         
            -
                    if (this.reconnectAttempts >= maxReconnectRetries || duration > maxReconnectDuration) {
         
     | 
| 
       500 
     | 
    
         
            -
                      recoverable = false;
         
     | 
| 
      
 537 
     | 
    
         
            +
                      requireSignalEvents = true;
         
     | 
| 
       501 
538 
     | 
    
         
             
                    }
         
     | 
| 
       502 
539 
     | 
    
         | 
| 
       503 
540 
     | 
    
         
             
                    if (recoverable) {
         
     | 
| 
       504 
     | 
    
         
            -
                      this.handleDisconnect('reconnect');
         
     | 
| 
      
 541 
     | 
    
         
            +
                      this.handleDisconnect('reconnect', requireSignalEvents);
         
     | 
| 
       505 
542 
     | 
    
         
             
                    } else {
         
     | 
| 
       506 
     | 
    
         
            -
                       
     | 
| 
       507 
     | 
    
         
            -
                        `could not recover connection after ${maxReconnectRetries} attempts, ${duration}ms. giving up`,
         
     | 
| 
       508 
     | 
    
         
            -
                      );
         
     | 
| 
       509 
     | 
    
         
            -
                      this.emit(EngineEvent.Disconnected);
         
     | 
| 
       510 
     | 
    
         
            -
                      this.close();
         
     | 
| 
      
 543 
     | 
    
         
            +
                      disconnect(Date.now() - this.reconnectStart);
         
     | 
| 
       511 
544 
     | 
    
         
             
                    }
         
     | 
| 
       512 
545 
     | 
    
         
             
                  } finally {
         
     | 
| 
       513 
546 
     | 
    
         
             
                    this.attemptingReconnect = false;
         
     | 
| 
         @@ -515,14 +548,25 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit 
     | 
|
| 
       515 
548 
     | 
    
         
             
                }, delay);
         
     | 
| 
       516 
549 
     | 
    
         
             
              };
         
     | 
| 
       517 
550 
     | 
    
         | 
| 
       518 
     | 
    
         
            -
              private  
     | 
| 
      
 551 
     | 
    
         
            +
              private getNextRetryDelay(context: ReconnectContext) {
         
     | 
| 
      
 552 
     | 
    
         
            +
                try {
         
     | 
| 
      
 553 
     | 
    
         
            +
                  return this.reconnectPolicy.nextRetryDelayInMs(context);
         
     | 
| 
      
 554 
     | 
    
         
            +
                } catch (e) {
         
     | 
| 
      
 555 
     | 
    
         
            +
                  log.warn('encountered error in reconnect policy', { error: e });
         
     | 
| 
      
 556 
     | 
    
         
            +
                }
         
     | 
| 
      
 557 
     | 
    
         
            +
             
     | 
| 
      
 558 
     | 
    
         
            +
                // error in user code with provided reconnect policy, stop reconnecting
         
     | 
| 
      
 559 
     | 
    
         
            +
                return null;
         
     | 
| 
      
 560 
     | 
    
         
            +
              }
         
     | 
| 
      
 561 
     | 
    
         
            +
             
     | 
| 
      
 562 
     | 
    
         
            +
              private async restartConnection(emitRestarting: boolean = false) {
         
     | 
| 
       519 
563 
     | 
    
         
             
                if (!this.url || !this.token) {
         
     | 
| 
       520 
564 
     | 
    
         
             
                  // permanent failure, don't attempt reconnection
         
     | 
| 
       521 
565 
     | 
    
         
             
                  throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
         
     | 
| 
       522 
566 
     | 
    
         
             
                }
         
     | 
| 
       523 
567 
     | 
    
         | 
| 
       524 
568 
     | 
    
         
             
                log.info(`reconnecting, attempt: ${this.reconnectAttempts}`);
         
     | 
| 
       525 
     | 
    
         
            -
                if (this.reconnectAttempts === 0) {
         
     | 
| 
      
 569 
     | 
    
         
            +
                if (emitRestarting || this.reconnectAttempts === 0) {
         
     | 
| 
       526 
570 
     | 
    
         
             
                  this.emit(EngineEvent.Restarting);
         
     | 
| 
       527 
571 
     | 
    
         
             
                }
         
     | 
| 
       528 
572 
     | 
    
         | 
| 
         @@ -550,7 +594,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit 
     | 
|
| 
       550 
594 
     | 
    
         
             
                this.emit(EngineEvent.Restarted, joinResponse);
         
     | 
| 
       551 
595 
     | 
    
         
             
              }
         
     | 
| 
       552 
596 
     | 
    
         | 
| 
       553 
     | 
    
         
            -
              private async resumeConnection(): Promise<void> {
         
     | 
| 
      
 597 
     | 
    
         
            +
              private async resumeConnection(emitResuming: boolean = false): Promise<void> {
         
     | 
| 
       554 
598 
     | 
    
         
             
                if (!this.url || !this.token) {
         
     | 
| 
       555 
599 
     | 
    
         
             
                  // permanent failure, don't attempt reconnection
         
     | 
| 
       556 
600 
     | 
    
         
             
                  throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
         
     | 
| 
         @@ -561,14 +605,18 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit 
     | 
|
| 
       561 
605 
     | 
    
         
             
                }
         
     | 
| 
       562 
606 
     | 
    
         | 
| 
       563 
607 
     | 
    
         
             
                log.info(`resuming signal connection, attempt ${this.reconnectAttempts}`);
         
     | 
| 
       564 
     | 
    
         
            -
                if (this.reconnectAttempts === 0) {
         
     | 
| 
      
 608 
     | 
    
         
            +
                if (emitResuming || this.reconnectAttempts === 0) {
         
     | 
| 
       565 
609 
     | 
    
         
             
                  this.emit(EngineEvent.Resuming);
         
     | 
| 
       566 
610 
     | 
    
         
             
                }
         
     | 
| 
       567 
611 
     | 
    
         | 
| 
       568 
612 
     | 
    
         
             
                try {
         
     | 
| 
       569 
613 
     | 
    
         
             
                  await this.client.reconnect(this.url, this.token);
         
     | 
| 
       570 
614 
     | 
    
         
             
                } catch (e) {
         
     | 
| 
       571 
     | 
    
         
            -
                   
     | 
| 
      
 615 
     | 
    
         
            +
                  let message = '';
         
     | 
| 
      
 616 
     | 
    
         
            +
                  if (e instanceof Error) {
         
     | 
| 
      
 617 
     | 
    
         
            +
                    message = e.message;
         
     | 
| 
      
 618 
     | 
    
         
            +
                  }
         
     | 
| 
      
 619 
     | 
    
         
            +
                  throw new SignalReconnectError(message);
         
     | 
| 
       572 
620 
     | 
    
         
             
                }
         
     | 
| 
       573 
621 
     | 
    
         
             
                this.emit(EngineEvent.SignalResumed);
         
     | 
| 
       574 
622 
     | 
    
         | 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            /** Controls reconnecting of the client */
         
     | 
| 
      
 2 
     | 
    
         
            +
            export interface ReconnectPolicy {
         
     | 
| 
      
 3 
     | 
    
         
            +
              /** Called after disconnect was detected
         
     | 
| 
      
 4 
     | 
    
         
            +
               *
         
     | 
| 
      
 5 
     | 
    
         
            +
               * @returns {number | null} Amount of time in milliseconds to delay the next reconnect attempt, `null` signals to stop retrying.
         
     | 
| 
      
 6 
     | 
    
         
            +
               */
         
     | 
| 
      
 7 
     | 
    
         
            +
              nextRetryDelayInMs(context: ReconnectContext): number | null;
         
     | 
| 
      
 8 
     | 
    
         
            +
            }
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            export interface ReconnectContext {
         
     | 
| 
      
 11 
     | 
    
         
            +
              /**
         
     | 
| 
      
 12 
     | 
    
         
            +
               * Number of failed reconnect attempts
         
     | 
| 
      
 13 
     | 
    
         
            +
               */
         
     | 
| 
      
 14 
     | 
    
         
            +
              readonly retryCount: number;
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              /**
         
     | 
| 
      
 17 
     | 
    
         
            +
               * Elapsed amount of time in milliseconds since the disconnect.
         
     | 
| 
      
 18 
     | 
    
         
            +
               */
         
     | 
| 
      
 19 
     | 
    
         
            +
              readonly elapsedMs: number;
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              /**
         
     | 
| 
      
 22 
     | 
    
         
            +
               * Reason for retrying
         
     | 
| 
      
 23 
     | 
    
         
            +
               */
         
     | 
| 
      
 24 
     | 
    
         
            +
              readonly retryReason?: Error;
         
     | 
| 
      
 25 
     | 
    
         
            +
            }
         
     | 
    
        package/src/room/Room.ts
    CHANGED
    
    | 
         @@ -134,9 +134,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) 
     | 
|
| 
       134 
134 
     | 
    
         
             
                  return;
         
     | 
| 
       135 
135 
     | 
    
         
             
                }
         
     | 
| 
       136 
136 
     | 
    
         | 
| 
       137 
     | 
    
         
            -
                this.engine = new RTCEngine();
         
     | 
| 
      
 137 
     | 
    
         
            +
                this.engine = new RTCEngine(this.options);
         
     | 
| 
       138 
138 
     | 
    
         | 
| 
       139 
     | 
    
         
            -
                this.engine.client.signalLatency = this.options.expSignalLatency;
         
     | 
| 
       140 
139 
     | 
    
         
             
                this.engine.client.onParticipantUpdate = this.handleParticipantUpdates;
         
     | 
| 
       141 
140 
     | 
    
         
             
                this.engine.client.onRoomUpdate = this.handleRoomUpdate;
         
     | 
| 
       142 
141 
     | 
    
         
             
                this.engine.client.onSpeakersChanged = this.handleSpeakersChanged;
         
     | 
| 
         @@ -255,36 +254,16 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) 
     | 
|
| 
       255 
254 
     | 
    
         
             
                  this.localParticipant.updateInfo(pi);
         
     | 
| 
       256 
255 
     | 
    
         
             
                  // forward metadata changed for the local participant
         
     | 
| 
       257 
256 
     | 
    
         
             
                  this.localParticipant
         
     | 
| 
       258 
     | 
    
         
            -
                    .on(ParticipantEvent.ParticipantMetadataChanged,  
     | 
| 
       259 
     | 
    
         
            -
             
     | 
| 
       260 
     | 
    
         
            -
                     
     | 
| 
       261 
     | 
    
         
            -
                    .on(ParticipantEvent. 
     | 
| 
       262 
     | 
    
         
            -
             
     | 
| 
       263 
     | 
    
         
            -
                     
     | 
| 
       264 
     | 
    
         
            -
                    .on(ParticipantEvent. 
     | 
| 
       265 
     | 
    
         
            -
                      this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
         
     | 
| 
       266 
     | 
    
         
            -
                    })
         
     | 
| 
       267 
     | 
    
         
            -
                    .on(ParticipantEvent.LocalTrackPublished, (pub: LocalTrackPublication) => {
         
     | 
| 
       268 
     | 
    
         
            -
                      this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
         
     | 
| 
       269 
     | 
    
         
            -
                    })
         
     | 
| 
       270 
     | 
    
         
            -
                    .on(ParticipantEvent.LocalTrackUnpublished, (pub: LocalTrackPublication) => {
         
     | 
| 
       271 
     | 
    
         
            -
                      this.emit(RoomEvent.LocalTrackUnpublished, pub, this.localParticipant);
         
     | 
| 
       272 
     | 
    
         
            -
                    })
         
     | 
| 
       273 
     | 
    
         
            -
                    .on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
         
     | 
| 
       274 
     | 
    
         
            -
                      this.emit(RoomEvent.ConnectionQualityChanged, quality, this.localParticipant);
         
     | 
| 
       275 
     | 
    
         
            -
                    })
         
     | 
| 
       276 
     | 
    
         
            -
                    .on(ParticipantEvent.MediaDevicesError, (e: Error) => {
         
     | 
| 
       277 
     | 
    
         
            -
                      this.emit(RoomEvent.MediaDevicesError, e);
         
     | 
| 
       278 
     | 
    
         
            -
                    })
         
     | 
| 
      
 257 
     | 
    
         
            +
                    .on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
         
     | 
| 
      
 258 
     | 
    
         
            +
                    .on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
         
     | 
| 
      
 259 
     | 
    
         
            +
                    .on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
         
     | 
| 
      
 260 
     | 
    
         
            +
                    .on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
         
     | 
| 
      
 261 
     | 
    
         
            +
                    .on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished)
         
     | 
| 
      
 262 
     | 
    
         
            +
                    .on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged)
         
     | 
| 
      
 263 
     | 
    
         
            +
                    .on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError)
         
     | 
| 
       279 
264 
     | 
    
         
             
                    .on(
         
     | 
| 
       280 
265 
     | 
    
         
             
                      ParticipantEvent.ParticipantPermissionsChanged,
         
     | 
| 
       281 
     | 
    
         
            -
                       
     | 
| 
       282 
     | 
    
         
            -
                        this.emit(
         
     | 
| 
       283 
     | 
    
         
            -
                          RoomEvent.ParticipantPermissionsChanged,
         
     | 
| 
       284 
     | 
    
         
            -
                          prevPermissions,
         
     | 
| 
       285 
     | 
    
         
            -
                          this.localParticipant,
         
     | 
| 
       286 
     | 
    
         
            -
                        );
         
     | 
| 
       287 
     | 
    
         
            -
                      },
         
     | 
| 
      
 266 
     | 
    
         
            +
                      this.onLocalParticipantPermissionsChanged,
         
     | 
| 
       288 
267 
     | 
    
         
             
                    );
         
     | 
| 
       289 
268 
     | 
    
         | 
| 
       290 
269 
     | 
    
         
             
                  // populate remote participants, these should not trigger new events
         
     | 
| 
         @@ -657,6 +636,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) 
     | 
|
| 
       657 
636 
     | 
    
         
             
                  });
         
     | 
| 
       658 
637 
     | 
    
         
             
                });
         
     | 
| 
       659 
638 
     | 
    
         | 
| 
      
 639 
     | 
    
         
            +
                this.localParticipant
         
     | 
| 
      
 640 
     | 
    
         
            +
                  .off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
         
     | 
| 
      
 641 
     | 
    
         
            +
                  .off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
         
     | 
| 
      
 642 
     | 
    
         
            +
                  .off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
         
     | 
| 
      
 643 
     | 
    
         
            +
                  .off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
         
     | 
| 
      
 644 
     | 
    
         
            +
                  .off(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished)
         
     | 
| 
      
 645 
     | 
    
         
            +
                  .off(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged)
         
     | 
| 
      
 646 
     | 
    
         
            +
                  .off(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError)
         
     | 
| 
      
 647 
     | 
    
         
            +
                  .off(
         
     | 
| 
      
 648 
     | 
    
         
            +
                    ParticipantEvent.ParticipantPermissionsChanged,
         
     | 
| 
      
 649 
     | 
    
         
            +
                    this.onLocalParticipantPermissionsChanged,
         
     | 
| 
      
 650 
     | 
    
         
            +
                  );
         
     | 
| 
      
 651 
     | 
    
         
            +
             
     | 
| 
       660 
652 
     | 
    
         
             
                this.localParticipant.tracks.forEach((pub) => {
         
     | 
| 
       661 
653 
     | 
    
         
             
                  if (pub.track) {
         
     | 
| 
       662 
654 
     | 
    
         
             
                    this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
         
     | 
| 
         @@ -666,6 +658,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) 
     | 
|
| 
       666 
658 
     | 
    
         
             
                    pub.track?.stop();
         
     | 
| 
       667 
659 
     | 
    
         
             
                  }
         
     | 
| 
       668 
660 
     | 
    
         
             
                });
         
     | 
| 
      
 661 
     | 
    
         
            +
             
     | 
| 
       669 
662 
     | 
    
         
             
                this.localParticipant.tracks.clear();
         
     | 
| 
       670 
663 
     | 
    
         
             
                this.localParticipant.videoTracks.clear();
         
     | 
| 
       671 
664 
     | 
    
         
             
                this.localParticipant.audioTracks.clear();
         
     | 
| 
         @@ -1081,6 +1074,38 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) 
     | 
|
| 
       1081 
1074 
     | 
    
         
             
                return false;
         
     | 
| 
       1082 
1075 
     | 
    
         
             
              }
         
     | 
| 
       1083 
1076 
     | 
    
         | 
| 
      
 1077 
     | 
    
         
            +
              private onLocalParticipantMetadataChanged = (metadata: string | undefined) => {
         
     | 
| 
      
 1078 
     | 
    
         
            +
                this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
         
     | 
| 
      
 1079 
     | 
    
         
            +
              };
         
     | 
| 
      
 1080 
     | 
    
         
            +
             
     | 
| 
      
 1081 
     | 
    
         
            +
              private onLocalTrackMuted = (pub: TrackPublication) => {
         
     | 
| 
      
 1082 
     | 
    
         
            +
                this.emit(RoomEvent.TrackMuted, pub, this.localParticipant);
         
     | 
| 
      
 1083 
     | 
    
         
            +
              };
         
     | 
| 
      
 1084 
     | 
    
         
            +
             
     | 
| 
      
 1085 
     | 
    
         
            +
              private onLocalTrackUnmuted = (pub: TrackPublication) => {
         
     | 
| 
      
 1086 
     | 
    
         
            +
                this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
         
     | 
| 
      
 1087 
     | 
    
         
            +
              };
         
     | 
| 
      
 1088 
     | 
    
         
            +
             
     | 
| 
      
 1089 
     | 
    
         
            +
              private onLocalTrackPublished = (pub: LocalTrackPublication) => {
         
     | 
| 
      
 1090 
     | 
    
         
            +
                this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
         
     | 
| 
      
 1091 
     | 
    
         
            +
              };
         
     | 
| 
      
 1092 
     | 
    
         
            +
             
     | 
| 
      
 1093 
     | 
    
         
            +
              private onLocalTrackUnpublished = (pub: LocalTrackPublication) => {
         
     | 
| 
      
 1094 
     | 
    
         
            +
                this.emit(RoomEvent.LocalTrackUnpublished, pub, this.localParticipant);
         
     | 
| 
      
 1095 
     | 
    
         
            +
              };
         
     | 
| 
      
 1096 
     | 
    
         
            +
             
     | 
| 
      
 1097 
     | 
    
         
            +
              private onLocalConnectionQualityChanged = (quality: ConnectionQuality) => {
         
     | 
| 
      
 1098 
     | 
    
         
            +
                this.emit(RoomEvent.ConnectionQualityChanged, quality, this.localParticipant);
         
     | 
| 
      
 1099 
     | 
    
         
            +
              };
         
     | 
| 
      
 1100 
     | 
    
         
            +
             
     | 
| 
      
 1101 
     | 
    
         
            +
              private onMediaDevicesError = (e: Error) => {
         
     | 
| 
      
 1102 
     | 
    
         
            +
                this.emit(RoomEvent.MediaDevicesError, e);
         
     | 
| 
      
 1103 
     | 
    
         
            +
              };
         
     | 
| 
      
 1104 
     | 
    
         
            +
             
     | 
| 
      
 1105 
     | 
    
         
            +
              private onLocalParticipantPermissionsChanged = (prevPermissions: ParticipantPermission) => {
         
     | 
| 
      
 1106 
     | 
    
         
            +
                this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, this.localParticipant);
         
     | 
| 
      
 1107 
     | 
    
         
            +
              };
         
     | 
| 
      
 1108 
     | 
    
         
            +
             
     | 
| 
       1084 
1109 
     | 
    
         
             
              // /** @internal */
         
     | 
| 
       1085 
1110 
     | 
    
         
             
              emit<E extends keyof RoomEventCallbacks>(
         
     | 
| 
       1086 
1111 
     | 
    
         
             
                event: E,
         
     | 
    
        package/src/room/events.ts
    CHANGED
    
    | 
         @@ -218,8 +218,8 @@ export enum RoomEvent { 
     | 
|
| 
       218 
218 
     | 
    
         
             
               * When we have encountered an error while attempting to create a track.
         
     | 
| 
       219 
219 
     | 
    
         
             
               * The errors take place in getUserMedia().
         
     | 
| 
       220 
220 
     | 
    
         
             
               * Use MediaDeviceFailure.getFailure(error) to get the reason of failure.
         
     | 
| 
       221 
     | 
    
         
            -
               * [[ 
     | 
| 
       222 
     | 
    
         
            -
               * an error while creating the audio or video track respectively.
         
     | 
| 
      
 221 
     | 
    
         
            +
               * [[LocalParticipant.lastCameraError]] and [[LocalParticipant.lastMicrophoneError]]
         
     | 
| 
      
 222 
     | 
    
         
            +
               * will indicate if it had an error while creating the audio or video track respectively.
         
     | 
| 
       223 
223 
     | 
    
         
             
               *
         
     | 
| 
       224 
224 
     | 
    
         
             
               * args: (error: Error)
         
     | 
| 
       225 
225 
     | 
    
         
             
               */
         
     | 
| 
         @@ -434,6 +434,23 @@ export default class LocalParticipant extends Participant { 
     | 
|
| 
       434 
434 
     | 
    
         
             
                if (opts.source) {
         
     | 
| 
       435 
435 
     | 
    
         
             
                  track.source = opts.source;
         
     | 
| 
       436 
436 
     | 
    
         
             
                }
         
     | 
| 
      
 437 
     | 
    
         
            +
                const existingTrackOfSource = Array.from(this.tracks.values()).find(
         
     | 
| 
      
 438 
     | 
    
         
            +
                  (publishedTrack) => track instanceof LocalTrack && publishedTrack.source === track.source,
         
     | 
| 
      
 439 
     | 
    
         
            +
                );
         
     | 
| 
      
 440 
     | 
    
         
            +
                if (existingTrackOfSource) {
         
     | 
| 
      
 441 
     | 
    
         
            +
                  try {
         
     | 
| 
      
 442 
     | 
    
         
            +
                    // throw an Error in order to capture the stack trace
         
     | 
| 
      
 443 
     | 
    
         
            +
                    throw Error(`publishing a second track with the same source: ${track.source}`);
         
     | 
| 
      
 444 
     | 
    
         
            +
                  } catch (e: unknown) {
         
     | 
| 
      
 445 
     | 
    
         
            +
                    if (e instanceof Error) {
         
     | 
| 
      
 446 
     | 
    
         
            +
                      log.warn(e.message, {
         
     | 
| 
      
 447 
     | 
    
         
            +
                        oldTrack: existingTrackOfSource,
         
     | 
| 
      
 448 
     | 
    
         
            +
                        newTrack: track,
         
     | 
| 
      
 449 
     | 
    
         
            +
                        trace: e.stack,
         
     | 
| 
      
 450 
     | 
    
         
            +
                      });
         
     | 
| 
      
 451 
     | 
    
         
            +
                    }
         
     | 
| 
      
 452 
     | 
    
         
            +
                  }
         
     | 
| 
      
 453 
     | 
    
         
            +
                }
         
     | 
| 
       437 
454 
     | 
    
         
             
                if (opts.stopMicTrackOnMute && track instanceof LocalAudioTrack) {
         
     | 
| 
       438 
455 
     | 
    
         
             
                  track.stopOnMute = true;
         
     | 
| 
       439 
456 
     | 
    
         
             
                }
         
     | 
| 
         @@ -686,8 +703,6 @@ export default class LocalParticipant extends Participant { 
     | 
|
| 
       686 
703 
     | 
    
         
             
                }
         
     | 
| 
       687 
704 
     | 
    
         | 
| 
       688 
705 
     | 
    
         
             
                track = publication.track;
         
     | 
| 
       689 
     | 
    
         
            -
             
     | 
| 
       690 
     | 
    
         
            -
                track.sender = undefined;
         
     | 
| 
       691 
706 
     | 
    
         
             
                track.off(TrackEvent.Muted, this.onTrackMuted);
         
     | 
| 
       692 
707 
     | 
    
         
             
                track.off(TrackEvent.Unmuted, this.onTrackUnmuted);
         
     | 
| 
       693 
708 
     | 
    
         
             
                track.off(TrackEvent.Ended, this.handleTrackEnded);
         
     | 
| 
         @@ -701,22 +716,21 @@ export default class LocalParticipant extends Participant { 
     | 
|
| 
       701 
716 
     | 
    
         
             
                  track.stop();
         
     | 
| 
       702 
717 
     | 
    
         
             
                }
         
     | 
| 
       703 
718 
     | 
    
         | 
| 
       704 
     | 
    
         
            -
                 
     | 
| 
       705 
     | 
    
         
            -
             
     | 
| 
       706 
     | 
    
         
            -
             
     | 
| 
       707 
     | 
    
         
            -
                   
     | 
| 
       708 
     | 
    
         
            -
             
     | 
| 
       709 
     | 
    
         
            -
             
     | 
| 
       710 
     | 
    
         
            -
             
     | 
| 
       711 
     | 
    
         
            -
             
     | 
| 
       712 
     | 
    
         
            -
             
     | 
| 
       713 
     | 
    
         
            -
             
     | 
| 
       714 
     | 
    
         
            -
             
     | 
| 
       715 
     | 
    
         
            -
                      }
         
     | 
| 
       716 
     | 
    
         
            -
                    }
         
     | 
| 
       717 
     | 
    
         
            -
                  });
         
     | 
| 
      
 719 
     | 
    
         
            +
                if (
         
     | 
| 
      
 720 
     | 
    
         
            +
                  this.engine.publisher &&
         
     | 
| 
      
 721 
     | 
    
         
            +
                  this.engine.publisher.pc.connectionState !== 'closed' &&
         
     | 
| 
      
 722 
     | 
    
         
            +
                  track.sender
         
     | 
| 
      
 723 
     | 
    
         
            +
                ) {
         
     | 
| 
      
 724 
     | 
    
         
            +
                  try {
         
     | 
| 
      
 725 
     | 
    
         
            +
                    this.engine.publisher.pc.removeTrack(track.sender);
         
     | 
| 
      
 726 
     | 
    
         
            +
                    this.engine.negotiate();
         
     | 
| 
      
 727 
     | 
    
         
            +
                  } catch (e) {
         
     | 
| 
      
 728 
     | 
    
         
            +
                    log.warn('failed to remove track', { error: e, method: 'unpublishTrack' });
         
     | 
| 
      
 729 
     | 
    
         
            +
                  }
         
     | 
| 
       718 
730 
     | 
    
         
             
                }
         
     | 
| 
       719 
731 
     | 
    
         | 
| 
      
 732 
     | 
    
         
            +
                track.sender = undefined;
         
     | 
| 
      
 733 
     | 
    
         
            +
             
     | 
| 
       720 
734 
     | 
    
         
             
                // remove from our maps
         
     | 
| 
       721 
735 
     | 
    
         
             
                this.tracks.delete(publication.trackSid);
         
     | 
| 
       722 
736 
     | 
    
         
             
                switch (publication.kind) {
         
     | 
| 
         @@ -220,6 +220,19 @@ export default class RemoteParticipant extends Participant { 
     | 
|
| 
       220 
220 
     | 
    
         
             
                // always emit events for new publications, Room will not forward them unless it's ready
         
     | 
| 
       221 
221 
     | 
    
         
             
                newTracks.forEach((publication) => {
         
     | 
| 
       222 
222 
     | 
    
         
             
                  this.emit(ParticipantEvent.TrackPublished, publication);
         
     | 
| 
      
 223 
     | 
    
         
            +
                  const existingTrackOfSource = Array.from(this.tracks.values()).find(
         
     | 
| 
      
 224 
     | 
    
         
            +
                    (publishedTrack) => publishedTrack.source === publication.source,
         
     | 
| 
      
 225 
     | 
    
         
            +
                  );
         
     | 
| 
      
 226 
     | 
    
         
            +
                  if (existingTrackOfSource) {
         
     | 
| 
      
 227 
     | 
    
         
            +
                    log.warn(
         
     | 
| 
      
 228 
     | 
    
         
            +
                      `received a second track publication for ${this.identity} with the same source: ${publication.source}`,
         
     | 
| 
      
 229 
     | 
    
         
            +
                      {
         
     | 
| 
      
 230 
     | 
    
         
            +
                        oldTrack: existingTrackOfSource,
         
     | 
| 
      
 231 
     | 
    
         
            +
                        newTrack: publication,
         
     | 
| 
      
 232 
     | 
    
         
            +
                        participant: this,
         
     | 
| 
      
 233 
     | 
    
         
            +
                      },
         
     | 
| 
      
 234 
     | 
    
         
            +
                    );
         
     | 
| 
      
 235 
     | 
    
         
            +
                  }
         
     | 
| 
       223 
236 
     | 
    
         
             
                });
         
     | 
| 
       224 
237 
     | 
    
         | 
| 
       225 
238 
     | 
    
         
             
                // detect removed tracks
         
     |