livekit-client 1.2.1 → 1.2.4

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 +974 -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 +14 -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.1";
10268
+ var version$1 = "1.2.4";
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 => {
@@ -14043,6 +14041,17 @@ class RemoteParticipant extends Participant {
14043
14041
  publication = new RemoteTrackPublication(kind, ti.sid, ti.name);
14044
14042
  publication.updateInfo(ti);
14045
14043
  newTracks.set(ti.sid, publication);
14044
+ const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => publishedTrack.source === (publication === null || publication === void 0 ? void 0 : publication.source));
14045
+
14046
+ if (existingTrackOfSource) {
14047
+ livekitLogger.warn("received a second track publication for ".concat(this.identity, " with the same source: ").concat(publication.source), {
14048
+ oldTrack: existingTrackOfSource,
14049
+ newTrack: publication,
14050
+ participant: this,
14051
+ participantInfo: info
14052
+ });
14053
+ }
14054
+
14046
14055
  this.addTrackPublication(publication);
14047
14056
  } else {
14048
14057
  publication.updateInfo(ti);
@@ -14618,6 +14627,23 @@ class LocalParticipant extends Participant {
14618
14627
  track.source = opts.source;
14619
14628
  }
14620
14629
 
14630
+ const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => track instanceof LocalTrack && publishedTrack.source === track.source);
14631
+
14632
+ if (existingTrackOfSource) {
14633
+ try {
14634
+ // throw an Error in order to capture the stack trace
14635
+ throw Error("publishing a second track with the same source: ".concat(track.source));
14636
+ } catch (e) {
14637
+ if (e instanceof Error) {
14638
+ livekitLogger.warn(e.message, {
14639
+ oldTrack: existingTrackOfSource,
14640
+ newTrack: track,
14641
+ trace: e.stack
14642
+ });
14643
+ }
14644
+ }
14645
+ }
14646
+
14621
14647
  if (opts.stopMicTrackOnMute && track instanceof LocalAudioTrack) {
14622
14648
  track.stopOnMute = true;
14623
14649
  }
@@ -14849,7 +14875,6 @@ class LocalParticipant extends Participant {
14849
14875
  }
14850
14876
 
14851
14877
  track = publication.track;
14852
- track.sender = undefined;
14853
14878
  track.off(TrackEvent.Muted, this.onTrackMuted);
14854
14879
  track.off(TrackEvent.Unmuted, this.onTrackUnmuted);
14855
14880
  track.off(TrackEvent.Ended, this.handleTrackEnded);
@@ -14864,29 +14889,19 @@ class LocalParticipant extends Participant {
14864
14889
  track.stop();
14865
14890
  }
14866
14891
 
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
14892
+ if (this.engine.publisher && this.engine.publisher.pc.connectionState !== 'closed' && track.sender) {
14893
+ try {
14894
+ this.engine.publisher.pc.removeTrack(track.sender);
14895
+ this.engine.negotiate();
14896
+ } catch (e) {
14897
+ livekitLogger.warn('failed to remove track', {
14898
+ error: e,
14899
+ method: 'unpublishTrack'
14900
+ });
14901
+ }
14902
+ }
14889
14903
 
14904
+ track.sender = undefined; // remove from our maps
14890
14905
 
14891
14906
  this.tracks.delete(publication.trackSid);
14892
14907
 
@@ -19050,6 +19065,710 @@ function createConnectionParams(token, info, opts) {
19050
19065
  return "?".concat(params.toString());
19051
19066
  }
19052
19067
 
