livekit-client 1.2.0 → 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.
Files changed (36) hide show
  1. package/dist/livekit-client.esm.mjs +1007 -107
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/src/index.d.ts +2 -1
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/options.d.ts +5 -0
  8. package/dist/src/options.d.ts.map +1 -1
  9. package/dist/src/room/DefaultReconnectPolicy.d.ts +8 -0
  10. package/dist/src/room/DefaultReconnectPolicy.d.ts.map +1 -0
  11. package/dist/src/room/PCTransport.d.ts +10 -0
  12. package/dist/src/room/PCTransport.d.ts.map +1 -1
  13. package/dist/src/room/RTCEngine.d.ts +6 -1
  14. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  15. package/dist/src/room/ReconnectPolicy.d.ts +23 -0
  16. package/dist/src/room/ReconnectPolicy.d.ts.map +1 -0
  17. package/dist/src/room/Room.d.ts +8 -0
  18. package/dist/src/room/Room.d.ts.map +1 -1
  19. package/dist/src/room/events.d.ts +2 -2
  20. package/dist/src/room/participant/LocalParticipant.d.ts +3 -3
  21. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  22. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  23. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  24. package/package.json +3 -1
  25. package/src/index.ts +2 -0
  26. package/src/options.ts +6 -0
  27. package/src/room/DefaultReconnectPolicy.ts +35 -0
  28. package/src/room/PCTransport.ts +113 -0
  29. package/src/room/RTCEngine.ts +79 -31
  30. package/src/room/ReconnectPolicy.ts +25 -0
  31. package/src/room/Room.ts +55 -30
  32. package/src/room/events.ts +2 -2
  33. package/src/room/participant/LocalParticipant.ts +56 -20
  34. package/src/room/participant/RemoteParticipant.ts +13 -0
  35. package/src/room/participant/publishUtils.ts +1 -1
  36. package/src/room/track/LocalVideoTrack.ts +0 -1
@@ -2997,7 +2997,7 @@ LongBits$2.prototype.length = function length() {
2997
2997
  };
2998
2998
  })(minimal$1);
2999
2999
 
3000
- var writer = Writer$1;
3000
+ var writer$2 = Writer$1;
3001
3001
  var util$4 = minimal$1;
3002
3002
  var BufferWriter$1; // cyclic
3003
3003
 
