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.
Files changed (34) hide show
  1. package/dist/livekit-client.esm.mjs +972 -109
  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 +1 -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.map +1 -1
  21. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  22. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  23. package/package.json +3 -1
  24. package/src/index.ts +2 -0
  25. package/src/options.ts +6 -0
  26. package/src/room/DefaultReconnectPolicy.ts +35 -0
  27. package/src/room/PCTransport.ts +91 -17
  28. package/src/room/RTCEngine.ts +79 -31
  29. package/src/room/ReconnectPolicy.ts +25 -0
  30. package/src/room/Room.ts +55 -30
  31. package/src/room/events.ts +2 -2
  32. package/src/room/participant/LocalParticipant.ts +30 -16
  33. package/src/room/participant/RemoteParticipant.ts +13 -0
  34. 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.2";
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 => {
@@ -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 => {
@@ -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
  }
@@ -14849,7 +14873,6 @@ class LocalParticipant extends Participant {
14849
14873
  }
14850
14874
 
14851
14875
  track = publication.track;
14852
- track.sender = undefined;
14853
14876
  track.off(TrackEvent.Muted, this.onTrackMuted);
14854
14877
  track.off(TrackEvent.Unmuted, this.onTrackUnmuted);
14855
14878
  track.off(TrackEvent.Ended, this.handleTrackEnded);
@@ -14864,29 +14887,19 @@ class LocalParticipant extends Participant {
14864
14887
  track.stop();
14865
14888
  }
14866
14889
 
14867
- const {
14868
- mediaStreamTrack
14869
- } = track;
14870
-
14871
- if (this.engine.publisher && this.engine.publisher.pc.connectionState !== 'closed') {
14872
- const senders = this.engine.publisher.pc.getSenders();
14873
- senders.forEach(sender => {
14874
- var _a;
14875
-
14876
- if (sender.track === mediaStreamTrack) {
14877
- try {
14878
- (_a = this.engine.publisher) === null || _a === void 0 ? void 0 : _a.pc.removeTrack(sender);
14879
- this.engine.negotiate();
14880
- } catch (e) {
14881
- livekitLogger.warn('failed to remove track', {
14882
- error: e,
14883
- method: 'unpublishTrack'
14884
- });
14885
- }
14886
- }
14887
- });
14888
- } // 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
+ }
14889
14901
 
14902
+ track.sender = undefined; // remove from our maps
14890
14903
 
14891
14904
  this.tracks.delete(publication.trackSid);
14892
14905
 
@@ -19050,6 +19063,710 @@ function createConnectionParams(token, info, opts) {
19050
19063
  return "?".concat(params.toString());
19051
19064
  }
19052
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
+
19053
19770
  /** @internal */
19054
19771
 
19055
19772
  class PCTransport {
@@ -19092,6 +19809,8 @@ class PCTransport {
19092
19809
  }
19093
19810
 
19094
19811
  async createAndSendOffer(options) {
19812
+ var _a;
19813
+
19095
19814
  if (this.onOffer === undefined) {
19096
19815
  return;
19097
19816
  }
@@ -19121,30 +19840,72 @@ class PCTransport {
19121
19840
 
19122
19841
 
19123
19842
  livekitLogger.debug('starting to negotiate');
19124
- const offer = await this.pc.createOffer(options); // mung sdp for codec bitrate setting that can't apply by sendEncoding
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
+ }
19125
19854
 
19126
- this.trackBitrates.forEach(trackbr => {
19127
- var _a;
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
+ }
19128
19861
 
19129
- let sdp = (_a = offer.sdp) !== null && _a !== void 0 ? _a : '';
19130
- const sidIndex = sdp.search(new RegExp("msid.* ".concat(trackbr.sid)));
19862
+ return false;
19863
+ }); // add x-google-max-bitrate to fmtp line if not exist
19131
19864
 
19132
- if (sidIndex < 0) {
19133
- return;
19134
- }
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
+ }
19135
19871
 
19136
- const mlineStart = sdp.substring(0, sidIndex).lastIndexOf('m=');
19137
- const mlineEnd = sdp.indexOf('m=', sidIndex);
19138
- const mediaSection = sdp.substring(mlineStart, mlineEnd);
19139
- const mungedMediaSection = mediaSection.replace(new RegExp("a=rtpmap:(\\d+) ".concat(trackbr.codec, "/\\d+"), 'i'), '$'.concat("&\r\na=fmtp:$1 x-google-max-bitrate=".concat(trackbr.maxbr)));
19140
- sdp = sdp.substring(0, mlineStart) + mungedMediaSection + sdp.substring(mlineEnd);
19141
- offer.sdp = sdp;
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
+ }
19142
19887
  });