19068
+ const maxRetryDelay = 7000;
19069
+ const DEFAULT_RETRY_DELAYS_IN_MS = [0, 300, 2 * 2 * 300, 3 * 3 * 300, 4 * 4 * 300, maxRetryDelay, maxRetryDelay, maxRetryDelay, maxRetryDelay, maxRetryDelay];
19070
+
19071
+ class DefaultReconnectPolicy {
19072
+ constructor(retryDelays) {
19073
+ this._retryDelays = retryDelays !== undefined ? [...retryDelays] : DEFAULT_RETRY_DELAYS_IN_MS;
19074
+ }
19075
+
19076
+ nextRetryDelayInMs(context) {
19077
+ if (context.retryCount >= this._retryDelays.length) return null;
19078
+ const retryDelay = this._retryDelays[context.retryCount];
19079
+ if (context.retryCount <= 1) return retryDelay;
19080
+ return retryDelay + Math.random() * 1000;
19081
+ }
19082
+
19083
+ }
19084
+
19085
+ var parser$1 = {};
19086
+
19087
+ var grammar$2 = {exports: {}};
19088
+
19089
+ var grammar$1 = grammar$2.exports = {
19090
+ v: [{
19091
+ name: 'version',
19092
+ reg: /^(\d*)$/
19093
+ }],
19094
+ o: [{
19095
+ // o=- 20518 0 IN IP4 203.0.113.1
19096
+ // NB: sessionId will be a String in most cases because it is huge
19097
+ name: 'origin',
19098
+ reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,
19099
+ names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],
19100
+ format: '%s %s %d %s IP%d %s'
19101
+ }],
19102
+ // default parsing of these only (though some of these feel outdated)
19103
+ s: [{
19104
+ name: 'name'
19105
+ }],
19106
+ i: [{
19107
+ name: 'description'
19108
+ }],
19109
+ u: [{
19110
+ name: 'uri'
19111
+ }],
19112
+ e: [{
19113
+ name: 'email'
19114
+ }],
19115
+ p: [{
19116
+ name: 'phone'
19117
+ }],
19118
+ z: [{
19119
+ name: 'timezones'
19120
+ }],
19121
+ // TODO: this one can actually be parsed properly...
19122
+ r: [{
19123
+ name: 'repeats'
19124
+ }],
19125
+ // TODO: this one can also be parsed properly
19126
+ // k: [{}], // outdated thing ignored
19127
+ t: [{
19128
+ // t=0 0
19129
+ name: 'timing',
19130
+ reg: /^(\d*) (\d*)/,
19131
+ names: ['start', 'stop'],
19132
+ format: '%d %d'
19133
+ }],
19134
+ c: [{
19135
+ // c=IN IP4 10.47.197.26
19136
+ name: 'connection',
19137
+ reg: /^IN IP(\d) (\S*)/,
19138
+ names: ['version', 'ip'],
19139
+ format: 'IN IP%d %s'
19140
+ }],
19141
+ b: [{
19142
+ // b=AS:4000
19143
+ push: 'bandwidth',
19144
+ reg: /^(TIAS|AS|CT|RR|RS):(\d*)/,
19145
+ names: ['type', 'limit'],
19146
+ format: '%s:%s'
19147
+ }],
19148
+ m: [{
19149
+ // m=video 51744 RTP/AVP 126 97 98 34 31
19150
+ // NB: special - pushes to session
19151
+ // TODO: rtp/fmtp should be filtered by the payloads found here?
19152
+ reg: /^(\w*) (\d*) ([\w/]*)(?: (.*))?/,
19153
+ names: ['type', 'port', 'protocol', 'payloads'],
19154
+ format: '%s %d %s %s'
19155
+ }],
19156
+ a: [{
19157
+ // a=rtpmap:110 opus/48000/2
19158
+ push: 'rtp',
19159
+ reg: /^rtpmap:(\d*) ([\w\-.]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/,
19160
+ names: ['payload', 'codec', 'rate', 'encoding'],
19161
+ format: function (o) {
19162
+ return o.encoding ? 'rtpmap:%d %s/%s/%s' : o.rate ? 'rtpmap:%d %s/%s' : 'rtpmap:%d %s';
19163
+ }
19164
+ }, {
19165
+ // a=fmtp:108 profile-level-id=24;object=23;bitrate=64000
19166
+ // a=fmtp:111 minptime=10; useinbandfec=1
19167
+ push: 'fmtp',
19168
+ reg: /^fmtp:(\d*) ([\S| ]*)/,
19169
+ names: ['payload', 'config'],
19170
+ format: 'fmtp:%d %s'
19171
+ }, {
19172
+ // a=control:streamid=0
19173
+ name: 'control',
19174
+ reg: /^control:(.*)/,
19175
+ format: 'control:%s'
19176
+ }, {
19177
+ // a=rtcp:65179 IN IP4 193.84.77.194
19178
+ name: 'rtcp',
19179
+ reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,
19180
+ names: ['port', 'netType', 'ipVer', 'address'],
19181
+ format: function (o) {
19182
+ return o.address != null ? 'rtcp:%d %s IP%d %s' : 'rtcp:%d';
19183
+ }
19184
+ }, {
19185
+ // a=rtcp-fb:98 trr-int 100
19186
+ push: 'rtcpFbTrrInt',
19187
+ reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/,
19188
+ names: ['payload', 'value'],
19189
+ format: 'rtcp-fb:%s trr-int %d'
19190
+ }, {
19191
+ // a=rtcp-fb:98 nack rpsi
19192
+ push: 'rtcpFb',
19193
+ reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,
19194
+ names: ['payload', 'type', 'subtype'],
19195
+ format: function (o) {
19196
+ return o.subtype != null ? 'rtcp-fb:%s %s %s' : 'rtcp-fb:%s %s';
19197
+ }
19198
+ }, {
19199
+ // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
19200
+ // a=extmap:1/recvonly URI-gps-string
19201
+ // a=extmap:3 urn:ietf:params:rtp-hdrext:encrypt urn:ietf:params:rtp-hdrext:smpte-tc 25@600/24
19202
+ push: 'ext',
19203
+ reg: /^extmap:(\d+)(?:\/(\w+))?(?: (urn:ietf:params:rtp-hdrext:encrypt))? (\S*)(?: (\S*))?/,
19204
+ names: ['value', 'direction', 'encrypt-uri', 'uri', 'config'],
19205
+ format: function (o) {
19206
+ return 'extmap:%d' + (o.direction ? '/%s' : '%v') + (o['encrypt-uri'] ? ' %s' : '%v') + ' %s' + (o.config ? ' %s' : '');
19207
+ }
19208
+ }, {
19209
+ // a=extmap-allow-mixed
19210
+ name: 'extmapAllowMixed',
19211
+ reg: /^(extmap-allow-mixed)/
19212
+ }, {
19213
+ // a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
19214
+ push: 'crypto',
19215
+ reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,
19216
+ names: ['id', 'suite', 'config', 'sessionConfig'],
19217
+ format: function (o) {
19218
+ return o.sessionConfig != null ? 'crypto:%d %s %s %s' : 'crypto:%d %s %s';
19219
+ }
19220
+ }, {
19221
+ // a=setup:actpass
19222
+ name: 'setup',
19223
+ reg: /^setup:(\w*)/,
19224
+ format: 'setup:%s'
19225
+ }, {
19226
+ // a=connection:new
19227
+ name: 'connectionType',
19228
+ reg: /^connection:(new|existing)/,
19229
+ format: 'connection:%s'
19230
+ }, {
19231
+ // a=mid:1
19232
+ name: 'mid',
19233
+ reg: /^mid:([^\s]*)/,
19234
+ format: 'mid:%s'
19235
+ }, {
19236
+ // a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a
19237
+ name: 'msid',
19238
+ reg: /^msid:(.*)/,
19239
+ format: 'msid:%s'
19240
+ }, {
19241
+ // a=ptime:20
19242
+ name: 'ptime',
19243
+ reg: /^ptime:(\d*(?:\.\d*)*)/,
19244
+ format: 'ptime:%d'
19245
+ }, {
19246
+ // a=maxptime:60
19247
+ name: 'maxptime',
19248
+ reg: /^maxptime:(\d*(?:\.\d*)*)/,
19249
+ format: 'maxptime:%d'
19250
+ }, {
19251
+ // a=sendrecv
19252
+ name: 'direction',
19253
+ reg: /^(sendrecv|recvonly|sendonly|inactive)/
19254
+ }, {
19255
+ // a=ice-lite
19256
+ name: 'icelite',
19257
+ reg: /^(ice-lite)/
19258
+ }, {
19259
+ // a=ice-ufrag:F7gI
19260
+ name: 'iceUfrag',
19261
+ reg: /^ice-ufrag:(\S*)/,
19262
+ format: 'ice-ufrag:%s'
19263
+ }, {
19264
+ // a=ice-pwd:x9cml/YzichV2+XlhiMu8g
19265
+ name: 'icePwd',
19266
+ reg: /^ice-pwd:(\S*)/,
19267
+ format: 'ice-pwd:%s'
19268
+ }, {
19269
+ // a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33
19270
+ name: 'fingerprint',
19271
+ reg: /^fingerprint:(\S*) (\S*)/,
19272
+ names: ['type', 'hash'],
19273
+ format: 'fingerprint:%s %s'
19274
+ }, {
19275
+ // a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
19276
+ // a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 network-id 3 network-cost 10
19277
+ // 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
19278
+ // a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0 network-id 3 network-cost 10
19279
+ // 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
19280
+ push: 'candidates',
19281
+ reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?(?: network-id (\d*))?(?: network-cost (\d*))?/,
19282
+ names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation', 'network-id', 'network-cost'],
19283
+ format: function (o) {
19284
+ var str = 'candidate:%s %d %s %d %s %d typ %s';
19285
+ str += o.raddr != null ? ' raddr %s rport %d' : '%v%v'; // NB: candidate has three optional chunks, so %void middles one if it's missing
19286
+
19287
+ str += o.tcptype != null ? ' tcptype %s' : '%v';
19288
+
19289
+ if (o.generation != null) {
19290
+ str += ' generation %d';
19291
+ }
19292
+
19293
+ str += o['network-id'] != null ? ' network-id %d' : '%v';
19294
+ str += o['network-cost'] != null ? ' network-cost %d' : '%v';
19295
+ return str;
19296
+ }
19297
+ }, {
19298
+ // a=end-of-candidates (keep after the candidates line for readability)
19299
+ name: 'endOfCandidates',
19300
+ reg: /^(end-of-candidates)/
19301
+ }, {
19302
+ // a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...
19303
+ name: 'remoteCandidates',
19304
+ reg: /^remote-candidates:(.*)/,
19305
+ format: 'remote-candidates:%s'
19306
+ }, {
19307
+ // a=ice-options:google-ice
19308
+ name: 'iceOptions',
19309
+ reg: /^ice-options:(\S*)/,
19310
+ format: 'ice-options:%s'
19311
+ }, {
19312
+ // a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1
19313
+ push: 'ssrcs',
19314
+ reg: /^ssrc:(\d*) ([\w_-]*)(?::(.*))?/,
19315
+ names: ['id', 'attribute', 'value'],
19316
+ format: function (o) {
19317
+ var str = 'ssrc:%d';
19318
+
19319
+ if (o.attribute != null) {
19320
+ str += ' %s';
19321
+
19322
+ if (o.value != null) {
19323
+ str += ':%s';
19324
+ }
19325
+ }
19326
+
19327
+ return str;
19328
+ }
19329
+ }, {
19330
+ // a=ssrc-group:FEC 1 2
19331
+ // a=ssrc-group:FEC-FR 3004364195 1080772241
19332
+ push: 'ssrcGroups',
19333
+ // token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 / %x41-5A / %x5E-7E
19334
+ reg: /^ssrc-group:([\x21\x23\x24\x25\x26\x27\x2A\x2B\x2D\x2E\w]*) (.*)/,
19335
+ names: ['semantics', 'ssrcs'],
19336
+ format: 'ssrc-group:%s %s'
19337
+ }, {
19338
+ // a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV
19339
+ name: 'msidSemantic',
19340
+ reg: /^msid-semantic:\s?(\w*) (\S*)/,
19341
+ names: ['semantic', 'token'],
19342
+ format: 'msid-semantic: %s %s' // space after ':' is not accidental
19343
+
19344
+ }, {
19345
+ // a=group:BUNDLE audio video
19346
+ push: 'groups',
19347
+ reg: /^group:(\w*) (.*)/,
19348
+ names: ['type', 'mids'],
19349
+ format: 'group:%s %s'
19350
+ }, {
19351
+ // a=rtcp-mux
19352
+ name: 'rtcpMux',
19353
+ reg: /^(rtcp-mux)/
19354
+ }, {
19355
+ // a=rtcp-rsize
19356
+ name: 'rtcpRsize',
19357
+ reg: /^(rtcp-rsize)/
19358
+ }, {
19359
+ // a=sctpmap:5000 webrtc-datachannel 1024
19360
+ name: 'sctpmap',
19361
+ reg: /^sctpmap:([\w_/]*) (\S*)(?: (\S*))?/,
19362
+ names: ['sctpmapNumber', 'app', 'maxMessageSize'],
19363
+ format: function (o) {
19364
+ return o.maxMessageSize != null ? 'sctpmap:%s %s %s' : 'sctpmap:%s %s';
19365
+ }
19366
+ }, {
19367
+ // a=x-google-flag:conference
19368
+ name: 'xGoogleFlag',
19369
+ reg: /^x-google-flag:([^\s]*)/,
19370
+ format: 'x-google-flag:%s'
19371
+ }, {
19372
+ // a=rid:1 send max-width=1280;max-height=720;max-fps=30;depend=0
19373
+ push: 'rids',
19374
+ reg: /^rid:([\d\w]+) (\w+)(?: ([\S| ]*))?/,
19375
+ names: ['id', 'direction', 'params'],
19376
+ format: function (o) {
19377
+ return o.params ? 'rid:%s %s %s' : 'rid:%s %s';
19378
+ }
19379
+ }, {
19380
+ // a=imageattr:97 send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] recv [x=330,y=250]
19381
+ // a=imageattr:* send [x=800,y=640] recv *
19382
+ // a=imageattr:100 recv [x=320,y=240]
19383
+ push: 'imageattrs',
19384
+ reg: new RegExp( // a=imageattr:97
19385
+ '^imageattr:(\\d+|\\*)' + // send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320]
19386
+ '[\\s\\t]+(send|recv)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*)' + // recv [x=330,y=250]
19387
+ '(?:[\\s\\t]+(recv|send)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*))?'),
19388
+ names: ['pt', 'dir1', 'attrs1', 'dir2', 'attrs2'],
19389
+ format: function (o) {
19390
+ return 'imageattr:%s %s %s' + (o.dir2 ? ' %s %s' : '');
19391
+ }
19392
+ }, {
19393
+ // a=simulcast:send 1,2,3;~4,~5 recv 6;~7,~8
19394
+ // a=simulcast:recv 1;4,5 send 6;7
19395
+ name: 'simulcast',
19396
+ reg: new RegExp( // a=simulcast:
19397
+ '^simulcast:' + // send 1,2,3;~4,~5
19398
+ '(send|recv) ([a-zA-Z0-9\\-_~;,]+)' + // space + recv 6;~7,~8
19399
+ '(?:\\s?(send|recv) ([a-zA-Z0-9\\-_~;,]+))?' + // end
19400
+ '$'),
19401
+ names: ['dir1', 'list1', 'dir2', 'list2'],
19402
+ format: function (o) {
19403
+ return 'simulcast:%s %s' + (o.dir2 ? ' %s %s' : '');
19404
+ }
19405
+ }, {
19406
+ // old simulcast draft 03 (implemented by Firefox)
19407
+ // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-03
19408
+ // a=simulcast: recv pt=97;98 send pt=97
19409
+ // a=simulcast: send rid=5;6;7 paused=6,7
19410
+ name: 'simulcast_03',
19411
+ reg: /^simulcast:[\s\t]+([\S+\s\t]+)$/,
19412
+ names: ['value'],
19413
+ format: 'simulcast: %s'
19414
+ }, {
19415
+ // a=framerate:25
19416
+ // a=framerate:29.97
19417
+ name: 'framerate',
19418
+ reg: /^framerate:(\d+(?:$|\.\d+))/,
19419
+ format: 'framerate:%s'
19420
+ }, {
19421
+ // RFC4570
19422
+ // a=source-filter: incl IN IP4 239.5.2.31 10.1.15.5
19423
+ name: 'sourceFilter',
19424
+ reg: /^source-filter: *(excl|incl) (\S*) (IP4|IP6|\*) (\S*) (.*)/,
19425
+ names: ['filterMode', 'netType', 'addressTypes', 'destAddress', 'srcList'],
19426
+ format: 'source-filter: %s %s %s %s %s'
19427
+ }, {
19428
+ // a=bundle-only
19429
+ name: 'bundleOnly',
19430
+ reg: /^(bundle-only)/
19431
+ }, {
19432
+ // a=label:1
19433
+ name: 'label',
19434
+ reg: /^label:(.+)/,
19435
+ format: 'label:%s'
19436
+ }, {
19437
+ // RFC version 26 for SCTP over DTLS
19438
+ // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-5
19439
+ name: 'sctpPort',
19440
+ reg: /^sctp-port:(\d+)$/,
19441
+ format: 'sctp-port:%s'
19442
+ }, {
19443
+ // RFC version 26 for SCTP over DTLS
19444
+ // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-6
19445
+ name: 'maxMessageSize',
19446
+ reg: /^max-message-size:(\d+)$/,
19447
+ format: 'max-message-size:%s'
19448
+ }, {
19449
+ // RFC7273
19450
+ // a=ts-refclk:ptp=IEEE1588-2008:39-A7-94-FF-FE-07-CB-D0:37
19451
+ push: 'tsRefClocks',
19452
+ reg: /^ts-refclk:([^\s=]*)(?:=(\S*))?/,
19453
+ names: ['clksrc', 'clksrcExt'],
19454
+ format: function (o) {
19455
+ return 'ts-refclk:%s' + (o.clksrcExt != null ? '=%s' : '');
19456
+ }
19457
+ }, {
19458
+ // RFC7273
19459
+ // a=mediaclk:direct=963214424
19460
+ name: 'mediaClk',
19461
+ reg: /^mediaclk:(?:id=(\S*))? *([^\s=]*)(?:=(\S*))?(?: *rate=(\d+)\/(\d+))?/,
19462
+ names: ['id', 'mediaClockName', 'mediaClockValue', 'rateNumerator', 'rateDenominator'],
19463
+ format: function (o) {
19464
+ var str = 'mediaclk:';
19465
+ str += o.id != null ? 'id=%s %s' : '%v%s';
19466
+ str += o.mediaClockValue != null ? '=%s' : '';
19467
+ str += o.rateNumerator != null ? ' rate=%s' : '';
19468
+ str += o.rateDenominator != null ? '/%s' : '';
19469
+ return str;
19470
+ }
19471
+ }, {
19472
+ // a=keywds:keywords
19473
+ name: 'keywords',
19474
+ reg: /^keywds:(.+)$/,
19475
+ format: 'keywds:%s'
19476
+ }, {
19477
+ // a=content:main
19478
+ name: 'content',
19479
+ reg: /^content:(.+)/,
19480
+ format: 'content:%s'
19481
+ }, // BFCP https://tools.ietf.org/html/rfc4583
19482
+ {
19483
+ // a=floorctrl:c-s
19484
+ name: 'bfcpFloorCtrl',
19485
+ reg: /^floorctrl:(c-only|s-only|c-s)/,
19486
+ format: 'floorctrl:%s'
19487
+ }, {
19488
+ // a=confid:1
19489
+ name: 'bfcpConfId',
19490
+ reg: /^confid:(\d+)/,
19491
+ format: 'confid:%s'
19492
+ }, {
19493
+ // a=userid:1
19494
+ name: 'bfcpUserId',
19495
+ reg: /^userid:(\d+)/,
19496
+ format: 'userid:%s'
19497
+ }, {
19498
+ // a=floorid:1
19499
+ name: 'bfcpFloorId',
19500
+ reg: /^floorid:(.+) (?:m-stream|mstrm):(.+)/,
19501
+ names: ['id', 'mStream'],
19502
+ format: 'floorid:%s mstrm:%s'
19503
+ }, {
19504
+ // any a= that we don't understand is kept verbatim on media.invalid
19505
+ push: 'invalid',
19506
+ names: ['value']
19507
+ }]
19508
+ }; // set sensible defaults to avoid polluting the grammar with boring details
19509
+
19510
+ Object.keys(grammar$1).forEach(function (key) {
19511
+ var objs = grammar$1[key];
19512
+ objs.forEach(function (obj) {
19513
+ if (!obj.reg) {
19514
+ obj.reg = /(.*)/;
19515
+ }
19516
+
19517
+ if (!obj.format) {
19518
+ obj.format = '%s';
19519
+ }
19520
+ });
19521
+ });
19522
+
19523
+ (function (exports) {
19524
+ var toIntIfInt = function (v) {
19525
+ return String(Number(v)) === v ? Number(v) : v;
19526
+ };
19527
+
19528
+ var attachProperties = function (match, location, names, rawName) {
19529
+ if (rawName && !names) {
19530
+ location[rawName] = toIntIfInt(match[1]);
19531
+ } else {
19532
+ for (var i = 0; i < names.length; i += 1) {
19533
+ if (match[i + 1] != null) {
19534
+ location[names[i]] = toIntIfInt(match[i + 1]);
19535
+ }
19536
+ }
19537
+ }
19538
+ };
19539
+
19540
+ var parseReg = function (obj, location, content) {
19541
+ var needsBlank = obj.name && obj.names;
19542
+
19543
+ if (obj.push && !location[obj.push]) {
19544
+ location[obj.push] = [];
19545
+ } else if (needsBlank && !location[obj.name]) {
19546
+ location[obj.name] = {};
19547
+ }
19548
+
19549
+ var keyLocation = obj.push ? {} : // blank object that will be pushed
19550
+ needsBlank ? location[obj.name] : location; // otherwise, named location or root
19551
+
19552
+ attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);
19553
+
19554
+ if (obj.push) {
19555
+ location[obj.push].push(keyLocation);
19556
+ }
19557
+ };
19558
+
19559
+ var grammar = grammar$2.exports;
19560
+ var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);
19561
+
19562
+ exports.parse = function (sdp) {
19563
+ var session = {},
19564
+ media = [],
19565
+ location = session; // points at where properties go under (one of the above)
19566
+ // parse lines we understand
19567
+
19568
+ sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) {
19569
+ var type = l[0];
19570
+ var content = l.slice(2);
19571
+
19572
+ if (type === 'm') {
19573
+ media.push({
19574
+ rtp: [],
19575
+ fmtp: []
19576
+ });
19577
+ location = media[media.length - 1]; // point at latest media line
19578
+ }
19579
+
19580
+ for (var j = 0; j < (grammar[type] || []).length; j += 1) {
19581
+ var obj = grammar[type][j];
19582
+
19583
+ if (obj.reg.test(content)) {
19584
+ return parseReg(obj, location, content);
19585
+ }
19586
+ }
19587
+ });
19588
+ session.media = media; // link it up
19589
+
19590
+ return session;
19591
+ };
19592
+
19593
+ var paramReducer = function (acc, expr) {
19594
+ var s = expr.split(/=(.+)/, 2);
19595
+
19596
+ if (s.length === 2) {
19597
+ acc[s[0]] = toIntIfInt(s[1]);
19598
+ } else if (s.length === 1 && expr.length > 1) {
19599
+ acc[s[0]] = undefined;
19600
+ }
19601
+
19602
+ return acc;
19603
+ };
19604
+
19605
+ exports.parseParams = function (str) {
19606
+ return str.split(/;\s?/).reduce(paramReducer, {});
19607
+ }; // For backward compatibility - alias will be removed in 3.0.0
19608
+
19609
+
19610
+ exports.parseFmtpConfig = exports.parseParams;
19611
+
19612
+ exports.parsePayloads = function (str) {
19613
+ return str.toString().split(' ').map(Number);
19614
+ };
19615
+
19616
+ exports.parseRemoteCandidates = function (str) {
19617
+ var candidates = [];
19618
+ var parts = str.split(' ').map(toIntIfInt);
19619
+
19620
+ for (var i = 0; i < parts.length; i += 3) {
19621
+ candidates.push({
19622
+ component: parts[i],
19623
+ ip: parts[i + 1],
19624
+ port: parts[i + 2]
19625
+ });
19626
+ }
19627
+
19628
+ return candidates;
19629
+ };
19630
+
19631
+ exports.parseImageAttributes = function (str) {
19632
+ return str.split(' ').map(function (item) {
19633
+ return item.substring(1, item.length - 1).split(',').reduce(paramReducer, {});
19634
+ });
19635
+ };
19636
+
19637
+ exports.parseSimulcastStreamList = function (str) {
19638
+ return str.split(';').map(function (stream) {
19639
+ return stream.split(',').map(function (format) {
19640
+ var scid,
19641
+ paused = false;
19642
+
19643
+ if (format[0] !== '~') {
19644
+ scid = toIntIfInt(format);
19645
+ } else {
19646
+ scid = toIntIfInt(format.substring(1, format.length));
19647
+ paused = true;
19648
+ }
19649
+
19650
+ return {
19651
+ scid: scid,
19652
+ paused: paused
19653
+ };
19654
+ });
19655
+ });
19656
+ };
19657
+ })(parser$1);
19658
+
19659
+ var grammar = grammar$2.exports; // customized util.format - discards excess arguments and can void middle ones
19660
+
19661
+ var formatRegExp = /%[sdv%]/g;
19662
+
19663
+ var format = function (formatStr) {
19664
+ var i = 1;
19665
+ var args = arguments;
19666
+ var len = args.length;
19667
+ return formatStr.replace(formatRegExp, function (x) {
19668
+ if (i >= len) {
19669
+ return x; // missing argument
19670
+ }
19671
+
19672
+ var arg = args[i];
19673
+ i += 1;
19674
+
19675
+ switch (x) {
19676
+ case '%%':
19677
+ return '%';
19678
+
19679
+ case '%s':
19680
+ return String(arg);
19681
+
19682
+ case '%d':
19683
+ return Number(arg);
19684
+
19685
+ case '%v':
19686
+ return '';
19687
+ }
19688
+ }); // NB: we discard excess arguments - they are typically undefined from makeLine
19689
+ };
19690
+
19691
+ var makeLine = function (type, obj, location) {
19692
+ var str = obj.format instanceof Function ? obj.format(obj.push ? location : location[obj.name]) : obj.format;
19693
+ var args = [type + '=' + str];
19694
+
19695
+ if (obj.names) {
19696
+ for (var i = 0; i < obj.names.length; i += 1) {
19697
+ var n = obj.names[i];
19698
+
19699
+ if (obj.name) {
19700
+ args.push(location[obj.name][n]);
19701
+ } else {
19702
+ // for mLine and push attributes
19703
+ args.push(location[obj.names[i]]);
19704
+ }
19705
+ }
19706
+ } else {
19707
+ args.push(location[obj.name]);
19708
+ }
19709
+
19710
+ return format.apply(null, args);
19711
+ }; // RFC specified order
19712
+ // TODO: extend this with all the rest
19713
+
19714
+
19715
+ var defaultOuterOrder = ['v', 'o', 's', 'i', 'u', 'e', 'p', 'c', 'b', 't', 'r', 'z', 'a'];
19716
+ var defaultInnerOrder = ['i', 'c', 'b', 'a'];
19717
+
19718
+ var writer$1 = function (session, opts) {
19719
+ opts = opts || {}; // ensure certain properties exist
19720
+
19721
+ if (session.version == null) {
19722
+ session.version = 0; // 'v=0' must be there (only defined version atm)
19723
+ }
19724
+
19725
+ if (session.name == null) {
19726
+ session.name = ' '; // 's= ' must be there if no meaningful name set
19727
+ }
19728
+
19729
+ session.media.forEach(function (mLine) {
19730
+ if (mLine.payloads == null) {
19731
+ mLine.payloads = '';
19732
+ }
19733
+ });
19734
+ var outerOrder = opts.outerOrder || defaultOuterOrder;
19735
+ var innerOrder = opts.innerOrder || defaultInnerOrder;
19736
+ var sdp = []; // loop through outerOrder for matching properties on session
19737
+
19738
+ outerOrder.forEach(function (type) {
19739
+ grammar[type].forEach(function (obj) {
19740
+ if (obj.name in session && session[obj.name] != null) {
19741
+ sdp.push(makeLine(type, obj, session));
19742
+ } else if (obj.push in session && session[obj.push] != null) {
19743
+ session[obj.push].forEach(function (el) {
19744
+ sdp.push(makeLine(type, obj, el));
19745
+ });
19746
+ }
19747
+ });
19748
+ }); // then for each media line, follow the innerOrder
19749
+
19750
+ session.media.forEach(function (mLine) {
19751
+ sdp.push(makeLine('m', grammar.m[0], mLine));
19752
+ innerOrder.forEach(function (type) {
19753
+ grammar[type].forEach(function (obj) {
19754
+ if (obj.name in mLine && mLine[obj.name] != null) {
19755
+ sdp.push(makeLine(type, obj, mLine));
19756
+ } else if (obj.push in mLine && mLine[obj.push] != null) {
19757
+ mLine[obj.push].forEach(function (el) {
19758
+ sdp.push(makeLine(type, obj, el));
19759
+ });
19760
+ }
19761
+ });
19762
+ });
19763
+ });
19764
+ return sdp.join('\r\n') + '\r\n';
19765
+ };
19766
+
19767
+ var parser = parser$1;
19768
+ var writer = writer$1;
19769
+ var write = writer;
19770
+ var parse = parser.parse;
19771
+
19053
19772
  /** @internal */
