livekit-client 2.9.0 → 2.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. package/dist/livekit-client.esm.mjs +393 -75
  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/connectionHelper/ConnectionCheck.d.ts +2 -0
  6. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  7. package/dist/src/connectionHelper/checks/Checker.d.ts +5 -2
  8. package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
  9. package/dist/src/connectionHelper/checks/cloudRegion.d.ts +17 -0
  10. package/dist/src/connectionHelper/checks/cloudRegion.d.ts.map +1 -0
  11. package/dist/src/connectionHelper/checks/connectionProtocol.d.ts +19 -0
  12. package/dist/src/connectionHelper/checks/connectionProtocol.d.ts.map +1 -0
  13. package/dist/src/connectionHelper/checks/publishAudio.d.ts.map +1 -1
  14. package/dist/src/connectionHelper/checks/publishVideo.d.ts +1 -0
  15. package/dist/src/connectionHelper/checks/publishVideo.d.ts.map +1 -1
  16. package/dist/src/index.d.ts +2 -2
  17. package/dist/src/index.d.ts.map +1 -1
  18. package/dist/src/room/StreamReader.d.ts +4 -4
  19. package/dist/src/room/StreamReader.d.ts.map +1 -1
  20. package/dist/src/room/StreamWriter.d.ts +3 -3
  21. package/dist/src/room/StreamWriter.d.ts.map +1 -1
  22. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  23. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  24. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  25. package/dist/src/room/track/create.d.ts.map +1 -1
  26. package/dist/src/room/types.d.ts +0 -5
  27. package/dist/src/room/types.d.ts.map +1 -1
  28. package/dist/src/room/utils.d.ts +1 -0
  29. package/dist/src/room/utils.d.ts.map +1 -1
  30. package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +2 -0
  31. package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +5 -2
  32. package/dist/ts4.2/src/connectionHelper/checks/cloudRegion.d.ts +18 -0
  33. package/dist/ts4.2/src/connectionHelper/checks/connectionProtocol.d.ts +20 -0
  34. package/dist/ts4.2/src/connectionHelper/checks/publishVideo.d.ts +1 -0
  35. package/dist/ts4.2/src/index.d.ts +2 -2
  36. package/dist/ts4.2/src/room/StreamReader.d.ts +4 -4
  37. package/dist/ts4.2/src/room/StreamWriter.d.ts +3 -12
  38. package/dist/ts4.2/src/room/types.d.ts +0 -5
  39. package/dist/ts4.2/src/room/utils.d.ts +1 -0
  40. package/package.json +17 -17
  41. package/src/connectionHelper/ConnectionCheck.ts +15 -0
  42. package/src/connectionHelper/checks/Checker.ts +41 -8
  43. package/src/connectionHelper/checks/cloudRegion.ts +94 -0
  44. package/src/connectionHelper/checks/connectionProtocol.ts +149 -0
  45. package/src/connectionHelper/checks/publishAudio.ts +8 -0
  46. package/src/connectionHelper/checks/publishVideo.ts +52 -0
  47. package/src/index.ts +1 -1
  48. package/src/room/StreamReader.ts +9 -16
  49. package/src/room/StreamWriter.ts +4 -4
  50. package/src/room/participant/LocalParticipant.ts +9 -26
  51. package/src/room/participant/publishUtils.ts +4 -0
  52. package/src/room/track/LocalTrack.ts +5 -2
  53. package/src/room/track/create.ts +9 -5
  54. package/src/room/types.ts +0 -6
  55. package/src/room/utils.ts +16 -0
@@ -6666,6 +6666,18 @@ PERFORMANCE OF THIS SOFTWARE.
6666
6666
  /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
6667
6667
 
6668
6668
 