19888
+ offer.sdp = write(sdpParsed);
19143
19889
  this.trackBitrates = [];
19144
19890
  await this.pc.setLocalDescription(offer);
19145
19891
  this.onOffer(offer);
19146
19892
  }
19147
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
+
19148
19909
  setTrackCodecBitrate(sid, codec, maxbr) {
19149
19910
  this.trackBitrates.push({
19150
19911
  sid,
@@ -19159,11 +19920,35 @@ class PCTransport {
19159
19920
 
19160
19921
  }
19161
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
+
19162
19949
  const lossyDataChannel = '_lossy';
19163
19950
  const reliableDataChannel = '_reliable';
19164
- const maxReconnectRetries = 10;
19165
19951
  const minReconnectWait = 2 * 1000;
19166
- const maxReconnectDuration = 60 * 1000;
19167
19952
  const maxICEConnectTimeout = 15 * 1000;
19168
19953
  var PCState;
19169
19954
 
@@ -19178,8 +19963,14 @@ var PCState;
19178
19963
 
19179
19964
 
19180
19965
  class RTCEngine extends events.exports.EventEmitter {
19181
- constructor() {
19966
+ constructor(options) {
19967
+ var _this;
19968
+
19969
+ var _a;
19970
+
19182
19971
  super();
19972
+ _this = this;
19973
+ this.options = options;
19183
19974
  this.rtcConfig = {};
19184
19975
  this.subscriberPrimary = false;
19185
19976
  this.pcState = PCState.New;
@@ -19254,53 +20045,85 @@ class RTCEngine extends events.exports.EventEmitter {
19254
20045
  // after a number of retries, we'll close and give up permanently
19255
20046
 
19256
20047
 
19257
- this.handleDisconnect = connection => {
19258
- 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) {
19259
20052
  return;
19260
20053
  }
19261
20054
 
19262
20055
  livekitLogger.debug("".concat(connection, " disconnected"));
19263
20056
 
19264
- if (this.reconnectAttempts === 0) {
20057
+ if (_this.reconnectAttempts === 0) {
19265
20058
  // only reset start time on the first try
19266
- this.reconnectStart = Date.now();
20059
+ _this.reconnectStart = Date.now();
20060
+ }
20061
+
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;
19267
20080
  }
19268
20081
 
19269
- const delay = this.reconnectAttempts * this.reconnectAttempts * 300;
19270
- setTimeout(async () => {
20082
+ livekitLogger.debug("reconnecting in ".concat(delay, "ms"));
20083
+
20084
+ if (_this.reconnectTimeout) {
20085
+ clearTimeout(_this.reconnectTimeout);
20086
+ }
20087
+
20088
+ _this.reconnectTimeout = setTimeout(async () => {
19271
20089
  var _a, _b, _c;
19272
20090
 
19273
- if (this._isClosed) {
20091
+ if (_this._isClosed) {
19274
20092
  return;
19275
20093
  } // guard for attempting reconnection multiple times while one attempt is still not finished
19276
20094
 
19277
20095
 
19278
- if (this.attemptingReconnect) {
20096
+ if (_this.attemptingReconnect) {
19279
20097
  return;
19280
20098
  }
19281
20099
 
19282
20100
  if (isFireFox() || // TODO remove once clientConfiguration handles firefox case server side
19283
- ((_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
19284
20102
  // those connections cannot be resumed
19285
- ((_c = (_b = this.primaryPC) === null || _b === void 0 ? void 0 : _b.signalingState) !== null && _c !== void 0 ? _c : 'closed') === 'closed') {
19286
- 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;
19287
20105
  }
19288
20106
 
19289
20107
  try {
19290
- this.attemptingReconnect = true;
20108
+ _this.attemptingReconnect = true;
19291
20109
 
19292
- if (this.fullReconnectOnNext) {
19293
- await this.restartConnection();
20110
+ if (_this.fullReconnectOnNext) {
20111
+ await _this.restartConnection(signalEvents);
19294
20112
  } else {
19295
- await this.resumeConnection();
20113
+ await _this.resumeConnection(signalEvents);
19296
20114
  }
19297
20115
 
19298
- this.reconnectAttempts = 0;
19299
- this.fullReconnectOnNext = false;
20116
+ _this.reconnectAttempts = 0;
20117
+ _this.fullReconnectOnNext = false;
20118
+
20119
+ if (_this.reconnectTimeout) {
20120
+ clearTimeout(_this.reconnectTimeout);
20121
+ }
19300
20122
  } catch (e) {
19301
- this.reconnectAttempts += 1;
20123
+ _this.reconnectAttempts += 1;
19302
20124
  let reconnectRequired = false;
19303
20125
  let recoverable = true;
20126
+ let requireSignalEvents = false;
19304
20127
 
19305
20128
  if (e instanceof UnexpectedConnectionState) {
19306
20129
  livekitLogger.debug('received unrecoverable error', {
@@ -19311,35 +20134,29 @@ class RTCEngine extends events.exports.EventEmitter {
19311
20134
  } else if (!(e instanceof SignalReconnectError)) {
19312
20135
  // cannot resume
19313
20136
  reconnectRequired = true;
19314
- } // when we flip from resume to reconnect, we need to reset reconnectAttempts
19315
- // this is needed to fire the right reconnecting events
19316
-
19317
-
19318
- if (reconnectRequired && !this.fullReconnectOnNext) {
19319
- this.fullReconnectOnNext = true;
19320
- this.reconnectAttempts = 0;
19321
- }
20137
+ } // when we flip from resume to reconnect
20138
+ // we need to fire the right reconnecting events
19322
20139
 
19323
- const duration = Date.now() - this.reconnectStart;
19324
20140
 
19325
- if (this.reconnectAttempts >= maxReconnectRetries || duration > maxReconnectDuration) {
19326
- recoverable = false;
20141
+ if (reconnectRequired && !_this.fullReconnectOnNext) {
20142
+ _this.fullReconnectOnNext = true;
20143
+ requireSignalEvents = true;
19327
20144
  }
19328
20145
 
19329
20146
  if (recoverable) {
19330
- this.handleDisconnect('reconnect');
20147
+ _this.handleDisconnect('reconnect', requireSignalEvents);
19331
20148
  } else {
19332
- livekitLogger.info("could not recover connection after ".concat(maxReconnectRetries, " attempts, ").concat(duration, "ms. giving up"));
19333
- this.emit(EngineEvent.Disconnected);
19334
- this.close();
20149
+ disconnect(Date.now() - _this.reconnectStart);
19335
20150
  }
19336
20151
  } finally {
19337
- this.attemptingReconnect = false;
20152
+ _this.attemptingReconnect = false;
19338
20153
  }
19339
20154
  }, delay);
19340
20155
  };
19341
20156
 
19342
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();
19343
20160
  }
19344
20161
 
19345
20162
  get isClosed() {
@@ -19403,8 +20220,16 @@ class RTCEngine extends events.exports.EventEmitter {
19403
20220
  throw new TrackInvalidError('a track with the same ID has already been published');
19404
20221
  }
19405
20222
 
19406
- return new Promise(resolve => {
19407
- 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
+
19408
20233
  this.client.sendAddTrack(req);
19409
20234
  });
19410
20235
  }
@@ -19578,8 +20403,7 @@ class RTCEngine extends events.exports.EventEmitter {
19578
20403
  });
19579
20404
  await this.subscriber.setRemoteDescription(sd); // answer the offer
19580
20405
 
19581
- const answer = await this.subscriber.pc.createAnswer();
19582
- await this.subscriber.pc.setLocalDescription(answer);
20406
+ const answer = await this.subscriber.createAndSetAnswer();
19583
20407
  this.client.sendAnswer(answer);
19584
20408
  };
19585
20409
 
@@ -19652,7 +20476,22 @@ class RTCEngine extends events.exports.EventEmitter {
19652
20476
  this.reliableDC.onerror = this.handleDataError;
19653
20477
  }
19654
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
+
19655
20492
  async restartConnection() {
20493
+ let emitRestarting = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
20494
+
19656
20495
  var _a, _b;
19657
20496
 
19658
20497
  if (!this.url || !this.token) {
@@ -19662,7 +20501,7 @@ class RTCEngine extends events.exports.EventEmitter {
19662
20501
 
19663
20502
  livekitLogger.info("reconnecting, attempt: ".concat(this.reconnectAttempts));
19664
20503
 
19665
- if (this.reconnectAttempts === 0) {
20504
+ if (emitRestarting || this.reconnectAttempts === 0) {
19666
20505
  this.emit(EngineEvent.Restarting);
19667
20506
  }
19668
20507
 
@@ -19691,6 +20530,8 @@ class RTCEngine extends events.exports.EventEmitter {
19691
20530
  }
19692
20531
 
19693
20532
  async resumeConnection() {
20533
+ let emitResuming = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
20534
+
19694
20535
  var _a;
19695
20536
 
19696
20537
  if (!this.url || !this.token) {
@@ -19705,14 +20546,20 @@ class RTCEngine extends events.exports.EventEmitter {
19705
20546
 
19706
20547
  livekitLogger.info("resuming signal connection, attempt ".concat(this.reconnectAttempts));
19707
20548
 
19708
- if (this.reconnectAttempts === 0) {
20549
+ if (emitResuming || this.reconnectAttempts === 0) {
19709
20550
  this.emit(EngineEvent.Resuming);
19710
20551
  }
19711
20552
 
19712
20553
  try {
19713
20554
  await this.client.reconnect(this.url, this.token);
19714
20555
  } catch (e) {
19715
- throw new SignalReconnectError();
20556
+ let message = '';
20557
+
20558
+ if (e instanceof Error) {
20559
+ message = e.message;
20560
+ }
20561
+
20562
+ throw new SignalReconnectError(message);
19716
20563
  }
19717
20564
 
19718
20565
  this.emit(EngineEvent.SignalResumed);
@@ -20020,23 +20867,7 @@ class Room extends events.exports.EventEmitter {
20020
20867
  this.localParticipant.identity = pi.identity;
20021
20868
  this.localParticipant.updateInfo(pi); // forward metadata changed for the local participant
20022
20869
 
20023
- this.localParticipant.on(ParticipantEvent.ParticipantMetadataChanged, metadata => {
20024
- this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
20025
- }).on(ParticipantEvent.TrackMuted, pub => {
20026
- this.emit(RoomEvent.TrackMuted, pub, this.localParticipant);
20027
- }).on(ParticipantEvent.TrackUnmuted, pub => {
20028
- this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
20029
- }).on(ParticipantEvent.LocalTrackPublished, pub => {
20030
- this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
20031
- }).on(ParticipantEvent.LocalTrackUnpublished, pub => {
20032
- this.emit(RoomEvent.LocalTrackUnpublished, pub, this.localParticipant);
20033
- }).on(ParticipantEvent.ConnectionQualityChanged, quality => {
20034
- this.emit(RoomEvent.ConnectionQualityChanged, quality, this.localParticipant);
20035
- }).on(ParticipantEvent.MediaDevicesError, e => {
20036
- this.emit(RoomEvent.MediaDevicesError, e);
20037
- }).on(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions => {
20038
- this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, this.localParticipant);
20039
- }); // 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
20040
20871
 
20041
20872
  joinResponse.otherParticipants.forEach(info => {
20042
20873
  if (info.sid !== this.localParticipant.sid && info.identity !== this.localParticipant.identity) {
@@ -20379,6 +21210,38 @@ class Room extends events.exports.EventEmitter {
20379
21210
  });
20380
21211
  };
20381
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
+
20382
21245
  this.participants = new Map();
20383
21246
  this.identityToSid = new Map();
20384
21247
  this.options = options || {};
@@ -20394,8 +21257,7 @@ class Room extends events.exports.EventEmitter {
20394
21257
  return;
20395
21258
  }
20396
21259
 
20397
- this.engine = new RTCEngine();
20398
- this.engine.client.signalLatency = this.options.expSignalLatency;
21260
+ this.engine = new RTCEngine(this.options);
20399
21261
  this.engine.client.onParticipantUpdate = this.handleParticipantUpdates;
20400
21262
  this.engine.client.onRoomUpdate = this.handleRoomUpdate;
20401
21263
  this.engine.client.onSpeakersChanged = this.handleSpeakersChanged;
@@ -20676,6 +21538,7 @@ class Room extends events.exports.EventEmitter {
20676
21538
  p.unpublishTrack(pub.trackSid);
20677
21539
  });
20678
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);
20679
21542
  this.localParticipant.tracks.forEach(pub => {
20680
21543
  var _a, _b;
20681
21544
 
@@ -21033,5 +21896,5 @@ async function createLocalScreenTracks(options) {
21033
21896
  return localTracks;
21034
21897
  }
21035
21898
 
21036
- 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 };
21037
21900
  //# sourceMappingURL=livekit-client.esm.mjs.map