@@ -3476,7 +3476,7 @@ Writer$1._configure = function (BufferWriter_) {
3476
3476
 
3477
3477
  var writer_buffer = BufferWriter; // extends Writer
3478
3478
 
3479
- var Writer = writer;
3479
+ var Writer = writer$2;
3480
3480
  (BufferWriter.prototype = Object.create(Writer.prototype)).constructor = BufferWriter;
3481
3481
  var util$3 = minimal$1;
3482
3482
  /**
@@ -4193,7 +4193,7 @@ var roots = {};
4193
4193
 
4194
4194
  protobuf.build = "minimal"; // Serialization
4195
4195
 
4196
- protobuf.Writer = writer;
4196
+ protobuf.Writer = writer$2;
4197
4197
  protobuf.BufferWriter = writer_buffer;
4198
4198
  protobuf.Reader = reader;
4199
4199
  protobuf.BufferReader = reader_buffer; // Utility
@@ -9997,8 +9997,8 @@ var RoomEvent;
9997
9997
  * When we have encountered an error while attempting to create a track.
9998
9998
  * The errors take place in getUserMedia().
9999
9999
  * Use MediaDeviceFailure.getFailure(error) to get the reason of failure.
10000
- * [[getAudioCreateError]] and [[getVideoCreateError]] will indicate if it had
10001
- * an error while creating the audio or video track respectively.
10000
+ * [[LocalParticipant.lastCameraError]] and [[LocalParticipant.lastMicrophoneError]]
10001
+ * will indicate if it had an error while creating the audio or video track respectively.
10002
10002
  *
10003
10003
  * args: (error: Error)
10004
10004
  */
@@ -10265,7 +10265,7 @@ function computeBitrate(currentStats, prevStats) {
10265
10265
  return (bytesNow - bytesPrev) * 8 * 1000 / (currentStats.timestamp - prevStats.timestamp);
10266
10266
  }
10267
10267
 
10268
- var version$1 = "1.2.0";
10268
+ var version$1 = "1.2.3";
10269
10269
 
10270
10270
  const version = version$1;
10271
10271
  const protocolVersion = 8;
@@ -12016,8 +12016,6 @@ class LocalVideoTrack extends LocalTrack {
12016
12016
  }
12017
12017
 
12018
12018
  stop() {
12019
- this.sender = undefined;
12020
-
12021
12019
  this._mediaStreamTrack.getConstraints();
12022
12020
 
12023
12021
  this.simulcastCodecs.forEach(trackInfo => {
@@ -13455,7 +13453,7 @@ function computeVideoEncodings(isScreenShare, width, height, options) {
13455
13453
  encodings.push({
13456
13454
  rid: videoRids[2 - i],
13457
13455
  scaleResolutionDownBy: 2 ** i,
13458
- maxBitrate: videoEncoding ? videoEncoding.maxBitrate / 2 ** i : 0,
13456
+ maxBitrate: videoEncoding ? videoEncoding.maxBitrate / 3 ** i : 0,
13459
13457
 
13460
13458
  /* @ts-ignore */
13461
13459
  maxFramerate: original.encoding.maxFramerate,
@@ -14053,6 +14051,15 @@ class RemoteParticipant extends Participant {
14053
14051
 
14054
14052
  newTracks.forEach(publication => {
14055
14053
  this.emit(ParticipantEvent.TrackPublished, publication);
14054
+ const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => publishedTrack.source === publication.source);
14055
+
14056
+ if (existingTrackOfSource) {
14057
+ livekitLogger.warn("received a second track publication for ".concat(this.identity, " with the same source: ").concat(publication.source), {
14058
+ oldTrack: existingTrackOfSource,
14059
+ newTrack: publication,
14060
+ participant: this
14061
+ });
14062
+ }
14056
14063
  }); // detect removed tracks
14057
14064
 
14058
14065
  this.tracks.forEach(publication => {
@@ -14314,8 +14321,8 @@ class LocalParticipant extends Participant {
14314
14321
  */
14315
14322
 
14316
14323
 
14317
- setCameraEnabled(enabled, options) {
14318
- return this.setTrackEnabled(Track.Source.Camera, enabled, options);
14324
+ setCameraEnabled(enabled, options, publishOptions) {
14325
+ return this.setTrackEnabled(Track.Source.Camera, enabled, options, publishOptions);
14319
14326
  }
14320
14327
  /**
14321
14328
  * Enable or disable a participant's microphone track.
@@ -14325,8 +14332,8 @@ class LocalParticipant extends Participant {
14325
14332
  */
14326
14333
 
14327
14334
 
14328
- setMicrophoneEnabled(enabled, options) {
14329
- return this.setTrackEnabled(Track.Source.Microphone, enabled, options);
14335
+ setMicrophoneEnabled(enabled, options, publishOptions) {
14336
+ return this.setTrackEnabled(Track.Source.Microphone, enabled, options, publishOptions);
14330
14337
  }
14331
14338
  /**
14332
14339
  * Start or stop sharing a participant's screen
@@ -14334,8 +14341,8 @@ class LocalParticipant extends Participant {
14334
14341
  */
14335
14342
 
14336
14343
 
14337
- setScreenShareEnabled(enabled, options) {
14338
- return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options);
14344
+ setScreenShareEnabled(enabled, options, publishOptions) {
14345
+ return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options, publishOptions);
14339
14346
  }
14340
14347
  /** @internal */
14341
14348
 
@@ -14351,7 +14358,7 @@ class LocalParticipant extends Participant {
14351
14358
  return changed;
14352
14359
  }
14353
14360
 
14354
- async setTrackEnabled(source, enabled, options) {
14361
+ async setTrackEnabled(source, enabled, options, publishOptions) {
14355
14362
  var _a, _b;
14356
14363
 
14357
14364
  livekitLogger.debug('setTrackEnabled', {
@@ -14401,7 +14408,7 @@ class LocalParticipant extends Participant {
14401
14408
  const publishPromises = [];
14402
14409
 
14403
14410
  for (const localTrack of localTracks) {
14404
- publishPromises.push(this.publishTrack(localTrack));
14411
+ publishPromises.push(this.publishTrack(localTrack, publishOptions));
14405
14412
  }
14406
14413
 
14407
14414
  const publishedTracks = await Promise.all(publishPromises); // for screen share publications including audio, this will only return the screen share publication, not the screen share audio one
@@ -14581,7 +14588,7 @@ class LocalParticipant extends Participant {
14581
14588
 
14582
14589
 
14583
14590
  async publishTrack(track, options) {
14584
- var _a, _b, _c, _d, _e, _f, _g;
14591
+ var _a, _b, _c, _d, _e, _f, _g, _h;
14585
14592
 
14586
14593
  const opts = _objectSpread2(_objectSpread2({}, (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.publishDefaults), options); // convert raw media track into audio or video track
14587
14594
 
@@ -14618,6 +14625,23 @@ class LocalParticipant extends Participant {
14618
14625
  track.source = opts.source;
14619
14626
  }
14620
14627
 
14628
+ const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => track instanceof LocalTrack && publishedTrack.source === track.source);
14629
+
14630
+ if (existingTrackOfSource) {
14631
+ try {
14632
+ // throw an Error in order to capture the stack trace
14633
+ throw Error("publishing a second track with the same source: ".concat(track.source));
14634
+ } catch (e) {
14635
+ if (e instanceof Error) {
14636
+ livekitLogger.warn(e.message, {
14637
+ oldTrack: existingTrackOfSource,
14638
+ newTrack: track,
14639
+ trace: e.stack
14640
+ });
14641
+ }
14642
+ }
14643
+ }
14644
+
14621
14645
  if (opts.stopMicTrackOnMute && track instanceof LocalAudioTrack) {
14622
14646
  track.stopOnMute = true;
14623
14647
  }
@@ -14725,6 +14749,10 @@ class LocalParticipant extends Participant {
14725
14749
  track.codec = opts.videoCodec;
14726
14750
  }
14727
14751
 
14752
+ if (track.codec === 'av1' && encodings && ((_h = encodings[0]) === null || _h === void 0 ? void 0 : _h.maxBitrate)) {
14753
+ this.engine.publisher.setTrackCodecBitrate(req.cid, track.codec, encodings[0].maxBitrate / 1000);
14754
+ }
14755
+
14728
14756
  this.engine.negotiate(); // store RTPSender
14729
14757
 
14730
14758
  track.sender = transceiver.sender;
@@ -14746,7 +14774,7 @@ class LocalParticipant extends Participant {
14746
14774
 
14747
14775
 
14748
14776
  async publishAdditionalCodecForTrack(track, videoCodec, options) {
14749
- var _a, _b, _c, _d, _e;
14777
+ var _a, _b, _c, _d, _e, _f;
14750
14778
 
14751
14779
  const opts = _objectSpread2(_objectSpread2({}, (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.publishDefaults), options); // clear scalabilityMode setting for backup codec
14752
14780
 
@@ -14814,6 +14842,11 @@ class LocalParticipant extends Participant {
14814
14842
  const transceiver = await this.engine.publisher.pc.addTransceiver(simulcastTrack.mediaStreamTrack, transceiverInit);
14815
14843
  this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
14816
14844
  track.setSimulcastTrackSender(opts.videoCodec, transceiver.sender);
14845
+
14846
+ if (videoCodec === 'av1' && ((_f = encodings[0]) === null || _f === void 0 ? void 0 : _f.maxBitrate)) {
14847
+ this.engine.publisher.setTrackCodecBitrate(req.cid, videoCodec, encodings[0].maxBitrate / 1000);
14848
+ }
14849
+
14817
14850
  this.engine.negotiate();
14818
14851
  livekitLogger.debug("published ".concat(opts.videoCodec, " for track ").concat(track.sid), {
14819
14852
  encodings,
@@ -14840,7 +14873,6 @@ class LocalParticipant extends Participant {
14840
14873
  }
14841
14874
 
14842
14875
  track = publication.track;
14843
- track.sender = undefined;
14844
14876
  track.off(TrackEvent.Muted, this.onTrackMuted);
14845
14877
  track.off(TrackEvent.Unmuted, this.onTrackUnmuted);
14846
14878
  track.off(TrackEvent.Ended, this.handleTrackEnded);
@@ -14855,29 +14887,19 @@ class LocalParticipant extends Participant {
14855
14887
  track.stop();
14856
14888
  }
14857
14889
 
14858
- const {
14859
- mediaStreamTrack
14860
- } = track;
14861
-
14862
- if (this.engine.publisher && this.engine.publisher.pc.connectionState !== 'closed') {
14863
- const senders = this.engine.publisher.pc.getSenders();
14864
- senders.forEach(sender => {
14865
- var _a;
14866
-
14867
- if (sender.track === mediaStreamTrack) {
14868
- try {
14869
- (_a = this.engine.publisher) === null || _a === void 0 ? void 0 : _a.pc.removeTrack(sender);
14870
- this.engine.negotiate();
14871
- } catch (e) {
14872
- livekitLogger.warn('failed to remove track', {
14873
- error: e,
14874
- method: 'unpublishTrack'
14875
- });
14876
- }
14877
- }
14878
- });
14879
- } // remove from our maps
14890
+ if (this.engine.publisher && this.engine.publisher.pc.connectionState !== 'closed' && track.sender) {
14891
+ try {
14892
+ this.engine.publisher.pc.removeTrack(track.sender);
14893
+ this.engine.negotiate();
14894
+ } catch (e) {
14895
+ livekitLogger.warn('failed to remove track', {
14896
+ error: e,
14897
+ method: 'unpublishTrack'
14898
+ });
14899
+ }
14900
+ }
14880
14901
 
14902
+ track.sender = undefined; // remove from our maps
14881
14903
 
14882
14904
  this.tracks.delete(publication.trackSid);
14883
14905
 
@@ -19041,13 +19063,718 @@ function createConnectionParams(token, info, opts) {
19041
19063
  return "?".concat(params.toString());
19042
19064
  }
19043
19065
 
19066
+ const maxRetryDelay = 7000;
19067
+ const DEFAULT_RETRY_DELAYS_IN_MS = [0, 300, 2 * 2 * 300, 3 * 3 * 300, 4 * 4 * 300, maxRetryDelay, maxRetryDelay, maxRetryDelay, maxRetryDelay, maxRetryDelay];
19068
+
19069
+ class DefaultReconnectPolicy {
19070
+ constructor(retryDelays) {
19071
+ this._retryDelays = retryDelays !== undefined ? [...retryDelays] : DEFAULT_RETRY_DELAYS_IN_MS;
19072
+ }
19073
+
19074
+ nextRetryDelayInMs(context) {
19075
+ if (context.retryCount >= this._retryDelays.length) return null;
19076
+ const retryDelay = this._retryDelays[context.retryCount];
19077
+ if (context.retryCount <= 1) return retryDelay;
19078
+ return retryDelay + Math.random() * 1000;
19079
+ }
19080
+
19081
+ }
19082
+
19083
+ var parser$1 = {};
19084
+
19085
+ var grammar$2 = {exports: {}};
19086
+
19087
+ var grammar$1 = grammar$2.exports = {
19088
+ v: [{
19089
+ name: 'version',
19090
+ reg: /^(\d*)$/
19091
+ }],
19092
+ o: [{
19093
+ // o=- 20518 0 IN IP4 203.0.113.1
19094
+ // NB: sessionId will be a String in most cases because it is huge
19095
+ name: 'origin',
19096
+ reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,
19097
+ names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],
19098
+ format: '%s %s %d %s IP%d %s'
19099
+ }],
19100
+ // default parsing of these only (though some of these feel outdated)
19101
+ s: [{
19102
+ name: 'name'
19103
+ }],
19104
+ i: [{
19105
+ name: 'description'
19106
+ }],
19107
+ u: [{
19108
+ name: 'uri'
19109
+ }],
19110
+ e: [{
19111
+ name: 'email'
19112
+ }],
19113
+ p: [{
19114
+ name: 'phone'
19115
+ }],
19116
+ z: [{
19117
+ name: 'timezones'
19118
+ }],
19119
+ // TODO: this one can actually be parsed properly...
19120
+ r: [{
19121
+ name: 'repeats'
19122
+ }],
19123
+ // TODO: this one can also be parsed properly
19124
+ // k: [{}], // outdated thing ignored
19125
+ t: [{
19126
+ // t=0 0
19127
+ name: 'timing',
19128
+ reg: /^(\d*) (\d*)/,
19129
+ names: ['start', 'stop'],
19130
+ format: '%d %d'
19131
+ }],
19132
+ c: [{
19133
+ // c=IN IP4 10.47.197.26
19134
+ name: 'connection',
19135
+ reg: /^IN IP(\d) (\S*)/,
19136
+ names: ['version', 'ip'],
19137
+ format: 'IN IP%d %s'
19138
+ }],
19139
+ b: [{
19140
+ // b=AS:4000
19141
+ push: 'bandwidth',
19142
+ reg: /^(TIAS|AS|CT|RR|RS):(\d*)/,
19143
+ names: ['type', 'limit'],
19144
+ format: '%s:%s'
19145
+ }],
19146
+ m: [{
19147
+ // m=video 51744 RTP/AVP 126 97 98 34 31
19148
+ // NB: special - pushes to session
19149
+ // TODO: rtp/fmtp should be filtered by the payloads found here?
19150
+ reg: /^(\w*) (\d*) ([\w/]*)(?: (.*))?/,
19151
+ names: ['type', 'port', 'protocol', 'payloads'],
19152
+ format: '%s %d %s %s'
19153
+ }],
19154
+ a: [{
19155
+ // a=rtpmap:110 opus/48000/2
19156
+ push: 'rtp',
19157
+ reg: /^rtpmap:(\d*) ([\w\-.]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/,
19158
+ names: ['payload', 'codec', 'rate', 'encoding'],
19159
+ format: function (o) {
19160
+ return o.encoding ? 'rtpmap:%d %s/%s/%s' : o.rate ? 'rtpmap:%d %s/%s' : 'rtpmap:%d %s';
19161
+ }
19162
+ }, {
19163
+ // a=fmtp:108 profile-level-id=24;object=23;bitrate=64000
19164
+ // a=fmtp:111 minptime=10; useinbandfec=1
19165
+ push: 'fmtp',
19166
+ reg: /^fmtp:(\d*) ([\S| ]*)/,
19167
+ names: ['payload', 'config'],
19168
+ format: 'fmtp:%d %s'
19169
+ }, {
19170
+ // a=control:streamid=0
19171
+ name: 'control',
19172
+ reg: /^control:(.*)/,
19173
+ format: 'control:%s'
19174
+ }, {
19175
+ // a=rtcp:65179 IN IP4 193.84.77.194
19176
+ name: 'rtcp',
19177
+ reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,
19178
+ names: ['port', 'netType', 'ipVer', 'address'],
19179
+ format: function (o) {
19180
+ return o.address != null ? 'rtcp:%d %s IP%d %s' : 'rtcp:%d';
19181
+ }
19182
+ }, {
19183
+ // a=rtcp-fb:98 trr-int 100
19184
+ push: 'rtcpFbTrrInt',
19185
+ reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/,
19186
+ names: ['payload', 'value'],
19187
+ format: 'rtcp-fb:%s trr-int %d'
19188
+ }, {
19189
+ // a=rtcp-fb:98 nack rpsi
19190
+ push: 'rtcpFb',
19191
+ reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,
19192
+ names: ['payload', 'type', 'subtype'],
19193
+ format: function (o) {
19194
+ return o.subtype != null ? 'rtcp-fb:%s %s %s' : 'rtcp-fb:%s %s';
19195
+ }
19196
+ }, {
19197
+ // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
19198
+ // a=extmap:1/recvonly URI-gps-string
19199
+ // a=extmap:3 urn:ietf:params:rtp-hdrext:encrypt urn:ietf:params:rtp-hdrext:smpte-tc 25@600/24
19200
+ push: 'ext',
19201
+ reg: /^extmap:(\d+)(?:\/(\w+))?(?: (urn:ietf:params:rtp-hdrext:encrypt))? (\S*)(?: (\S*))?/,
19202
+ names: ['value', 'direction', 'encrypt-uri', 'uri', 'config'],
19203
+ format: function (o) {
19204
+ return 'extmap:%d' + (o.direction ? '/%s' : '%v') + (o['encrypt-uri'] ? ' %s' : '%v') + ' %s' + (o.config ? ' %s' : '');
19205
+ }
19206
+ }, {
19207
+ // a=extmap-allow-mixed
19208
+ name: 'extmapAllowMixed',
19209
+ reg: /^(extmap-allow-mixed)/
19210
+ }, {
19211
+ // a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
19212
+ push: 'crypto',
19213
+ reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,
19214
+ names: ['id', 'suite', 'config', 'sessionConfig'],
19215
+ format: function (o) {
19216
+ return o.sessionConfig != null ? 'crypto:%d %s %s %s' : 'crypto:%d %s %s';
19217
+ }
19218
+ }, {
19219
+ // a=setup:actpass
19220
+ name: 'setup',
19221
+ reg: /^setup:(\w*)/,
19222
+ format: 'setup:%s'
19223
+ }, {
19224
+ // a=connection:new
19225
+ name: 'connectionType',
19226
+ reg: /^connection:(new|existing)/,
19227
+ format: 'connection:%s'
19228
+ }, {
19229
+ // a=mid:1
19230
+ name: 'mid',
19231
+ reg: /^mid:([^\s]*)/,
19232
+ format: 'mid:%s'
19233
+ }, {
19234
+ // a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a
19235
+ name: 'msid',
19236
+ reg: /^msid:(.*)/,
19237
+ format: 'msid:%s'
19238
+ }, {
19239
+ // a=ptime:20
19240
+ name: 'ptime',
19241
+ reg: /^ptime:(\d*(?:\.\d*)*)/,
19242
+ format: 'ptime:%d'
19243
+ }, {
19244
+ // a=maxptime:60
19245
+ name: 'maxptime',
19246
+ reg: /^maxptime:(\d*(?:\.\d*)*)/,
19247
+ format: 'maxptime:%d'
19248
+ }, {
19249
+ // a=sendrecv
19250
+ name: 'direction',
19251
+ reg: /^(sendrecv|recvonly|sendonly|inactive)/
19252
+ }, {
19253
+ // a=ice-lite
19254
+ name: 'icelite',
19255
+ reg: /^(ice-lite)/
19256
+ }, {
19257
+ // a=ice-ufrag:F7gI
19258
+ name: 'iceUfrag',
19259
+ reg: /^ice-ufrag:(\S*)/,
19260
+ format: 'ice-ufrag:%s'
19261
+ }, {
19262
+ // a=ice-pwd:x9cml/YzichV2+XlhiMu8g
19263
+ name: 'icePwd',
19264
+ reg: /^ice-pwd:(\S*)/,
19265
+ format: 'ice-pwd:%s'
19266
+ }, {
19267
+ // a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33
19268
+ name: 'fingerprint',
19269
+ reg: /^fingerprint:(\S*) (\S*)/,
19270
+ names: ['type', 'hash'],
19271
+ format: 'fingerprint:%s %s'
19272
+ }, {
19273
+ // a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
19274
+ // a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 network-id 3 network-cost 10
19275
+ // a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 network-id 3 network-cost 10
19276
+ // a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0 network-id 3 network-cost 10
19277
+ // a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0 network-id 3 network-cost 10
19278
+ push: 'candidates',
19279
+ reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?(?: network-id (\d*))?(?: network-cost (\d*))?/,
19280
+ names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation', 'network-id', 'network-cost'],
19281
+ format: function (o) {
19282
+ var str = 'candidate:%s %d %s %d %s %d typ %s';
19283
+ str += o.raddr != null ? ' raddr %s rport %d' : '%v%v'; // NB: candidate has three optional chunks, so %void middles one if it's missing
19284
+
19285
+ str += o.tcptype != null ? ' tcptype %s' : '%v';
19286
+
19287
+ if (o.generation != null) {
19288
+ str += ' generation %d';
19289
+ }
19290
+
19291
+ str += o['network-id'] != null ? ' network-id %d' : '%v';
19292
+ str += o['network-cost'] != null ? ' network-cost %d' : '%v';
19293
+ return str;
19294
+ }
19295
+ }, {
19296
+ // a=end-of-candidates (keep after the candidates line for readability)
19297
+ name: 'endOfCandidates',
19298
+ reg: /^(end-of-candidates)/
19299
+ }, {
19300
+ // a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...
19301
+ name: 'remoteCandidates',
19302
+ reg: /^remote-candidates:(.*)/,
19303
+ format: 'remote-candidates:%s'
19304
+ }, {
19305
+ // a=ice-options:google-ice
19306
+ name: 'iceOptions',
19307
+ reg: /^ice-options:(\S*)/,
19308
+ format: 'ice-options:%s'
19309
+ }, {
19310
+ // a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1
19311
+ push: 'ssrcs',
19312
+ reg: /^ssrc:(\d*) ([\w_-]*)(?::(.*))?/,
19313
+ names: ['id', 'attribute', 'value'],
19314
+ format: function (o) {
19315
+ var str = 'ssrc:%d';
19316
+
19317
+ if (o.attribute != null) {
19318
+ str += ' %s';
19319
+
19320
+ if (o.value != null) {
19321
+ str += ':%s';
19322
+ }
19323
+ }
19324
+
19325
+ return str;
19326
+ }
19327
+ }, {
19328
+ // a=ssrc-group:FEC 1 2
19329
+ // a=ssrc-group:FEC-FR 3004364195 1080772241
19330
+ push: 'ssrcGroups',
19331
+ // token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 / %x41-5A / %x5E-7E
19332
+ reg: /^ssrc-group:([\x21\x23\x24\x25\x26\x27\x2A\x2B\x2D\x2E\w]*) (.*)/,
19333
+ names: ['semantics', 'ssrcs'],
19334
+ format: 'ssrc-group:%s %s'
19335
+ }, {
19336
+ // a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV
19337
+ name: 'msidSemantic',
19338
+ reg: /^msid-semantic:\s?(\w*) (\S*)/,
19339
+ names: ['semantic', 'token'],
19340
+ format: 'msid-semantic: %s %s' // space after ':' is not accidental
19341
+
19342
+ }, {
19343
+ // a=group:BUNDLE audio video
19344
+ push: 'groups',
19345
+ reg: /^group:(\w*) (.*)/,
19346
+ names: ['type', 'mids'],
19347
+ format: 'group:%s %s'
19348
+ }, {
19349
+ // a=rtcp-mux
19350
+ name: 'rtcpMux',
19351
+ reg: /^(rtcp-mux)/
19352
+ }, {
19353
+ // a=rtcp-rsize
19354
+ name: 'rtcpRsize',
19355
+ reg: /^(rtcp-rsize)/
19356
+ }, {
19357
+ // a=sctpmap:5000 webrtc-datachannel 1024
19358
+ name: 'sctpmap',
19359
+ reg: /^sctpmap:([\w_/]*) (\S*)(?: (\S*))?/,
19360
+ names: ['sctpmapNumber', 'app', 'maxMessageSize'],
19361
+ format: function (o) {
19362
+ return o.maxMessageSize != null ? 'sctpmap:%s %s %s' : 'sctpmap:%s %s';
19363
+ }
19364
+ }, {
19365
+ // a=x-google-flag:conference
19366
+ name: 'xGoogleFlag',
19367
+ reg: /^x-google-flag:([^\s]*)/,
19368
+ format: 'x-google-flag:%s'
19369
+ }, {
19370
+ // a=rid:1 send max-width=1280;max-height=720;max-fps=30;depend=0
19371
+ push: 'rids',
19372
+ reg: /^rid:([\d\w]+) (\w+)(?: ([\S| ]*))?/,
19373
+ names: ['id', 'direction', 'params'],
19374
+ format: function (o) {
19375
+ return o.params ? 'rid:%s %s %s' : 'rid:%s %s';
19376
+ }
19377
+ }, {
19378
+ // a=imageattr:97 send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] recv [x=330,y=250]
19379
+ // a=imageattr:* send [x=800,y=640] recv *
19380
+ // a=imageattr:100 recv [x=320,y=240]
19381
+ push: 'imageattrs',
19382
+ reg: new RegExp( // a=imageattr:97
19383
+ '^imageattr:(\\d+|\\*)' + // send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320]
19384
+ '[\\s\\t]+(send|recv)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*)' + // recv [x=330,y=250]
19385
+ '(?:[\\s\\t]+(recv|send)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*))?'),
19386
+ names: ['pt', 'dir1', 'attrs1', 'dir2', 'attrs2'],
19387
+ format: function (o) {
19388
+ return 'imageattr:%s %s %s' + (o.dir2 ? ' %s %s' : '');
19389
+ }
19390
+ }, {
19391
+ // a=simulcast:send 1,2,3;~4,~5 recv 6;~7,~8
19392
+ // a=simulcast:recv 1;4,5 send 6;7
19393
+ name: 'simulcast',
19394
+ reg: new RegExp( // a=simulcast:
19395
+ '^simulcast:' + // send 1,2,3;~4,~5
19396
+ '(send|recv) ([a-zA-Z0-9\\-_~;,]+)' + // space + recv 6;~7,~8
19397
+ '(?:\\s?(send|recv) ([a-zA-Z0-9\\-_~;,]+))?' + // end
19398
+ '$'),
19399
+ names: ['dir1', 'list1', 'dir2', 'list2'],
19400
+ format: function (o) {
19401
+ return 'simulcast:%s %s' + (o.dir2 ? ' %s %s' : '');
19402
+ }
19403
+ }, {
19404
+ // old simulcast draft 03 (implemented by Firefox)
19405
+ // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-03
19406
+ // a=simulcast: recv pt=97;98 send pt=97
19407
+ // a=simulcast: send rid=5;6;7 paused=6,7
19408
+ name: 'simulcast_03',
19409
+ reg: /^simulcast:[\s\t]+([\S+\s\t]+)$/,
19410
+ names: ['value'],
19411
+ format: 'simulcast: %s'
19412
+ }, {
19413
+ // a=framerate:25
19414
+ // a=framerate:29.97
19415
+ name: 'framerate',
19416
+ reg: /^framerate:(\d+(?:$|\.\d+))/,
19417
+ format: 'framerate:%s'
19418
+ }, {
19419
+ // RFC4570
19420
+ // a=source-filter: incl IN IP4 239.5.2.31 10.1.15.5
19421
+ name: 'sourceFilter',
19422
+ reg: /^source-filter: *(excl|incl) (\S*) (IP4|IP6|\*) (\S*) (.*)/,
19423
+ names: ['filterMode', 'netType', 'addressTypes', 'destAddress', 'srcList'],
19424
+ format: 'source-filter: %s %s %s %s %s'
19425
+ }, {
19426
+ // a=bundle-only
19427
+ name: 'bundleOnly',
19428
+ reg: /^(bundle-only)/
19429
+ }, {
19430
+ // a=label:1
19431
+ name: 'label',
19432
+ reg: /^label:(.+)/,
19433
+ format: 'label:%s'
19434
+ }, {
19435
+ // RFC version 26 for SCTP over DTLS
19436
+ // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-5
19437
+ name: 'sctpPort',
19438
+ reg: /^sctp-port:(\d+)$/,
19439
+ format: 'sctp-port:%s'
19440
+ }, {
19441
+ // RFC version 26 for SCTP over DTLS
19442
+ // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-6
19443
+ name: 'maxMessageSize',
19444
+ reg: /^max-message-size:(\d+)$/,
19445
+ format: 'max-message-size:%s'
19446
+ }, {
19447
+ // RFC7273
19448
+ // a=ts-refclk:ptp=IEEE1588-2008:39-A7-94-FF-FE-07-CB-D0:37
19449
+ push: 'tsRefClocks',
19450
+ reg: /^ts-refclk:([^\s=]*)(?:=(\S*))?/,
19451
+ names: ['clksrc', 'clksrcExt'],
19452
+ format: function (o) {
19453
+ return 'ts-refclk:%s' + (o.clksrcExt != null ? '=%s' : '');
19454
+ }
19455
+ }, {
19456
+ // RFC7273
19457
+ // a=mediaclk:direct=963214424
19458
+ name: 'mediaClk',
19459
+ reg: /^mediaclk:(?:id=(\S*))? *([^\s=]*)(?:=(\S*))?(?: *rate=(\d+)\/(\d+))?/,
19460
+ names: ['id', 'mediaClockName', 'mediaClockValue', 'rateNumerator', 'rateDenominator'],
19461
+ format: function (o) {
19462
+ var str = 'mediaclk:';
19463
+ str += o.id != null ? 'id=%s %s' : '%v%s';
19464
+ str += o.mediaClockValue != null ? '=%s' : '';
19465
+ str += o.rateNumerator != null ? ' rate=%s' : '';
19466
+ str += o.rateDenominator != null ? '/%s' : '';
19467
+ return str;
19468
+ }
19469
+ }, {
19470
+ // a=keywds:keywords
19471
+ name: 'keywords',
19472
+ reg: /^keywds:(.+)$/,
19473
+ format: 'keywds:%s'
19474
+ }, {
19475
+ // a=content:main
19476
+ name: 'content',
19477
+ reg: /^content:(.+)/,
19478
+ format: 'content:%s'
19479
+ }, // BFCP https://tools.ietf.org/html/rfc4583
19480
+ {
19481
+ // a=floorctrl:c-s
19482
+ name: 'bfcpFloorCtrl',
19483
+ reg: /^floorctrl:(c-only|s-only|c-s)/,
19484
+ format: 'floorctrl:%s'
19485
+ }, {
19486
+ // a=confid:1
19487
+ name: 'bfcpConfId',
19488
+ reg: /^confid:(\d+)/,
19489
+ format: 'confid:%s'
19490
+ }, {
19491
+ // a=userid:1
19492
+ name: 'bfcpUserId',
19493
+ reg: /^userid:(\d+)/,
19494
+ format: 'userid:%s'
19495
+ }, {
19496
+ // a=floorid:1
19497
+ name: 'bfcpFloorId',
19498
+ reg: /^floorid:(.+) (?:m-stream|mstrm):(.+)/,
19499
+ names: ['id', 'mStream'],
19500
+ format: 'floorid:%s mstrm:%s'
19501
+ }, {
19502
+ // any a= that we don't understand is kept verbatim on media.invalid
19503
+ push: 'invalid',
19504
+ names: ['value']
19505
+ }]
19506
+ }; // set sensible defaults to avoid polluting the grammar with boring details
19507
+
19508
+ Object.keys(grammar$1).forEach(function (key) {
19509
+ var objs = grammar$1[key];
19510
+ objs.forEach(function (obj) {
19511
+ if (!obj.reg) {
19512
+ obj.reg = /(.*)/;
19513
+ }
19514
+
19515
+ if (!obj.format) {
19516
+ obj.format = '%s';
19517
+ }
19518
+ });
19519
+ });
19520
+
19521
+ (function (exports) {
19522
+ var toIntIfInt = function (v) {
19523
+ return String(Number(v)) === v ? Number(v) : v;
19524
+ };
19525
+
19526
+ var attachProperties = function (match, location, names, rawName) {
19527
+ if (rawName && !names) {
19528
+ location[rawName] = toIntIfInt(match[1]);
19529
+ } else {
19530
+ for (var i = 0; i < names.length; i += 1) {
19531
+ if (match[i + 1] != null) {
19532
+ location[names[i]] = toIntIfInt(match[i + 1]);
19533
+ }
19534
+ }
19535
+ }
19536
+ };
19537
+
19538
+ var parseReg = function (obj, location, content) {
19539
+ var needsBlank = obj.name && obj.names;
19540
+
19541
+ if (obj.push && !location[obj.push]) {
19542
+ location[obj.push] = [];
19543
+ } else if (needsBlank && !location[obj.name]) {
19544
+ location[obj.name] = {};
19545
+ }
19546
+
19547
+ var keyLocation = obj.push ? {} : // blank object that will be pushed
19548
+ needsBlank ? location[obj.name] : location; // otherwise, named location or root
19549
+
19550
+ attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);
19551
+
19552
+ if (obj.push) {
19553
+ location[obj.push].push(keyLocation);
19554
+ }
19555
+ };
19556
+
19557
+ var grammar = grammar$2.exports;
19558
+ var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);
19559
+
19560
+ exports.parse = function (sdp) {
19561
+ var session = {},
19562
+ media = [],
19563
+ location = session; // points at where properties go under (one of the above)
19564
+ // parse lines we understand
19565
+
19566
+ sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) {
19567
+ var type = l[0];
19568
+ var content = l.slice(2);
19569
+
19570
+ if (type === 'm') {
19571
+ media.push({
19572
+ rtp: [],
19573
+ fmtp: []
19574
+ });
19575
+ location = media[media.length - 1]; // point at latest media line
19576
+ }
19577
+
19578
+ for (var j = 0; j < (grammar[type] || []).length; j += 1) {
19579
+ var obj = grammar[type][j];
19580
+
19581
+ if (obj.reg.test(content)) {
19582
+ return parseReg(obj, location, content);
19583
+ }
19584
+ }
19585
+ });
19586
+ session.media = media; // link it up
19587
+
19588
+ return session;
19589
+ };
19590
+
19591
+ var paramReducer = function (acc, expr) {
19592
+ var s = expr.split(/=(.+)/, 2);
19593
+
19594
+ if (s.length === 2) {
19595
+ acc[s[0]] = toIntIfInt(s[1]);
19596
+ } else if (s.length === 1 && expr.length > 1) {
19597
+ acc[s[0]] = undefined;
19598
+ }
19599
+
19600
+ return acc;
19601
+ };
19602
+
19603
+ exports.parseParams = function (str) {
19604
+ return str.split(/;\s?/).reduce(paramReducer, {});
19605
+ }; // For backward compatibility - alias will be removed in 3.0.0
19606
+
19607
+
19608
+ exports.parseFmtpConfig = exports.parseParams;
19609
+
19610
+ exports.parsePayloads = function (str) {
19611
+ return str.toString().split(' ').map(Number);
19612
+ };
19613
+
19614
+ exports.parseRemoteCandidates = function (str) {
19615
+ var candidates = [];
19616
+ var parts = str.split(' ').map(toIntIfInt);
19617
+
19618
+ for (var i = 0; i < parts.length; i += 3) {
19619
+ candidates.push({
19620
+ component: parts[i],
19621
+ ip: parts[i + 1],
19622
+ port: parts[i + 2]
19623
+ });
19624
+ }
19625
+
19626
+ return candidates;
19627
+ };
19628
+
19629
+ exports.parseImageAttributes = function (str) {
19630
+ return str.split(' ').map(function (item) {
19631
+ return item.substring(1, item.length - 1).split(',').reduce(paramReducer, {});
19632
+ });
19633
+ };
19634
+
19635
+ exports.parseSimulcastStreamList = function (str) {
19636
+ return str.split(';').map(function (stream) {
19637
+ return stream.split(',').map(function (format) {
19638
+ var scid,
19639
+ paused = false;
19640
+
19641
+ if (format[0] !== '~') {
19642
+ scid = toIntIfInt(format);
19643
+ } else {
19644
+ scid = toIntIfInt(format.substring(1, format.length));
19645
+ paused = true;
19646
+ }
19647
+
19648
+ return {
19649
+ scid: scid,
19650
+ paused: paused
19651
+ };
19652
+ });
19653
+ });
19654
+ };
19655
+ })(parser$1);
19656
+
19657
+ var grammar = grammar$2.exports; // customized util.format - discards excess arguments and can void middle ones
19658
+
19659
+ var formatRegExp = /%[sdv%]/g;
19660
+
19661
+ var format = function (formatStr) {
19662
+ var i = 1;
19663
+ var args = arguments;
19664
+ var len = args.length;
19665
+ return formatStr.replace(formatRegExp, function (x) {
19666
+ if (i >= len) {
19667
+ return x; // missing argument
19668
+ }
19669
+
19670
+ var arg = args[i];
19671
+ i += 1;
19672
+
19673
+ switch (x) {
19674
+ case '%%':
19675
+ return '%';
19676
+
19677
+ case '%s':
19678
+ return String(arg);
19679
+
19680
+ case '%d':
19681
+ return Number(arg);
19682
+
19683
+ case '%v':
19684
+ return '';
19685
+ }
19686
+ }); // NB: we discard excess arguments - they are typically undefined from makeLine
19687
+ };
19688
+
19689
+ var makeLine = function (type, obj, location) {
19690
+ var str = obj.format instanceof Function ? obj.format(obj.push ? location : location[obj.name]) : obj.format;
19691
+ var args = [type + '=' + str];
19692
+
19693
+ if (obj.names) {
19694
+ for (var i = 0; i < obj.names.length; i += 1) {
19695
+ var n = obj.names[i];
19696
+
19697
+ if (obj.name) {
19698
+ args.push(location[obj.name][n]);
19699
+ } else {
19700
+ // for mLine and push attributes
19701
+ args.push(location[obj.names[i]]);
19702
+ }
19703
+ }
19704
+ } else {
19705
+ args.push(location[obj.name]);
19706
+ }
19707
+
19708
+ return format.apply(null, args);
19709
+ }; // RFC specified order
19710
+ // TODO: extend this with all the rest
19711
+
19712
+
19713
+ var defaultOuterOrder = ['v', 'o', 's', 'i', 'u', 'e', 'p', 'c', 'b', 't', 'r', 'z', 'a'];
19714
+ var defaultInnerOrder = ['i', 'c', 'b', 'a'];
19715
+
19716
+ var writer$1 = function (session, opts) {
19717
+ opts = opts || {}; // ensure certain properties exist
19718
+
19719
+ if (session.version == null) {
19720
+ session.version = 0; // 'v=0' must be there (only defined version atm)
19721
+ }
19722
+
19723
+ if (session.name == null) {
19724
+ session.name = ' '; // 's= ' must be there if no meaningful name set
19725
+ }
19726
+
19727
+ session.media.forEach(function (mLine) {
19728
+ if (mLine.payloads == null) {
19729
+ mLine.payloads = '';
19730
+ }
19731
+ });
19732
+ var outerOrder = opts.outerOrder || defaultOuterOrder;
19733
+ var innerOrder = opts.innerOrder || defaultInnerOrder;
19734
+ var sdp = []; // loop through outerOrder for matching properties on session
19735
+
19736
+ outerOrder.forEach(function (type) {
19737
+ grammar[type].forEach(function (obj) {
19738
+ if (obj.name in session && session[obj.name] != null) {
19739
+ sdp.push(makeLine(type, obj, session));
19740
+ } else if (obj.push in session && session[obj.push] != null) {
19741
+ session[obj.push].forEach(function (el) {
19742
+ sdp.push(makeLine(type, obj, el));
19743
+ });
19744
+ }
19745
+ });
19746
+ }); // then for each media line, follow the innerOrder
19747
+
19748
+ session.media.forEach(function (mLine) {
19749
+ sdp.push(makeLine('m', grammar.m[0], mLine));
19750
+ innerOrder.forEach(function (type) {
19751
+ grammar[type].forEach(function (obj) {
19752
+ if (obj.name in mLine && mLine[obj.name] != null) {
19753
+ sdp.push(makeLine(type, obj, mLine));
19754
+ } else if (obj.push in mLine && mLine[obj.push] != null) {
19755
+ mLine[obj.push].forEach(function (el) {
19756
+ sdp.push(makeLine(type, obj, el));
19757
+ });
19758
+ }
19759
+ });
19760
+ });
19761
+ });
19762
+ return sdp.join('\r\n') + '\r\n';
19763
+ };
19764
+
19765
+ var parser = parser$1;
19766
+ var writer = writer$1;
19767
+ var write = writer;
19768
+ var parse = parser.parse;
19769
+
19044
19770
  /** @internal */
19045
19771
 
19046
19772
  class PCTransport {
19047
19773
  constructor(config) {
19048
19774
  this.pendingCandidates = [];
19049
19775
  this.restartingIce = false;
19050
- this.renegotiate = false; // debounced negotiate interface
19776
+ this.renegotiate = false;
19777
+ this.trackBitrates = []; // debounced negotiate interface
19051
19778
 
19052
19779
  this.negotiate = r(() => {
19053
19780
  this.createAndSendOffer();
@@ -19082,6 +19809,8 @@ class PCTransport {
19082
19809
  }
19083
19810
 
19084
19811
  async createAndSendOffer(options) {
19812
+ var _a;
19813
+
19085
19814
  if (this.onOffer === undefined) {
19086
19815
  return;
19087
19816
  }
@@ -19112,21 +19841,114 @@ class PCTransport {
19112
19841
 
19113
19842
  livekitLogger.debug('starting to negotiate');
19114
19843
  const offer = await this.pc.createOffer(options);
19844
+ const sdpParsed = parse((_a = offer.sdp) !== null && _a !== void 0 ? _a : '');
19845
+ sdpParsed.media.forEach(media => {
19846
+ if (media.type === 'audio') {
19847
+ ensureAudioNack(media);
19848
+ } else if (media.type === 'video') {
19849
+ // mung sdp for codec bitrate setting that can't apply by sendEncoding
19850
+ this.trackBitrates.some(trackbr => {
19851
+ if (!media.msid || !media.msid.includes(trackbr.sid)) {
19852
+ return false;
19853
+ }
19854
+
19855
+ let codecPayload = 0;
19856
+ media.rtp.some(rtp => {
19857
+ if (rtp.codec.toUpperCase() === trackbr.codec.toUpperCase()) {
19858
+ codecPayload = rtp.payload;
19859
+ return true;
19860
+ }
19861
+
19862
+ return false;
19863
+ }); // add x-google-max-bitrate to fmtp line if not exist
19864
+
19865
+ if (codecPayload > 0) {
19866
+ if (!media.fmtp.some(fmtp => {
19867
+ if (fmtp.payload === codecPayload) {
19868
+ if (!fmtp.config.includes('x-google-max-bitrate')) {
19869
+ fmtp.config += ";x-google-max-bitrate=".concat(trackbr.maxbr);
19870
+ }
19871
+
19872
+ return true;
19873
+ }
19874
+
19875
+ return false;
19876
+ })) {
19877
+ media.fmtp.push({
19878
+ payload: codecPayload,
19879
+ config: "x-google-max-bitrate=".concat(trackbr.maxbr)
19880
+ });
19881
+ }
19882
+ }
19883
+
19884
+ return true;
19885
+ });
19886
+ }
19887
+ });
19888
+ offer.sdp = write(sdpParsed);
19889
+ this.trackBitrates = [];
19115
19890
  await this.pc.setLocalDescription(offer);
19116
19891
  this.onOffer(offer);
19117
19892
  }
19118
19893
 
19894
+ async createAndSetAnswer() {
19895
+ var _a;
19896
+
19897
+ const answer = await this.pc.createAnswer();
19898
+ const sdpParsed = parse((_a = answer.sdp) !== null && _a !== void 0 ? _a : '');
19899
+ sdpParsed.media.forEach(media => {
19900
+ if (media.type === 'audio') {
19901
+ ensureAudioNack(media);
19902
+ }
19903
+ });
19904
+ answer.sdp = write(sdpParsed);
19905
+ await this.pc.setLocalDescription(answer);
19906
+ return answer;
19907
+ }
19908
+
19909
+ setTrackCodecBitrate(sid, codec, maxbr) {
19910
+ this.trackBitrates.push({
19911
+ sid,
19912
+ codec,
19913
+ maxbr
19914
+ });
19915
+ }
19916
+
19119
19917
  close() {
19120
19918
  this.pc.close();
19121
19919
  }
19122
19920
 
19123
19921
  }
19124
19922
 
19923
+ function ensureAudioNack(media) {
19924
+ // found opus codec to add nack fb
19925
+ let opusPayload = 0;
19926
+ media.rtp.some(rtp => {
19927
+ if (rtp.codec === 'opus') {
19928
+ opusPayload = rtp.payload;
19929
+ return true;
19930
+ }
19931
+
19932
+ return false;
19933
+ }); // add nack rtcpfb if not exist
19934
+
19935
+ if (opusPayload > 0) {
19936
+ if (!media.rtcpFb) {
19937
+ media.rtcpFb = [];
19938
+ }
19939
+
19940
+ if (!media.rtcpFb.some(fb => fb.payload === opusPayload && fb.type === 'nack')) {
19941
+ media.rtcpFb.push({
19942
+ payload: opusPayload,
19943
+ type: 'nack'
19944
+ });
19945
+ }
19946
+ }
19947
+ }
19948
+
19125
19949
  const lossyDataChannel = '_lossy';
19126
19950
  const reliableDataChannel = '_reliable';
19127
- const maxReconnectRetries = 10;
19128
19951
  const minReconnectWait = 2 * 1000;
19129
- const maxReconnectDuration = 60 * 1000;
19130
19952
  const maxICEConnectTimeout = 15 * 1000;
19131
19953
  var PCState;
19132
19954
 
@@ -19141,8 +19963,14 @@ var PCState;
19141
19963
 
19142
19964
 
19143
19965
  class RTCEngine extends events.exports.EventEmitter {
19144
- constructor() {
19966
+ constructor(options) {
19967
+ var _this;
19968
+
19969
+ var _a;
19970
+
19145
19971
  super();
19972
+ _this = this;
19973
+ this.options = options;
19146
19974
  this.rtcConfig = {};
19147
19975
  this.subscriberPrimary = false;
19148
19976
  this.pcState = PCState.New;
@@ -19217,53 +20045,85 @@ class RTCEngine extends events.exports.EventEmitter {
19217
20045
  // after a number of retries, we'll close and give up permanently
19218
20046
 
19219
20047
 
19220
- this.handleDisconnect = connection => {
19221
- if (this._isClosed) {
20048
+ this.handleDisconnect = function (connection) {
20049
+ let signalEvents = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
20050
+
20051
+ if (_this._isClosed) {
19222
20052
  return;
19223
20053
  }
19224
20054
 
19225
20055
  livekitLogger.debug("".concat(connection, " disconnected"));
19226
20056
 
19227
- if (this.reconnectAttempts === 0) {
20057
+ if (_this.reconnectAttempts === 0) {
19228
20058
  // only reset start time on the first try
19229
- this.reconnectStart = Date.now();
20059
+ _this.reconnectStart = Date.now();
19230
20060
  }
19231
20061
 
19232
- const delay = this.reconnectAttempts * this.reconnectAttempts * 300;
19233
- setTimeout(async () => {
20062
+ const disconnect = duration => {
20063
+ livekitLogger.info("could not recover connection after ".concat(_this.reconnectAttempts, " attempts, ").concat(duration, "ms. giving up"));
20064
+
20065
+ _this.emit(EngineEvent.Disconnected);
20066
+
20067
+ _this.close();
20068
+ };
20069
+
20070
+ const duration = Date.now() - _this.reconnectStart;
20071
+
20072
+ const delay = _this.getNextRetryDelay({
20073
+ elapsedMs: duration,
20074
+ retryCount: _this.reconnectAttempts
20075
+ });
20076
+
20077
+ if (delay === null) {
20078
+ disconnect(duration);
20079
+ return;
20080
+ }
20081
+
20082
+ livekitLogger.debug("reconnecting in ".concat(delay, "ms"));
20083
+
20084
+ if (_this.reconnectTimeout) {
20085
+ clearTimeout(_this.reconnectTimeout);
20086
+ }
20087
+
20088
+ _this.reconnectTimeout = setTimeout(async () => {
19234
20089
  var _a, _b, _c;
19235
20090
 
19236
- if (this._isClosed) {
20091
+ if (_this._isClosed) {
19237
20092
  return;
19238
20093
  } // guard for attempting reconnection multiple times while one attempt is still not finished
19239
20094
 
19240
20095
 
19241
- if (this.attemptingReconnect) {
20096
+ if (_this.attemptingReconnect) {
19242
20097
  return;
19243
20098
  }
19244
20099
 
19245
20100
  if (isFireFox() || // TODO remove once clientConfiguration handles firefox case server side
19246
- ((_a = this.clientConfiguration) === null || _a === void 0 ? void 0 : _a.resumeConnection) === ClientConfigSetting.DISABLED || // signaling state could change to closed due to hardware sleep
20101
+ ((_a = _this.clientConfiguration) === null || _a === void 0 ? void 0 : _a.resumeConnection) === ClientConfigSetting.DISABLED || // signaling state could change to closed due to hardware sleep
19247
20102
  // those connections cannot be resumed
19248
- ((_c = (_b = this.primaryPC) === null || _b === void 0 ? void 0 : _b.signalingState) !== null && _c !== void 0 ? _c : 'closed') === 'closed') {
19249
- this.fullReconnectOnNext = true;
20103
+ ((_c = (_b = _this.primaryPC) === null || _b === void 0 ? void 0 : _b.signalingState) !== null && _c !== void 0 ? _c : 'closed') === 'closed') {
20104
+ _this.fullReconnectOnNext = true;
19250
20105
  }
19251
20106
 
19252
20107
  try {
19253
- this.attemptingReconnect = true;
20108
+ _this.attemptingReconnect = true;
19254
20109
 
19255
- if (this.fullReconnectOnNext) {
19256
- await this.restartConnection();
20110
+ if (_this.fullReconnectOnNext) {
20111
+ await _this.restartConnection(signalEvents);
19257
20112
  } else {
19258
- await this.resumeConnection();
20113
+ await _this.resumeConnection(signalEvents);
19259
20114
  }
19260
20115
 
19261
- this.reconnectAttempts = 0;
19262
- this.fullReconnectOnNext = false;
20116
+ _this.reconnectAttempts = 0;
20117
+ _this.fullReconnectOnNext = false;
20118
+
20119
+ if (_this.reconnectTimeout) {
20120
+ clearTimeout(_this.reconnectTimeout);
20121
+ }
19263
20122
  } catch (e) {
19264
- this.reconnectAttempts += 1;
20123
+ _this.reconnectAttempts += 1;
19265
20124
  let reconnectRequired = false;
19266
20125
  let recoverable = true;
20126
+ let requireSignalEvents = false;
19267
20127
 
19268
20128
  if (e instanceof UnexpectedConnectionState) {
19269
20129
  livekitLogger.debug('received unrecoverable error', {
@@ -19274,35 +20134,29 @@ class RTCEngine extends events.exports.EventEmitter {
19274
20134
  } else if (!(e instanceof SignalReconnectError)) {
19275
20135
  // cannot resume
19276
20136
  reconnectRequired = true;
19277
- } // when we flip from resume to reconnect, we need to reset reconnectAttempts
19278
- // this is needed to fire the right reconnecting events
20137
+ } // when we flip from resume to reconnect
20138
+ // we need to fire the right reconnecting events
19279
20139
 
19280
20140
 
19281
- if (reconnectRequired && !this.fullReconnectOnNext) {
19282
- this.fullReconnectOnNext = true;
19283
- this.reconnectAttempts = 0;
19284
- }
19285
-
19286
- const duration = Date.now() - this.reconnectStart;
19287
-
19288
- if (this.reconnectAttempts >= maxReconnectRetries || duration > maxReconnectDuration) {
19289
- recoverable = false;
20141
+ if (reconnectRequired && !_this.fullReconnectOnNext) {
20142
+ _this.fullReconnectOnNext = true;
20143
+ requireSignalEvents = true;
19290
20144
  }
19291
20145
 
19292
20146
  if (recoverable) {
19293
- this.handleDisconnect('reconnect');
20147
+ _this.handleDisconnect('reconnect', requireSignalEvents);
19294
20148
  } else {
19295
- livekitLogger.info("could not recover connection after ".concat(maxReconnectRetries, " attempts, ").concat(duration, "ms. giving up"));
19296
- this.emit(EngineEvent.Disconnected);
19297
- this.close();
20149
+ disconnect(Date.now() - _this.reconnectStart);
19298
20150
  }
19299
20151
  } finally {
19300
- this.attemptingReconnect = false;
20152
+ _this.attemptingReconnect = false;
19301
20153
  }
19302
20154
  }, delay);
19303
20155
  };
19304
20156
 
19305
20157
  this.client = new SignalClient();
20158
+ this.client.signalLatency = this.options.expSignalLatency;
20159
+ this.reconnectPolicy = (_a = this.options.reconnectPolicy) !== null && _a !== void 0 ? _a : new DefaultReconnectPolicy();
19306
20160
  }
19307
20161
 
19308
20162
  get isClosed() {
@@ -19366,8 +20220,16 @@ class RTCEngine extends events.exports.EventEmitter {
19366
20220
  throw new TrackInvalidError('a track with the same ID has already been published');
19367
20221
  }
19368
20222
 
19369
- return new Promise(resolve => {
19370
- this.pendingTrackResolvers[req.cid] = resolve;
20223
+ return new Promise((resolve, reject) => {
20224
+ const publicationTimeout = setTimeout(() => {
20225
+ reject(new ConnectionError('publication of local track timed out, no response from server'));
20226
+ }, 15000);
20227
+
20228
+ this.pendingTrackResolvers[req.cid] = info => {
20229
+ clearTimeout(publicationTimeout);
20230
+ resolve(info);
20231
+ };
20232
+
19371
20233
  this.client.sendAddTrack(req);
19372
20234
  });
19373
20235
  }
@@ -19541,8 +20403,7 @@ class RTCEngine extends events.exports.EventEmitter {
19541
20403
  });
19542
20404
  await this.subscriber.setRemoteDescription(sd); // answer the offer
19543
20405
 
19544
- const answer = await this.subscriber.pc.createAnswer();
19545
- await this.subscriber.pc.setLocalDescription(answer);
20406
+ const answer = await this.subscriber.createAndSetAnswer();
19546
20407
  this.client.sendAnswer(answer);
19547
20408
  };
19548
20409
 
@@ -19615,7 +20476,22 @@ class RTCEngine extends events.exports.EventEmitter {
19615
20476
  this.reliableDC.onerror = this.handleDataError;
19616
20477
  }
19617
20478
 
20479
+ getNextRetryDelay(context) {
20480
+ try {
20481
+ return this.reconnectPolicy.nextRetryDelayInMs(context);
20482
+ } catch (e) {
20483
+ livekitLogger.warn('encountered error in reconnect policy', {
20484
+ error: e
20485
+ });
20486
+ } // error in user code with provided reconnect policy, stop reconnecting
20487
+
20488
+
20489
+ return null;
20490
+ }
20491
+
19618
20492
  async restartConnection() {
20493
+ let emitRestarting = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
20494
+
19619
20495
  var _a, _b;
19620
20496
 
19621
20497
  if (!this.url || !this.token) {
@@ -19625,7 +20501,7 @@ class RTCEngine extends events.exports.EventEmitter {
19625
20501
 
19626
20502
  livekitLogger.info("reconnecting, attempt: ".concat(this.reconnectAttempts));
19627
20503
 
19628
- if (this.reconnectAttempts === 0) {
20504
+ if (emitRestarting || this.reconnectAttempts === 0) {
19629
20505
  this.emit(EngineEvent.Restarting);
19630
20506
  }
19631
20507
 
@@ -19654,6 +20530,8 @@ class RTCEngine extends events.exports.EventEmitter {
19654
20530
  }
19655
20531
 
19656
20532
  async resumeConnection() {
20533
+ let emitResuming = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
20534
+
19657
20535
  var _a;
19658
20536
 
19659
20537
  if (!this.url || !this.token) {
@@ -19668,14 +20546,20 @@ class RTCEngine extends events.exports.EventEmitter {
19668
20546
 
19669
20547
  livekitLogger.info("resuming signal connection, attempt ".concat(this.reconnectAttempts));
19670
20548
 
19671
- if (this.reconnectAttempts === 0) {
20549
+ if (emitResuming || this.reconnectAttempts === 0) {
19672
20550
  this.emit(EngineEvent.Resuming);
19673
20551
  }
19674
20552
 
19675
20553
  try {
19676
20554
  await this.client.reconnect(this.url, this.token);
19677
20555
  } catch (e) {
19678
- throw new SignalReconnectError();
20556
+ let message = '';
20557
+
20558
+ if (e instanceof Error) {
20559
+ message = e.message;
20560
+ }
20561
+
20562
+ throw new SignalReconnectError(message);
19679
20563
  }
19680
20564
 
19681
20565
  this.emit(EngineEvent.SignalResumed);
@@ -19983,23 +20867,7 @@ class Room extends events.exports.EventEmitter {
19983
20867
  this.localParticipant.identity = pi.identity;
19984
20868
  this.localParticipant.updateInfo(pi); // forward metadata changed for the local participant
19985
20869
 
19986
- this.localParticipant.on(ParticipantEvent.ParticipantMetadataChanged, metadata => {
19987
- this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
19988
- }).on(ParticipantEvent.TrackMuted, pub => {
19989
- this.emit(RoomEvent.TrackMuted, pub, this.localParticipant);
19990
- }).on(ParticipantEvent.TrackUnmuted, pub => {
19991
- this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
19992
- }).on(ParticipantEvent.LocalTrackPublished, pub => {
19993
- this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
19994
- }).on(ParticipantEvent.LocalTrackUnpublished, pub => {
19995
- this.emit(RoomEvent.LocalTrackUnpublished, pub, this.localParticipant);
19996
- }).on(ParticipantEvent.ConnectionQualityChanged, quality => {
19997
- this.emit(RoomEvent.ConnectionQualityChanged, quality, this.localParticipant);
19998
- }).on(ParticipantEvent.MediaDevicesError, e => {
19999
- this.emit(RoomEvent.MediaDevicesError, e);
20000
- }).on(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions => {
20001
- this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, this.localParticipant);
20002
- }); // populate remote participants, these should not trigger new events
20870
+ this.localParticipant.on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged).on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted).on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted).on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished).on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished).on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged).on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError).on(ParticipantEvent.ParticipantPermissionsChanged, this.onLocalParticipantPermissionsChanged); // populate remote participants, these should not trigger new events
20003
20871
 
20004
20872
  joinResponse.otherParticipants.forEach(info => {
20005
20873
  if (info.sid !== this.localParticipant.sid && info.identity !== this.localParticipant.identity) {
@@ -20342,6 +21210,38 @@ class Room extends events.exports.EventEmitter {
20342
21210
  });
20343
21211
  };
20344
21212
 
21213
+ this.onLocalParticipantMetadataChanged = metadata => {
21214
+ this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
21215
+ };
21216
+
21217
+ this.onLocalTrackMuted = pub => {
21218
+ this.emit(RoomEvent.TrackMuted, pub, this.localParticipant);
21219
+ };
21220
+
21221
+ this.onLocalTrackUnmuted = pub => {
21222
+ this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
21223
+ };
21224
+
21225
+ this.onLocalTrackPublished = pub => {
21226
+ this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
21227
+ };
21228
+
21229
+ this.onLocalTrackUnpublished = pub => {
21230
+ this.emit(RoomEvent.LocalTrackUnpublished, pub, this.localParticipant);
21231
+ };
21232
+
21233
+ this.onLocalConnectionQualityChanged = quality => {
21234
+ this.emit(RoomEvent.ConnectionQualityChanged, quality, this.localParticipant);
21235
+ };
21236
+
21237
+ this.onMediaDevicesError = e => {
21238
+ this.emit(RoomEvent.MediaDevicesError, e);
21239
+ };
21240
+
21241
+ this.onLocalParticipantPermissionsChanged = prevPermissions => {
21242
+ this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, this.localParticipant);
21243
+ };
21244
+
20345
21245
  this.participants = new Map();
20346
21246
  this.identityToSid = new Map();
20347
21247
  this.options = options || {};
@@ -20357,8 +21257,7 @@ class Room extends events.exports.EventEmitter {
20357
21257
  return;
20358
21258
  }
20359
21259
 
20360
- this.engine = new RTCEngine();
20361
- this.engine.client.signalLatency = this.options.expSignalLatency;
21260
+ this.engine = new RTCEngine(this.options);
20362
21261
  this.engine.client.onParticipantUpdate = this.handleParticipantUpdates;
20363
21262
  this.engine.client.onRoomUpdate = this.handleRoomUpdate;
20364
21263
  this.engine.client.onSpeakersChanged = this.handleSpeakersChanged;
@@ -20639,6 +21538,7 @@ class Room extends events.exports.EventEmitter {
20639
21538
  p.unpublishTrack(pub.trackSid);
20640
21539
  });
20641
21540
  });
21541
+ this.localParticipant.off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged).off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted).off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted).off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished).off(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished).off(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged).off(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError).off(ParticipantEvent.ParticipantPermissionsChanged, this.onLocalParticipantPermissionsChanged);
20642
21542
  this.localParticipant.tracks.forEach(pub => {
20643
21543
  var _a, _b;
20644
21544
 
@@ -20996,5 +21896,5 @@ async function createLocalScreenTracks(options) {
20996
21896
  return localTracks;
20997
21897
  }
20998
21898
 
20999
- export { AudioPresets, ConnectionError, ConnectionQuality, ConnectionState, DataPacket_Kind, DisconnectReason, EngineEvent, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, MediaDeviceFailure, Participant, ParticipantEvent, PublishDataError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, RoomState, ScreenSharePresets, Track, TrackEvent, TrackInvalidError, TrackPublication, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, detachTrack, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, protocolVersion, setLogExtension, setLogLevel, version };
21899
+ export { AudioPresets, ConnectionError, ConnectionQuality, ConnectionState, DataPacket_Kind, DefaultReconnectPolicy, DisconnectReason, EngineEvent, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, MediaDeviceFailure, Participant, ParticipantEvent, PublishDataError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, RoomState, ScreenSharePresets, Track, TrackEvent, TrackInvalidError, TrackPublication, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, detachTrack, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, protocolVersion, setLogExtension, setLogLevel, version };
21000
21900
  //# sourceMappingURL=livekit-client.esm.mjs.map