6669
+ function __rest(s, e) {
6670
+ var t = {};
6671
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
6672
+ t[p] = s[p];
6673
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6674
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
6675
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
6676
+ t[p[i]] = s[p[i]];
6677
+ }
6678
+ return t;
6679
+ }
6680
+
6669
6681
  function __awaiter(thisArg, _arguments, P, generator) {
6670
6682
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6671
6683
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -11150,7 +11162,7 @@ function getOSVersion(ua) {
11150
11162
  return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
11151
11163
  }
11152
11164
 
11153
- var version$1 = "2.9.0";
11165
+ var version$1 = "2.9.2";
11154
11166
 
11155
11167
  const version = version$1;
11156
11168
  const protocolVersion = 15;
@@ -12130,6 +12142,21 @@ function isLocalParticipant(p) {
12130
12142
  function isRemoteParticipant(p) {
12131
12143
  return !p.isLocal;
12132
12144
  }
12145
+ function splitUtf8(s, n) {
12146
+ // adapted from https://stackoverflow.com/a/6043797
12147
+ const result = [];
12148
+ while (s.length > n) {
12149
+ let k = n;
12150
+ // Move back to find the start of a UTF-8 character
12151
+ while ((s.charCodeAt(k) & 0xc0) === 0x80) {
12152
+ k--;
12153
+ }
12154
+ result.push(s.slice(0, k));
12155
+ s = s.slice(k);
12156
+ }
12157
+ result.push(s);
12158
+ return result;
12159
+ }
12133
12160
 
12134
12161
  function mergeDefaultOptions(options, audioDefaults, videoDefaults) {
12135
12162
  var _a, _b, _c;
@@ -15557,6 +15584,11 @@ class LocalTrack extends Track {
15557
15584
  if (!constraints) {
15558
15585
  constraints = this._constraints;
15559
15586
  }
15587
+ const _a = this._constraints,
15588
+ {
15589
+ deviceId
15590
+ } = _a,
15591
+ otherConstraints = __rest(_a, ["deviceId"]);
15560
15592
  this.log.debug('restarting track with constraints', Object.assign(Object.assign({}, this.logContext), {
15561
15593
  constraints
15562
15594
  }));
@@ -15565,9 +15597,13 @@ class LocalTrack extends Track {
15565
15597
  video: false
15566
15598
  };
15567
15599
  if (this.kind === Track.Kind.Video) {
15568
- streamConstraints.video = constraints;
15600
+ streamConstraints.video = deviceId ? {
15601
+ deviceId
15602
+ } : true;
15569
15603
  } else {
15570
- streamConstraints.audio = constraints;
15604
+ streamConstraints.audio = deviceId ? {
15605
+ deviceId
15606
+ } : true;
15571
15607
  }
15572
15608
  // these steps are duplicated from setMediaStreamTrack because we must stop
15573
15609
  // the previous tracks before new tracks can be acquired
@@ -15582,6 +15618,7 @@ class LocalTrack extends Track {
15582
15618
  // create new track and attach
15583
15619
  const mediaStream = yield navigator.mediaDevices.getUserMedia(streamConstraints);
15584
15620
  const newTrack = mediaStream.getTracks()[0];
15621
+ yield newTrack.applyConstraints(otherConstraints);
15585
15622
  newTrack.addEventListener('ended', this.handleEnded);
15586
15623
  this.log.debug('re-acquired MediaStreamTrack', this.logContext);
15587
15624
  yield this.setMediaStreamTrack(newTrack);
@@ -16191,6 +16228,10 @@ function computeTrackBackupEncodings(track, videoCodec, opts) {
16191
16228
  const settings = track.mediaStreamTrack.getSettings();
16192
16229
  const width = (_a = settings.width) !== null && _a !== void 0 ? _a : (_b = track.dimensions) === null || _b === void 0 ? void 0 : _b.width;
16193
16230
  const height = (_c = settings.height) !== null && _c !== void 0 ? _c : (_d = track.dimensions) === null || _d === void 0 ? void 0 : _d.height;
16231
+ // disable simulcast for screenshare backup codec since L1Tx is used by primary codec
16232
+ if (track.source === Track.Source.ScreenShare && opts.simulcast) {
16233
+ opts.simulcast = false;
16234
+ }
16194
16235
  const encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, width, height, opts);
16195
16236
  return encodings;
16196
16237
  }
@@ -18239,11 +18280,13 @@ class ByteStreamReader extends BaseStreamReader {
18239
18280
  }
18240
18281
  }),
18241
18282
  return() {
18242
- reader.releaseLock();
18243
- return {
18244
- done: true,
18245
- value: undefined
18246
- };
18283
+ return __awaiter(this, void 0, void 0, function* () {
18284
+ reader.releaseLock();
18285
+ return {
18286
+ done: true,
18287
+ value: undefined
18288
+ };
18289
+ });
18247
18290
  }
18248
18291
  };
18249
18292
  }
@@ -18322,11 +18365,7 @@ class TextStreamReader extends BaseStreamReader {
18322
18365
  this.handleChunkReceived(value);
18323
18366
  return {
18324
18367
  done: false,
18325
- value: {
18326
- index: bigIntToNumber(value.chunkIndex),
18327
- current: decoder.decode(value.content),
18328
- collected: Array.from(this.receivedChunks.values()).sort((a, b) => bigIntToNumber(a.chunkIndex) - bigIntToNumber(b.chunkIndex)).map(chunk => decoder.decode(chunk.content)).join('')
18329
- }
18368
+ value: decoder.decode(value.content)
18330
18369
  };
18331
18370
  }
18332
18371
  } catch (error) {
@@ -18338,26 +18377,26 @@ class TextStreamReader extends BaseStreamReader {
18338
18377
  }
18339
18378
  }),
18340
18379
  return() {
18341
- reader.releaseLock();
18342
- return {
18343
- done: true,
18344
- value: undefined
18345
- };
18380
+ return __awaiter(this, void 0, void 0, function* () {
18381
+ reader.releaseLock();
18382
+ return {
18383
+ done: true,
18384
+ value: undefined
18385
+ };
18386
+ });
18346
18387
  }