19054
19773
 
19055
19774
  class PCTransport {
@@ -19092,6 +19811,8 @@ class PCTransport {
19092
19811
  }
19093
19812
 
19094
19813
  async createAndSendOffer(options) {
19814
+ var _a;
19815
+
19095
19816
  if (this.onOffer === undefined) {
19096
19817
  return;
19097
19818
  }
@@ -19121,30 +19842,72 @@ class PCTransport {
19121
19842
 
19122
19843
 
19123
19844
  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
19845
+ const offer = await this.pc.createOffer(options);
19846
+ const sdpParsed = parse((_a = offer.sdp) !== null && _a !== void 0 ? _a : '');
19847
+ sdpParsed.media.forEach(media => {
19848
+ if (media.type === 'audio') {
19849
+ ensureAudioNack(media);
19850
+ } else if (media.type === 'video') {
19851
+ // mung sdp for codec bitrate setting that can't apply by sendEncoding
19852
+ this.trackBitrates.some(trackbr => {
19853
+ if (!media.msid || !media.msid.includes(trackbr.sid)) {
19854
+ return false;
19855
+ }
19125
19856
 
19126
- this.trackBitrates.forEach(trackbr => {
19127
- var _a;
19857
+ let codecPayload = 0;
19858
+ media.rtp.some(rtp => {
19859
+ if (rtp.codec.toUpperCase() === trackbr.codec.toUpperCase()) {
19860
+ codecPayload = rtp.payload;
19861
+ return true;
19862
+ }
19128
19863
 
19129
- let sdp = (_a = offer.sdp) !== null && _a !== void 0 ? _a : '';
19130
- const sidIndex = sdp.search(new RegExp("msid.* ".concat(trackbr.sid)));
19864
+ return false;
19865
+ }); // add x-google-max-bitrate to fmtp line if not exist
19131
19866
 
19132
- if (sidIndex < 0) {
19133
- return;
19134
- }
19867
+ if (codecPayload > 0) {
19868
+ if (!media.fmtp.some(fmtp => {
19869
+ if (fmtp.payload === codecPayload) {
19870
+ if (!fmtp.config.includes('x-google-max-bitrate')) {
19871
+ fmtp.config += ";x-google-max-bitrate=".concat(trackbr.maxbr);
19872
+ }
19135
19873
 
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'), "$&\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;
19874
+ return true;
19875
+ }
19876
+
19877
+ return false;
19878
+ })) {
19879
+ media.fmtp.push({
19880
+ payload: codecPayload,
19881
+ config: "x-google-max-bitrate=".concat(trackbr.maxbr)
19882
+ });
19883
+ }
19884
+ }
19885
+
19886
+ return true;
19887
+ });
19888
+ }
19142
19889
  });
19890
+ offer.sdp = write(sdpParsed);
19143
19891
  this.trackBitrates = [];
19144
19892
  await this.pc.setLocalDescription(offer);
19145
19893
  this.onOffer(offer);
19146
19894
  }
19147
19895
 
19896
+ async createAndSetAnswer() {
19897
+ var _a;
19898
+
19899
+ const answer = await this.pc.createAnswer();
19900
+ const sdpParsed = parse((_a = answer.sdp) !== null && _a !== void 0 ? _a : '');
19901
+ sdpParsed.media.forEach(media => {
19902
+ if (media.type === 'audio') {
19903
+ ensureAudioNack(media);
19904
+ }
19905
+ });
19906
+ answer.sdp = write(sdpParsed);
19907
+ await this.pc.setLocalDescription(answer);
19908
+ return answer;
19909
+ }
19910
+
19148
19911
  setTrackCodecBitrate(sid, codec, maxbr) {
19149
19912
  this.trackBitrates.push({
19150
19913
  sid,
@@ -19159,11 +19922,35 @@ class PCTransport {
19159
19922
 
19160
19923
  }
19161
19924
 
19925
+ function ensureAudioNack(media) {
19926
+ // found opus codec to add nack fb
19927
+ let opusPayload = 0;
19928
+ media.rtp.some(rtp => {
19929
+ if (rtp.codec === 'opus') {
19930
+ opusPayload = rtp.payload;
19931
+ return true;
19932
+ }
19933
+
19934
+ return false;
19935
+ }); // add nack rtcpfb if not exist
19936
+
19937
+ if (opusPayload > 0) {
19938
+ if (!media.rtcpFb) {
19939
+ media.rtcpFb = [];
19940
+ }
19941
+
19942
+ if (!media.rtcpFb.some(fb => fb.payload === opusPayload && fb.type === 'nack')) {
19943
+ media.rtcpFb.push({
19944
+ payload: opusPayload,
19945
+ type: 'nack'
19946
+ });
19947
+ }
19948
+ }
19949
+ }
19950
+
19162
19951
  const lossyDataChannel = '_lossy';
19163
19952
  const reliableDataChannel = '_reliable';
19164
- const maxReconnectRetries = 10;
19165
19953
  const minReconnectWait = 2 * 1000;
19166
- const maxReconnectDuration = 60 * 1000;
19167
19954
  const maxICEConnectTimeout = 15 * 1000;
19168
19955
  var PCState;
19169
19956
 
@@ -19178,8 +19965,14 @@ var PCState;
19178
19965
 
19179
19966
 
19180
19967
  class RTCEngine extends events.exports.EventEmitter {
19181
- constructor() {
19968
+ constructor(options) {
19969
+ var _this;
19970
+
19971
+ var _a;
19972
+
19182
19973
  super();
19974
+ _this = this;
19975
+ this.options = options;
19183
19976
  this.rtcConfig = {};
19184
19977
  this.subscriberPrimary = false;
19185
19978
  this.pcState = PCState.New;
@@ -19254,53 +20047,85 @@ class RTCEngine extends events.exports.EventEmitter {
19254
20047
  // after a number of retries, we'll close and give up permanently
19255
20048
 
19256
20049
 
19257
- this.handleDisconnect = connection => {
19258
- if (this._isClosed) {
20050
+ this.handleDisconnect = function (connection) {
20051
+ let signalEvents = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
20052
+
20053
+ if (_this._isClosed) {
19259
20054
  return;
19260
20055
  }
19261
20056
 
19262
20057
  livekitLogger.debug("".concat(connection, " disconnected"));
19263
20058
 
19264
- if (this.reconnectAttempts === 0) {
20059
+ if (_this.reconnectAttempts === 0) {
19265
20060
  // only reset start time on the first try
19266
- this.reconnectStart = Date.now();
20061
+ _this.reconnectStart = Date.now();
20062
+ }
20063
+
20064
+ const disconnect = duration => {
20065
+ livekitLogger.info("could not recover connection after ".concat(_this.reconnectAttempts, " attempts, ").concat(duration, "ms. giving up"));
20066
+
20067
+ _this.emit(EngineEvent.Disconnected);
20068
+
20069
+ _this.close();
20070
+ };
20071
+
20072
+ const duration = Date.now() - _this.reconnectStart;
20073
+
20074
+ const delay = _this.getNextRetryDelay({
20075
+ elapsedMs: duration,
20076
+ retryCount: _this.reconnectAttempts
20077
+ });
20078
+
20079
+ if (delay === null) {
20080
+ disconnect(duration);
20081
+ return;
19267
20082
  }
19268
20083
 
19269
- const delay = this.reconnectAttempts * this.reconnectAttempts * 300;
19270
- setTimeout(async () => {
20084
+ livekitLogger.debug("reconnecting in ".concat(delay, "ms"));
20085
+
20086
+ if (_this.reconnectTimeout) {
20087
+ clearTimeout(_this.reconnectTimeout);
20088
+ }
20089
+
20090
+ _this.reconnectTimeout = setTimeout(async () => {
19271
20091
  var _a, _b, _c;
19272
20092
 
19273
- if (this._isClosed) {
20093
+ if (_this._isClosed) {
19274
20094
  return;
19275
20095
  } // guard for attempting reconnection multiple times while one attempt is still not finished
19276
20096
 
19277
20097
 
19278
- if (this.attemptingReconnect) {
20098
+ if (_this.attemptingReconnect) {
19279
20099
  return;
19280
20100
  }
19281
20101
 
19282
20102
  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
20103
+ ((_a = _this.clientConfiguration) === null || _a === void 0 ? void 0 : _a.resumeConnection) === ClientConfigSetting.DISABLED || // signaling state could change to closed due to hardware sleep
19284
20104
  // 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;
20105
+ ((_c = (_b = _this.primaryPC) === null || _b === void 0 ? void 0 : _b.signalingState) !== null && _c !== void 0 ? _c : 'closed') === 'closed') {
20106
+ _this.fullReconnectOnNext = true;
19287
20107
  }
19288
20108
 
19289
20109
  try {
19290
- this.attemptingReconnect = true;
20110
+ _this.attemptingReconnect = true;
19291
20111
 
19292
- if (this.fullReconnectOnNext) {
19293
- await this.restartConnection();
20112
+ if (_this.fullReconnectOnNext) {
20113
+ await _this.restartConnection(signalEvents);
19294
20114
  } else {
19295
- await this.resumeConnection();
20115
+ await _this.resumeConnection(signalEvents);
19296
20116
  }
19297
20117
 
19298
- this.reconnectAttempts = 0;
19299
- this.fullReconnectOnNext = false;
20118
+ _this.reconnectAttempts = 0;
20119
+ _this.fullReconnectOnNext = false;
20120
+
20121
+ if (_this.reconnectTimeout) {
20122
+ clearTimeout(_this.reconnectTimeout);
20123
+ }
19300
20124
  } catch (e) {
19301
- this.reconnectAttempts += 1;
20125
+ _this.reconnectAttempts += 1;
19302
20126
  let reconnectRequired = false;
19303
20127
  let recoverable = true;
20128
+ let requireSignalEvents = false;
19304
20129
 
19305
20130
  if (e instanceof UnexpectedConnectionState) {
19306
20131
  livekitLogger.debug('received unrecoverable error', {
@@ -19311,35 +20136,29 @@ class RTCEngine extends events.exports.EventEmitter {
19311
20136
  } else if (!(e instanceof SignalReconnectError)) {
19312
20137
  // cannot resume
19313
20138
  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
- }
20139
+ } // when we flip from resume to reconnect
20140
+ // we need to fire the right reconnecting events
19322
20141
 
19323
- const duration = Date.now() - this.reconnectStart;
19324
20142
 
19325
- if (this.reconnectAttempts >= maxReconnectRetries || duration > maxReconnectDuration) {
19326
- recoverable = false;
20143
+ if (reconnectRequired && !_this.fullReconnectOnNext) {
20144
+ _this.fullReconnectOnNext = true;
20145
+ requireSignalEvents = true;
19327
20146
  }
19328
20147
 
19329
20148
  if (recoverable) {
19330
- this.handleDisconnect('reconnect');
20149
+ _this.handleDisconnect('reconnect', requireSignalEvents);
19331
20150
  } 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();
20151
+ disconnect(Date.now() - _this.reconnectStart);
19335
20152
  }
19336
20153
  } finally {
19337
- this.attemptingReconnect = false;
20154
+ _this.attemptingReconnect = false;
19338
20155
  }
19339
20156
  }, delay);
19340
20157
  };
19341
20158
 
19342
20159
  this.client = new SignalClient();
20160
+ this.client.signalLatency = this.options.expSignalLatency;
20161
+ this.reconnectPolicy = (_a = this.options.reconnectPolicy) !== null && _a !== void 0 ? _a : new DefaultReconnectPolicy();
19343
20162
  }
19344
20163
 
19345
20164
  get isClosed() {
@@ -19403,8 +20222,16 @@ class RTCEngine extends events.exports.EventEmitter {
19403
20222
  throw new TrackInvalidError('a track with the same ID has already been published');
19404
20223
  }
19405
20224
 
19406
- return new Promise(resolve => {
19407
- this.pendingTrackResolvers[req.cid] = resolve;
20225
+ return new Promise((resolve, reject) => {
20226
+ const publicationTimeout = setTimeout(() => {
20227
+ reject(new ConnectionError('publication of local track timed out, no response from server'));
20228
+ }, 15000);
20229
+
20230
+ this.pendingTrackResolvers[req.cid] = info => {
20231
+ clearTimeout(publicationTimeout);
20232
+ resolve(info);
20233
+ };
20234
+
19408
20235
  this.client.sendAddTrack(req);
19409
20236
  });
19410
20237
  }
@@ -19578,8 +20405,7 @@ class RTCEngine extends events.exports.EventEmitter {
19578
20405
  });
19579
20406
  await this.subscriber.setRemoteDescription(sd); // answer the offer
19580
20407
 
19581
- const answer = await this.subscriber.pc.createAnswer();
19582
- await this.subscriber.pc.setLocalDescription(answer);
20408
+ const answer = await this.subscriber.createAndSetAnswer();
19583
20409
  this.client.sendAnswer(answer);
19584
20410
  };
19585
20411
 
@@ -19652,7 +20478,22 @@ class RTCEngine extends events.exports.EventEmitter {
19652
20478
  this.reliableDC.onerror = this.handleDataError;
19653
20479
  }
19654
20480
 
20481
+ getNextRetryDelay(context) {
20482
+ try {
20483
+ return this.reconnectPolicy.nextRetryDelayInMs(context);
20484
+ } catch (e) {
20485
+ livekitLogger.warn('encountered error in reconnect policy', {
20486
+ error: e
20487
+ });
20488
+ } // error in user code with provided reconnect policy, stop reconnecting
20489
+
20490
+
20491
+ return null;
20492
+ }
20493
+
19655
20494
  async restartConnection() {
20495
+ let emitRestarting = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
20496
+
19656
20497
  var _a, _b;
19657
20498
 
19658
20499
  if (!this.url || !this.token) {
@@ -19662,7 +20503,7 @@ class RTCEngine extends events.exports.EventEmitter {
19662
20503
 
19663
20504
  livekitLogger.info("reconnecting, attempt: ".concat(this.reconnectAttempts));
19664
20505
 
19665
- if (this.reconnectAttempts === 0) {
20506
+ if (emitRestarting || this.reconnectAttempts === 0) {
19666
20507
  this.emit(EngineEvent.Restarting);
19667
20508
  }
19668
20509
 
@@ -19691,6 +20532,8 @@ class RTCEngine extends events.exports.EventEmitter {
19691
20532
  }
19692
20533
 
19693
20534
  async resumeConnection() {
20535
+ let emitResuming = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
20536
+
19694
20537
  var _a;
19695
20538
 
19696
20539
  if (!this.url || !this.token) {
@@ -19705,14 +20548,20 @@ class RTCEngine extends events.exports.EventEmitter {
19705
20548
 
19706
20549
  livekitLogger.info("resuming signal connection, attempt ".concat(this.reconnectAttempts));
19707
20550
 
19708
- if (this.reconnectAttempts === 0) {
20551
+ if (emitResuming || this.reconnectAttempts === 0) {
19709
20552
  this.emit(EngineEvent.Resuming);
19710
20553
  }
19711
20554
 
19712
20555
  try {
19713
20556
  await this.client.reconnect(this.url, this.token);
19714
20557
  } catch (e) {
19715
- throw new SignalReconnectError();
20558
+ let message = '';
20559
+
20560
+ if (e instanceof Error) {
20561
+ message = e.message;
20562
+ }
20563
+
20564
+ throw new SignalReconnectError(message);
19716
20565
  }
19717
20566
 
19718
20567
  this.emit(EngineEvent.SignalResumed);
@@ -20020,23 +20869,7 @@ class Room extends events.exports.EventEmitter {
20020
20869
  this.localParticipant.identity = pi.identity;
20021
20870
  this.localParticipant.updateInfo(pi); // forward metadata changed for the local participant
20022
20871
 
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
20872
+ 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
20873
 
20041
20874
  joinResponse.otherParticipants.forEach(info => {
20042
20875
  if (info.sid !== this.localParticipant.sid && info.identity !== this.localParticipant.identity) {
@@ -20379,6 +21212,38 @@ class Room extends events.exports.EventEmitter {
20379
21212
  });
20380
21213
  };
20381
21214
 
21215
+ this.onLocalParticipantMetadataChanged = metadata => {
21216
+ this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
21217
+ };
21218
+
21219
+ this.onLocalTrackMuted = pub => {
21220
+ this.emit(RoomEvent.TrackMuted, pub, this.localParticipant);
21221
+ };
21222
+
21223
+ this.onLocalTrackUnmuted = pub => {
21224
+ this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
21225
+ };
21226
+
21227
+ this.onLocalTrackPublished = pub => {
21228
+ this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
21229
+ };
21230
+
21231
+ this.onLocalTrackUnpublished = pub => {
21232
+ this.emit(RoomEvent.LocalTrackUnpublished, pub, this.localParticipant);
21233
+ };
21234
+
21235
+ this.onLocalConnectionQualityChanged = quality => {
21236
+ this.emit(RoomEvent.ConnectionQualityChanged, quality, this.localParticipant);
21237
+ };
21238
+
21239
+ this.onMediaDevicesError = e => {
21240
+ this.emit(RoomEvent.MediaDevicesError, e);
21241
+ };
21242
+
21243
+ this.onLocalParticipantPermissionsChanged = prevPermissions => {
21244
+ this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, this.localParticipant);
21245
+ };
21246
+
20382
21247
  this.participants = new Map();
20383
21248
  this.identityToSid = new Map();
20384
21249
  this.options = options || {};
@@ -20394,8 +21259,7 @@ class Room extends events.exports.EventEmitter {
20394
21259
  return;
20395
21260
  }
20396
21261
 
20397
- this.engine = new RTCEngine();
20398
- this.engine.client.signalLatency = this.options.expSignalLatency;
21262
+ this.engine = new RTCEngine(this.options);
20399
21263
  this.engine.client.onParticipantUpdate = this.handleParticipantUpdates;
20400
21264
  this.engine.client.onRoomUpdate = this.handleRoomUpdate;
20401
21265
  this.engine.client.onSpeakersChanged = this.handleSpeakersChanged;
@@ -20676,6 +21540,7 @@ class Room extends events.exports.EventEmitter {
20676
21540
  p.unpublishTrack(pub.trackSid);
20677
21541
  });
20678
21542
  });
21543
+ 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
21544
  this.localParticipant.tracks.forEach(pub => {
20680
21545
  var _a, _b;
20681
21546
 
@@ -21033,5 +21898,5 @@ async function createLocalScreenTracks(options) {
21033
21898
  return localTracks;
21034
21899
  }
21035
21900
 
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 };
21901
+ 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
21902
  //# sourceMappingURL=livekit-client.esm.mjs.map