18347
18388
  };
18348
18389
  }
18349
18390
  readAll() {
18350
18391
  return __awaiter(this, void 0, void 0, function* () {
18351
18392
  var _a, e_2, _b, _c;
18352
- let latestString = '';
18393
+ let finalString = '';
18353
18394
  try {
18354
18395
  for (var _d = true, _e = __asyncValues(this), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
18355
18396
  _c = _f.value;
18356
18397
  _d = false;
18357
- const {
18358
- collected
18359
- } = _c;
18360
- latestString = collected;
18398
+ const chunk = _c;
18399
+ finalString += chunk;
18361
18400
  }
18362
18401
  } catch (e_2_1) {
18363
18402
  e_2 = {
@@ -18370,7 +18409,7 @@ class TextStreamReader extends BaseStreamReader {
18370
18409
  if (e_2) throw e_2.error;
18371
18410
  }
18372
18411
  }
18373
- return latestString;
18412
+ return finalString;
18374
18413
  });
18375
18414
  }
18376
18415
  }
@@ -18383,7 +18422,7 @@ class BaseStreamWriter {
18383
18422
  this.info = info;
18384
18423
  }
18385
18424
  write(chunk) {
18386
- return this.defaultWriter.write([chunk]);
18425
+ return this.defaultWriter.write(chunk);
18387
18426
  }
18388
18427
  close() {
18389
18428
  return __awaiter(this, void 0, void 0, function* () {
@@ -20790,14 +20829,9 @@ class LocalParticipant extends Participant {
20790
20829
  topic: options === null || options === void 0 ? void 0 : options.topic,
20791
20830
  attachedStreamIds: fileIds
20792
20831
  });
20793
- const textChunkSize = Math.floor(STREAM_CHUNK_SIZE / 4); // utf8 is at most 4 bytes long, so play it safe and take a quarter of the byte size to slice the string
20794
- const totalTextChunks = Math.ceil(totalTextLength / textChunkSize);
20795
- for (let i = 0; i < totalTextChunks; i++) {
20796
- const chunkData = text.slice(i * textChunkSize, Math.min((i + 1) * textChunkSize, totalTextLength));
20797
- yield this.engine.waitForBufferStatusLow(DataPacket_Kind.RELIABLE);
20798
- yield writer.write(chunkData);
20799
- handleProgress(Math.ceil((i + 1) / totalTextChunks), 0);
20800
- }
20832
+ yield writer.write(text);
20833
+ // set text part of progress to 1
20834
+ handleProgress(1, 0);
20801
20835
  yield writer.close();
20802
20836
  if ((options === null || options === void 0 ? void 0 : options.attachments) && fileIds) {
20803
20837
  yield Promise.all(options.attachments.map((file, idx) => __awaiter(this, void 0, void 0, function* () {
@@ -20857,32 +20891,26 @@ class LocalParticipant extends Participant {
20857
20891
  const localP = this;
20858
20892
  const writableStream = new WritableStream({
20859
20893
  // Implement the sink
20860
- write(_ref3) {
20861
- let [textChunk] = _ref3;
20862
- var _a;
20863
- const textInBytes = new TextEncoder().encode(textChunk);
20864
- if (textInBytes.byteLength > STREAM_CHUNK_SIZE) {
20865
- (_a = this.abort) === null || _a === void 0 ? void 0 : _a.call(this);
20866
- throw new Error('chunk size too large');
20867
- }
20868
- return new Promise(resolve => __awaiter(this, void 0, void 0, function* () {
20869
- yield localP.engine.waitForBufferStatusLow(DataPacket_Kind.RELIABLE);
20870
- const chunk = new DataStream_Chunk({
20871
- content: textInBytes,
20872
- streamId,
20873
- chunkIndex: numberToBigInt(chunkId)
20874
- });
20875
- const chunkPacket = new DataPacket({
20876
- destinationIdentities,
20877
- value: {
20878
- case: 'streamChunk',
20879
- value: chunk
20880
- }
20881
- });
20882
- yield localP.engine.sendDataPacket(chunkPacket, DataPacket_Kind.RELIABLE);
20883
- chunkId += 1;
20884
- resolve();
20885
- }));
20894
+ write(text) {
20895
+ return __awaiter(this, void 0, void 0, function* () {
20896
+ for (const textChunk in splitUtf8(text, STREAM_CHUNK_SIZE)) {
20897
+ yield localP.engine.waitForBufferStatusLow(DataPacket_Kind.RELIABLE);
20898
+ const chunk = new DataStream_Chunk({
20899
+ content: new TextEncoder().encode(textChunk),
20900
+ streamId,
20901
+ chunkIndex: numberToBigInt(chunkId)
20902
+ });
20903
+ const chunkPacket = new DataPacket({
20904
+ destinationIdentities,
20905
+ value: {
20906
+ case: 'streamChunk',
20907
+ value: chunk
20908
+ }
20909
+ });
20910
+ yield localP.engine.sendDataPacket(chunkPacket, DataPacket_Kind.RELIABLE);
20911
+ chunkId += 1;
20912
+ }
20913
+ });
20886
20914
  },
20887
20915
  close() {
20888
20916
  return __awaiter(this, void 0, void 0, function* () {
@@ -20996,14 +21024,14 @@ class LocalParticipant extends Participant {
20996
21024
  * @throws Error on failure. Details in `message`.
20997
21025
  */
20998
21026
  performRpc(_a) {
20999
- return __awaiter(this, arguments, void 0, function (_ref4) {
21027
+ return __awaiter(this, arguments, void 0, function (_ref3) {
21000
21028
  var _this5 = this;
21001
21029
  let {
21002
21030
  destinationIdentity,
21003
21031
  method,
21004
21032
  payload,
21005
21033
  responseTimeout = 10000
21006
- } = _ref4;
21034
+ } = _ref3;
21007
21035
  return function* () {
21008
21036
  const maxRoundTripLatency = 2000;
21009
21037
  return new Promise((resolve, reject) => __awaiter(_this5, void 0, void 0, function* () {
@@ -21208,8 +21236,8 @@ class LocalParticipant extends Participant {
21208
21236
  const waitForPendingTimeout = 10000;
21209
21237
  const startTime = Date.now();
21210
21238
  while (Date.now() < startTime + waitForPendingTimeout) {
21211
- const publishPromiseEntry = Array.from(this.pendingPublishPromises.entries()).find(_ref5 => {
21212
- let [pendingTrack] = _ref5;
21239
+ const publishPromiseEntry = Array.from(this.pendingPublishPromises.entries()).find(_ref4 => {
21240
+ let [pendingTrack] = _ref4;
21213
21241
  return pendingTrack.source === source;
21214
21242
  });
21215
21243
  if (publishPromiseEntry) {
@@ -23666,15 +23694,13 @@ class Checker extends eventsExports.EventEmitter {
23666
23694
  super();
23667
23695
  this.status = CheckStatus.IDLE;
23668
23696
  this.logs = [];
23669
- this.errorsAsWarnings = false;
23697
+ this.options = {};
23670
23698
  this.url = url;
23671
23699
  this.token = token;
23672
23700
  this.name = this.constructor.name;
23673
23701
  this.room = new Room(options.roomOptions);
23674
23702
  this.connectOptions = options.connectOptions;
23675
- if (options.errorsAsWarnings) {
23676
- this.errorsAsWarnings = options.errorsAsWarnings;
23677
- }
23703
+ this.options = options;
23678
23704
  }
23679
23705
  run(onComplete) {
23680
23706
  return __awaiter(this, void 0, void 0, function* () {
@@ -23686,7 +23712,7 @@ class Checker extends eventsExports.EventEmitter {
23686
23712
  yield this.perform();
23687
23713
  } catch (err) {
23688
23714
  if (err instanceof Error) {
23689
- if (this.errorsAsWarnings) {
23715
+ if (this.options.errorsAsWarnings) {
23690
23716
  this.appendWarning(err.message);
23691
23717
  } else {
23692
23718
  this.appendError(err.message);
@@ -23709,12 +23735,15 @@ class Checker extends eventsExports.EventEmitter {
23709
23735
  isSuccess() {
23710
23736
  return !this.logs.some(l => l.level === 'error');
23711
23737
  }
23712
- connect() {
23738
+ connect(url) {
23713
23739
  return __awaiter(this, void 0, void 0, function* () {
23714
23740
  if (this.room.state === ConnectionState.Connected) {
23715
23741
  return this.room;
23716
23742
  }
23717
- yield this.room.connect(this.url, this.token, this.connectOptions);
23743
+ if (!url) {
23744
+ url = this.url;
23745
+ }
23746
+ yield this.room.connect(url, this.token, this.connectOptions);
23718
23747
  return this.room;
23719
23748
  });
23720
23749
  }
@@ -23730,6 +23759,33 @@ class Checker extends eventsExports.EventEmitter {
23730
23759
  skip() {
23731
23760
  this.setStatus(CheckStatus.SKIPPED);
23732
23761
  }
23762
+ switchProtocol(protocol) {
23763
+ return __awaiter(this, void 0, void 0, function* () {
23764
+ let hasReconnecting = false;
23765
+ let hasReconnected = false;
23766
+ this.room.on(RoomEvent.Reconnecting, () => {
23767
+ hasReconnecting = true;
23768
+ });
23769
+ this.room.once(RoomEvent.Reconnected, () => {
23770
+ hasReconnected = true;
23771
+ });
23772
+ this.room.simulateScenario("force-".concat(protocol));
23773
+ yield new Promise(resolve => setTimeout(resolve, 1000));
23774
+ if (!hasReconnecting) {
23775
+ // no need to wait for reconnection
23776
+ return;
23777
+ }
23778
+ // wait for 10 seconds for reconnection
23779
+ const timeout = Date.now() + 10000;
23780
+ while (Date.now() < timeout) {
23781
+ if (hasReconnected) {
23782
+ return;
23783
+ }
23784
+ yield sleep(100);
23785
+ }
23786
+ throw new Error("Could not reconnect using ".concat(protocol, " protocol after 10 seconds"));
23787
+ });
23788
+ }
23733
23789
  appendMessage(message) {
23734
23790
  this.logs.push({
23735
23791
  level: 'info',
@@ -23769,6 +23825,201 @@ class Checker extends eventsExports.EventEmitter {
23769
23825
  }
23770
23826
  }
23771
23827
 
23828
+ /**
23829
+ * Checks for connections quality to closests Cloud regions and determining the best quality
23830
+ */
23831
+ class CloudRegionCheck extends Checker {
23832
+ get description() {
23833
+ return 'Cloud regions';
23834
+ }
23835
+ perform() {
23836
+ return __awaiter(this, void 0, void 0, function* () {
23837
+ const regionProvider = new RegionUrlProvider(this.url, this.token);
23838
+ if (!regionProvider.isCloud()) {
23839
+ this.skip();
23840
+ return;
23841
+ }
23842
+ const regionStats = [];
23843
+ const seenUrls = new Set();
23844
+ for (let i = 0; i < 3; i++) {
23845
+ const regionUrl = yield regionProvider.getNextBestRegionUrl();
23846
+ if (!regionUrl) {
23847
+ break;
23848
+ }
23849
+ if (seenUrls.has(regionUrl)) {
23850
+ continue;
23851
+ }
23852
+ seenUrls.add(regionUrl);
23853
+ const stats = yield this.checkCloudRegion(regionUrl);
23854
+ this.appendMessage("".concat(stats.region, " RTT: ").concat(stats.rtt, "ms, duration: ").concat(stats.duration, "ms"));
23855
+ regionStats.push(stats);
23856
+ }
23857
+ regionStats.sort((a, b) => {
23858
+ return (a.duration - b.duration) * 0.5 + (a.rtt - b.rtt) * 0.5;
23859
+ });
23860
+ const bestRegion = regionStats[0];
23861
+ this.bestStats = bestRegion;
23862
+ this.appendMessage("best Cloud region: ".concat(bestRegion.region));
23863
+ });
23864
+ }
23865
+ getInfo() {
23866
+ const info = super.getInfo();
23867
+ info.data = this.bestStats;
23868
+ return info;
23869
+ }
23870
+ checkCloudRegion(url) {
23871
+ return __awaiter(this, void 0, void 0, function* () {
23872
+ var _a, _b;
23873
+ yield this.connect(url);
23874
+ if (this.options.protocol === 'tcp') {
23875
+ yield this.switchProtocol('tcp');
23876
+ }
23877
+ const region = (_a = this.room.serverInfo) === null || _a === void 0 ? void 0 : _a.region;
23878
+ if (!region) {
23879
+ throw new Error('Region not found');
23880
+ }
23881
+ const writer = yield this.room.localParticipant.streamText({
23882
+ topic: 'test'
23883
+ });
23884
+ const chunkSize = 1000; // each chunk is about 1000 bytes
23885
+ const totalSize = 1000000; // approximately 1MB of data
23886
+ const numChunks = totalSize / chunkSize; // will yield 1000 chunks
23887
+ const chunkData = 'A'.repeat(chunkSize); // create a string of 1000 'A' characters
23888
+ const startTime = Date.now();
23889
+ for (let i = 0; i < numChunks; i++) {
23890
+ yield writer.write(chunkData);
23891
+ }
23892
+ yield writer.close();
23893
+ const endTime = Date.now();
23894
+ const stats = yield (_b = this.room.engine.pcManager) === null || _b === void 0 ? void 0 : _b.publisher.getStats();
23895
+ const regionStats = {
23896
+ region: region,
23897
+ rtt: 10000,
23898
+ duration: endTime - startTime
23899
+ };
23900
+ stats === null || stats === void 0 ? void 0 : stats.forEach(stat => {
23901
+ if (stat.type === 'candidate-pair' && stat.nominated) {
23902
+ regionStats.rtt = stat.currentRoundTripTime * 1000;
23903
+ }
23904
+ });
23905
+ yield this.disconnect();
23906
+ return regionStats;
23907
+ });
23908
+ }
23909
+ }
23910
+
23911
+ const TEST_DURATION = 10000;
23912
+ class ConnectionProtocolCheck extends Checker {
23913
+ get description() {
23914
+ return 'Connection via UDP vs TCP';
23915
+ }
23916
+ perform() {
23917
+ return __awaiter(this, void 0, void 0, function* () {
23918
+ const udpStats = yield this.checkConnectionProtocol('udp');
23919
+ const tcpStats = yield this.checkConnectionProtocol('tcp');
23920
+ this.bestStats = udpStats;
23921
+ // udp should is the better protocol typically. however, we'd prefer TCP when either of these conditions are true:
23922
+ // 1. the bandwidth limitation is worse on UDP by 500ms
23923
+ // 2. the packet loss is higher on UDP by 1%
23924
+ if (udpStats.qualityLimitationDurations.bandwidth - tcpStats.qualityLimitationDurations.bandwidth > 0.5 || (udpStats.packetsLost - tcpStats.packetsLost) / udpStats.packetsSent > 0.01) {
23925
+ this.appendMessage('best connection quality via tcp');
23926
+ this.bestStats = tcpStats;
23927
+ } else {
23928
+ this.appendMessage('best connection quality via udp');
23929
+ }
23930
+ const stats = this.bestStats;
23931
+ this.appendMessage("upstream bitrate: ".concat((stats.bitrateTotal / stats.count / 1000 / 1000).toFixed(2), " mbps"));
23932
+ this.appendMessage("RTT: ".concat((stats.rttTotal / stats.count * 1000).toFixed(2), " ms"));
23933
+ this.appendMessage("jitter: ".concat((stats.jitterTotal / stats.count * 1000).toFixed(2), " ms"));
23934
+ if (stats.packetsLost > 0) {
23935
+ this.appendWarning("packets lost: ".concat((stats.packetsLost / stats.packetsSent * 100).toFixed(2), "%"));
23936
+ }
23937
+ if (stats.qualityLimitationDurations.bandwidth > 1) {
23938
+ this.appendWarning("bandwidth limited ".concat((stats.qualityLimitationDurations.bandwidth / (TEST_DURATION / 1000) * 100).toFixed(2), "%"));
23939
+ }
23940
+ if (stats.qualityLimitationDurations.cpu > 0) {
23941
+ this.appendWarning("cpu limited ".concat((stats.qualityLimitationDurations.cpu / (TEST_DURATION / 1000) * 100).toFixed(2), "%"));
23942
+ }
23943
+ });
23944
+ }
23945
+ getInfo() {
23946
+ const info = super.getInfo();
23947
+ info.data = this.bestStats;
23948
+ return info;
23949
+ }
23950
+ checkConnectionProtocol(protocol) {
23951
+ return __awaiter(this, void 0, void 0, function* () {
23952
+ yield this.connect();
23953
+ if (protocol === 'tcp') {
23954
+ yield this.switchProtocol('tcp');
23955
+ } else {
23956
+ yield this.switchProtocol('udp');
23957
+ }
23958
+ // create a canvas with animated content
23959
+ const canvas = document.createElement('canvas');
23960
+ canvas.width = 1280;
23961
+ canvas.height = 720;
23962
+ const ctx = canvas.getContext('2d');
23963
+ if (!ctx) {
23964
+ throw new Error('Could not get canvas context');
23965
+ }
23966
+ let hue = 0;
23967
+ const animate = () => {
23968
+ hue = (hue + 1) % 360;
23969
+ ctx.fillStyle = "hsl(".concat(hue, ", 100%, 50%)");
23970
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
23971
+ requestAnimationFrame(animate);
23972
+ };
23973
+ animate();
23974
+ // create video track from canvas
23975
+ const stream = canvas.captureStream(30); // 30fps
23976
+ const videoTrack = stream.getVideoTracks()[0];
23977
+ // publish to room
23978
+ const pub = yield this.room.localParticipant.publishTrack(videoTrack, {
23979
+ simulcast: false,
23980
+ degradationPreference: 'maintain-resolution',
23981
+ videoEncoding: {
23982
+ maxBitrate: 2000000
23983
+ }
23984
+ });
23985
+ const track = pub.track;
23986
+ const protocolStats = {
23987
+ protocol,
23988
+ packetsLost: 0,
23989
+ packetsSent: 0,
23990
+ qualityLimitationDurations: {},
23991
+ rttTotal: 0,
23992
+ jitterTotal: 0,
23993
+ bitrateTotal: 0,
23994
+ count: 0
23995
+ };
23996
+ // gather stats once a second
23997
+ const interval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
23998
+ const stats = yield track.getRTCStatsReport();
23999
+ stats === null || stats === void 0 ? void 0 : stats.forEach(stat => {
24000
+ if (stat.type === 'outbound-rtp') {
24001
+ protocolStats.packetsSent = stat.packetsSent;
24002
+ protocolStats.qualityLimitationDurations = stat.qualityLimitationDurations;
24003
+ protocolStats.bitrateTotal += stat.targetBitrate;
24004
+ protocolStats.count++;
24005
+ } else if (stat.type === 'remote-inbound-rtp') {
24006
+ protocolStats.packetsLost = stat.packetsLost;
24007
+ protocolStats.rttTotal += stat.roundTripTime;
24008
+ protocolStats.jitterTotal += stat.jitter;
24009
+ }
24010
+ });
24011
+ }), 1000);
24012
+ // wait a bit to gather stats
24013
+ yield new Promise(resolve => setTimeout(resolve, TEST_DURATION));
24014
+ clearInterval(interval);
24015
+ videoTrack.stop();
24016
+ canvas.remove();
24017
+ yield this.disconnect();
24018
+ return protocolStats;
24019
+ });
24020
+ }
24021
+ }
24022
+
23772
24023
  /**
23773
24024
  * Creates a local video and audio track at the same time. When acquiring both
23774
24025
  * audio and video tracks together, it'll display a single permission prompt to
@@ -23814,11 +24065,12 @@ function createLocalTracks(options) {
23814
24065
  }
23815
24066
  // update the constraints with the device id the user gave permissions to in the permission prompt
23816
24067
  // otherwise each track restart (e.g. mute - unmute) will try to initialize the device again -> causing additional permission prompts
23817
- if (trackConstraints) {
23818
- trackConstraints.deviceId = mediaStreamTrack.getSettings().deviceId;
23819
- } else {
24068
+ const newDeviceId = mediaStreamTrack.getSettings().deviceId;
24069
+ if ((trackConstraints === null || trackConstraints === void 0 ? void 0 : trackConstraints.deviceId) && unwrapConstraint(trackConstraints.deviceId) !== newDeviceId) {
24070
+ trackConstraints.deviceId = newDeviceId;
24071
+ } else if (!trackConstraints) {
23820
24072
  trackConstraints = {
23821
- deviceId: mediaStreamTrack.getSettings().deviceId
24073
+ deviceId: newDeviceId
23822
24074
  };
23823
24075
  }
23824
24076
  const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints);
@@ -23902,6 +24154,11 @@ class PublishAudioCheck extends Checker {
23902
24154
  var _a;
23903
24155
  const room = yield this.connect();
23904
24156
  const track = yield createLocalAudioTrack();
24157
+ const trackIsSilent = yield detectSilence(track, 1000);
24158
+ if (trackIsSilent) {
24159
+ throw new Error('unable to detect audio from microphone');
24160
+ }
24161
+ this.appendMessage('detected audio from microphone');
23905
24162
  room.localParticipant.publishTrack(track);
23906
24163
  // wait for a few seconds to publish
23907
24164
  yield new Promise(resolve => setTimeout(resolve, 3000));
@@ -23933,6 +24190,8 @@ class PublishVideoCheck extends Checker {
23933
24190
  var _a;
23934
24191
  const room = yield this.connect();
23935
24192
  const track = yield createLocalVideoTrack();
24193
+ // check if we have video from camera
24194
+ yield this.checkForVideo(track.mediaStreamTrack);
23936
24195
  room.localParticipant.publishTrack(track);
23937
24196
  // wait for a few seconds to publish
23938
24197
  yield new Promise(resolve => setTimeout(resolve, 5000));
@@ -23953,6 +24212,50 @@ class PublishVideoCheck extends Checker {
23953
24212
  this.appendMessage("published ".concat(numPackets, " video packets"));
23954
24213
  });
23955
24214
  }
24215
+ checkForVideo(track) {
24216
+ return __awaiter(this, void 0, void 0, function* () {
24217
+ const stream = new MediaStream();
24218
+ stream.addTrack(track.clone());
24219
+ // Create video element to check frames
24220
+ const video = document.createElement('video');
24221
+ video.srcObject = stream;
24222
+ video.muted = true;
24223
+ yield new Promise(resolve => {
24224
+ video.onplay = () => {
24225
+ setTimeout(() => {
24226
+ var _a, _b, _c, _d;
24227
+ const canvas = document.createElement('canvas');
24228
+ const settings = track.getSettings();
24229
+ const width = (_b = (_a = settings.width) !== null && _a !== void 0 ? _a : video.videoWidth) !== null && _b !== void 0 ? _b : 1280;
24230
+ const height = (_d = (_c = settings.height) !== null && _c !== void 0 ? _c : video.videoHeight) !== null && _d !== void 0 ? _d : 720;
24231
+ canvas.width = width;
24232
+ canvas.height = height;
24233
+ const ctx = canvas.getContext('2d');
24234
+ // Draw video frame to canvas
24235
+ ctx.drawImage(video, 0, 0);
24236
+ // Get image data and check if all pixels are black
24237
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
24238
+ const data = imageData.data;
24239
+ let isAllBlack = true;
24240
+ for (let i = 0; i < data.length; i += 4) {
24241
+ if (data[i] !== 0 || data[i + 1] !== 0 || data[i + 2] !== 0) {
24242
+ isAllBlack = false;
24243
+ break;
24244
+ }
24245
+ }
24246
+ if (isAllBlack) {
24247
+ this.appendError('camera appears to be producing only black frames');
24248
+ } else {
24249
+ this.appendMessage('received video frames');
24250
+ }
24251
+ resolve();
24252
+ }, 1000);
24253
+ };
24254
+ video.play();
24255
+ });
24256
+ video.remove();
24257
+ });
24258
+ }
23956
24259
  }
23957
24260
 
23958
24261
  class ReconnectCheck extends Checker {
@@ -24218,6 +24521,21 @@ class ConnectionCheck extends eventsExports.EventEmitter {
24218
24521
  return this.createAndRunCheck(PublishVideoCheck);
24219
24522
  });
24220
24523
  }
24524
+ checkConnectionProtocol() {
24525
+ return __awaiter(this, void 0, void 0, function* () {
24526
+ const info = yield this.createAndRunCheck(ConnectionProtocolCheck);
24527
+ if (info.data && 'protocol' in info.data) {
24528
+ const stats = info.data;
24529
+ this.options.protocol = stats.protocol;
24530
+ }
24531
+ return info;
24532
+ });
24533
+ }
24534
+ checkCloudRegion() {
24535
+ return __awaiter(this, void 0, void 0, function* () {
24536
+ return this.createAndRunCheck(CloudRegionCheck);
24537
+ });
24538
+ }
24221
24539
  }
24222
24540
 
24223
24541
  /**
@@ -24301,5 +24619,5 @@ function isFacingModeValue(item) {
24301
24619
  return item === undefined || allowedValues.includes(item);
24302
24620
  }
24303
24621
 
24304
- export { AudioPresets, BackupCodecPolicy, BaseKeyProvider, CheckStatus, Checker, ConnectionCheck, ConnectionError, ConnectionErrorReason, ConnectionQuality, ConnectionState, CriticalTimers, CryptorError, CryptorErrorReason, CryptorEvent, DataPacket_Kind, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, EngineEvent, ExternalE2EEKeyProvider, KeyHandlerEvent, KeyProviderEvent, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, LoggerNames, MediaDeviceFailure, _ as Mutex, NegotiationError, Participant, ParticipantEvent, ParticipantInfo_Kind as ParticipantKind, PublishDataError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, RpcError, ScreenSharePresets, SignalRequestError, SubscriptionError, Track, TrackEvent, TrackInvalidError, TrackPublication, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, compareVersions, createAudioAnalyser, createE2EEKey, createKeyMaterialFromBuffer, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, deriveKeys, detachTrack, facingModeFromDeviceLabel, facingModeFromLocalTrack, getBrowser, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, getLogger, importKey, isAudioTrack, isBackupCodec, isBrowserSupported, isE2EESupported, isInsertableStreamSupported, isLocalParticipant, isLocalTrack, isRemoteParticipant, isRemoteTrack, isScriptTransformSupported, isVideoFrame, isVideoTrack, needsRbspUnescaping, parseRbsp, protocolVersion, ratchet, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, version, videoCodecs, writeRbsp };
24622
+ export { AudioPresets, BackupCodecPolicy, BaseKeyProvider, CheckStatus, Checker, ConnectionCheck, ConnectionError, ConnectionErrorReason, ConnectionQuality, ConnectionState, CriticalTimers, CryptorError, CryptorErrorReason, CryptorEvent, DataPacket_Kind, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, EngineEvent, ExternalE2EEKeyProvider, KeyHandlerEvent, KeyProviderEvent, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, LoggerNames, MediaDeviceFailure, _ as Mutex, NegotiationError, Participant, ParticipantEvent, ParticipantInfo_Kind as ParticipantKind, PublishDataError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, RpcError, ScreenSharePresets, SignalRequestError, SubscriptionError, Track, TrackEvent, TrackInvalidError, TrackPublication, TrackType, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, compareVersions, createAudioAnalyser, createE2EEKey, createKeyMaterialFromBuffer, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, deriveKeys, detachTrack, facingModeFromDeviceLabel, facingModeFromLocalTrack, getBrowser, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, getLogger, importKey, isAudioTrack, isBackupCodec, isBrowserSupported, isE2EESupported, isInsertableStreamSupported, isLocalParticipant, isLocalTrack, isRemoteParticipant, isRemoteTrack, isScriptTransformSupported, isVideoFrame, isVideoTrack, needsRbspUnescaping, parseRbsp, protocolVersion, ratchet, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, version, videoCodecs, writeRbsp };
24305
24623
  //# sourceMappingURL=livekit-client.esm.mjs.map