@nuancelabs/js-sdk 0.2.0 → 0.2.2

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 (2) hide show
  1. package/index.js +83 -6
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -4,7 +4,7 @@ function isRecord(value) {
4
4
  }
5
5
 
6
6
  // src/core/sdk-version.ts
7
- var SDK_HEADER = `js/${"0.2.0"}`;
7
+ var SDK_HEADER = `js/${"0.2.2"}`;
8
8
 
9
9
  // src/utils.ts
10
10
  function normalizeHost(host) {
@@ -1167,6 +1167,15 @@ function microphonePublishOptions(resilient) {
1167
1167
  return;
1168
1168
  return { dtx: false, audioPreset: AudioPresets.speech };
1169
1169
  }
1170
+ var SCREEN_CAPTURE = {
1171
+ audio: false,
1172
+ resolution: { width: 1920, height: 1080, frameRate: 2 },
1173
+ contentHint: "detail"
1174
+ };
1175
+ var SCREEN_PUBLISH = {
1176
+ screenShareEncoding: { maxBitrate: 1500000, maxFramerate: 2 },
1177
+ simulcast: false
1178
+ };
1170
1179
 
1171
1180
  class LiveKitUserParticipant {
1172
1181
  room;
@@ -1174,6 +1183,8 @@ class LiveKitUserParticipant {
1174
1183
  onSpeakingChanged;
1175
1184
  onMicrophoneChanged;
1176
1185
  onCameraChanged;
1186
+ onScreenShareChanged;
1187
+ screenTrack;
1177
1188
  inputs = {
1178
1189
  audioinput: { changing: false, unavailable: false },
1179
1190
  videoinput: { changing: false, unavailable: false }
@@ -1188,7 +1199,7 @@ class LiveKitUserParticipant {
1188
1199
  lastAudioSource = null;
1189
1200
  _isSpeaking = false;
1190
1201
  speakingHandler = null;
1191
- constructor(room, onTracksChanged, onSpeakingChanged, onMicrophoneChanged, onCameraChanged = () => {}, initialCameraFacing, resilient = false) {
1202
+ constructor(room, onTracksChanged, onSpeakingChanged, onMicrophoneChanged, onCameraChanged = () => {}, initialCameraFacing, resilient = false, onScreenShareChanged = () => {}) {
1192
1203
  this.room = room;
1193
1204
  this.onTracksChanged = onTracksChanged;
1194
1205
  this.onSpeakingChanged = onSpeakingChanged;
@@ -1196,7 +1207,9 @@ class LiveKitUserParticipant {
1196
1207
  this.onCameraChanged = onCameraChanged;
1197
1208
  this.initialCameraFacing = initialCameraFacing;
1198
1209
  this.microphoneOptions = microphonePublishOptions(resilient);
1210
+ this.onScreenShareChanged = onScreenShareChanged;
1199
1211
  this.room.on(RoomEvent2.ActiveDeviceChanged, this.handleDeviceChanged);
1212
+ this.room.localParticipant.on(ParticipantEvent2.LocalTrackUnpublished, this.handleTrackUnpublished);
1200
1213
  }
1201
1214
  updateTracks() {
1202
1215
  let changed = false;
@@ -1357,9 +1370,14 @@ class LiveKitUserParticipant {
1357
1370
  this.notifyInput(kind);
1358
1371
  }
1359
1372
  };
1373
+ handleTrackUnpublished = (publication) => {
1374
+ if (!this.disposed && publication.source === Track5.Source.ScreenShare)
1375
+ this.onScreenShareChanged();
1376
+ };
1360
1377
  cleanup() {
1361
1378
  this.disposed = true;
1362
1379
  this.room.off(RoomEvent2.ActiveDeviceChanged, this.handleDeviceChanged);
1380
+ this.room.localParticipant.off(ParticipantEvent2.LocalTrackUnpublished, this.handleTrackUnpublished);
1363
1381
  if (this.inputs.audioinput.changing)
1364
1382
  this.inputTrack("audioinput")?.stop();
1365
1383
  if (this.inputs.videoinput.changing)
@@ -1437,10 +1455,44 @@ class LiveKitUserParticipant {
1437
1455
  get isVideoEnabled() {
1438
1456
  return this.room.localParticipant.isCameraEnabled;
1439
1457
  }
1458
+ async startScreenShare() {
1459
+ await this.setScreenShare(true);
1460
+ }
1461
+ async stopScreenShare() {
1462
+ const track = this.screenTrack;
1463
+ this.screenTrack = undefined;
1464
+ try {
1465
+ if (!this.disposed)
1466
+ await this.setScreenShare(false);
1467
+ } finally {
1468
+ track?.stop();
1469
+ }
1470
+ }
1471
+ get isScreenSharing() {
1472
+ return this.room.localParticipant.isScreenShareEnabled;
1473
+ }
1474
+ async setScreenShare(enabled) {
1475
+ if (this.disposed)
1476
+ throw new Error("Participant is disconnected");
1477
+ try {
1478
+ const publication = await this.room.localParticipant.setScreenShareEnabled(enabled, SCREEN_CAPTURE, SCREEN_PUBLISH);
1479
+ if (this.disposed)
1480
+ publication?.track?.stop();
1481
+ else if (enabled)
1482
+ this.screenTrack = publication?.track;
1483
+ } catch (error) {
1484
+ throw new MediaAccessError(error);
1485
+ } finally {
1486
+ if (!this.disposed)
1487
+ this.onScreenShareChanged();
1488
+ }
1489
+ }
1440
1490
  releaseDevices() {
1441
1491
  for (const pub of this.room.localParticipant.trackPublications.values()) {
1442
1492
  pub.track?.stop();
1443
1493
  }
1494
+ this.screenTrack?.stop();
1495
+ this.screenTrack = undefined;
1444
1496
  }
1445
1497
  startSpeakingDetection() {
1446
1498
  this.stopSpeakingDetection();
@@ -1601,7 +1653,7 @@ class LiveKitConversation {
1601
1653
  type: "speaking-changed",
1602
1654
  participant: "user",
1603
1655
  isSpeaking
1604
- }), () => this.emit({ type: "microphone-changed" }), () => this.emit({ type: "camera-changed" }), this.cameraFacing, this.resilient);
1656
+ }), () => this.emit({ type: "microphone-changed" }), () => this.emit({ type: "camera-changed" }), this.cameraFacing, this.resilient, () => this.emit({ type: "screen-share-changed" }));
1605
1657
  this._user.startSpeakingDetection();
1606
1658
  try {
1607
1659
  await this._user.unmuteAudio();
@@ -1677,7 +1729,7 @@ class LiveKitConversation {
1677
1729
  }
1678
1730
  onChange(handler) {
1679
1731
  return this.onEvent((event) => {
1680
- if (event.type !== "data-event")
1732
+ if (event.type !== "data-event" && event.type !== "screen-share-changed")
1681
1733
  handler(event);
1682
1734
  });
1683
1735
  }
@@ -2086,6 +2138,8 @@ var DEFAULT_HOST = "https://api.nuancelabs.ai";
2086
2138
  var QUEUE_HEARTBEAT_INTERVAL_MS = 5000;
2087
2139
  var HEARTBEAT_INTERVAL_MS = 5000;
2088
2140
  var ADMISSION_FAILED_BEATS = 6;
2141
+ var BUSY_RETRY_LIMIT_MS = 60000;
2142
+ var BUSY_RETRY_DEFAULT_MS = 3000;
2089
2143
  var LOST_WINDOW_MS = 30000;
2090
2144
  function createNuanceConversation(options = {}) {
2091
2145
  return new NuanceConversation(options);
@@ -2115,6 +2169,9 @@ function isLegalRequiredBody(body) {
2115
2169
  function isMaintenanceBody(body) {
2116
2170
  return isRecord(body) && body.code === "maintenance";
2117
2171
  }
2172
+ function isBusyBody(body) {
2173
+ return isRecord(body) && body.code === "busy";
2174
+ }
2118
2175
  function isCurfewBody(body) {
2119
2176
  return isRecord(body) && body.code === "policy_curfew";
2120
2177
  }
@@ -2212,7 +2269,7 @@ class NuanceConversation {
2212
2269
  request.personality = this.personality;
2213
2270
  if (this.variant)
2214
2271
  request.variant = this.variant;
2215
- conversation = await this.api.createConversation(request);
2272
+ conversation = await this.createWhenNotBusy(request, attempt);
2216
2273
  } catch (error) {
2217
2274
  if (error instanceof ApiError && error.status === 409) {
2218
2275
  throw new ActiveSessionError;
@@ -2316,7 +2373,7 @@ class NuanceConversation {
2316
2373
  }
2317
2374
  onChange(handler) {
2318
2375
  return this.onEvent((event) => {
2319
- if (event.type !== "data-event")
2376
+ if (event.type !== "data-event" && event.type !== "screen-share-changed")
2320
2377
  handler(event);
2321
2378
  });
2322
2379
  }
@@ -2354,6 +2411,26 @@ class NuanceConversation {
2354
2411
  resilientReconnect: this.resilient
2355
2412
  });
2356
2413
  }
2414
+ async createWhenNotBusy(request, attempt) {
2415
+ const giveUpAt = Date.now() + BUSY_RETRY_LIMIT_MS;
2416
+ for (;; ) {
2417
+ try {
2418
+ return await this.api.createConversation(request);
2419
+ } catch (error) {
2420
+ if (!(error instanceof ApiError) || error.status !== 503 || !isBusyBody(error.body) || attempt !== this.attempt) {
2421
+ throw error;
2422
+ }
2423
+ const waitMs = typeof error.body.retry_after_seconds === "number" ? error.body.retry_after_seconds * 1000 : BUSY_RETRY_DEFAULT_MS;
2424
+ if (Date.now() + waitMs > giveUpAt)
2425
+ throw error;
2426
+ if (this._state !== "queued")
2427
+ this.setState("queued");
2428
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
2429
+ if (attempt !== this.attempt)
2430
+ throw error;
2431
+ }
2432
+ }
2433
+ }
2357
2434
  startHeartbeat(conversationId, intervalMs) {
2358
2435
  this.stopHeartbeat();
2359
2436
  this.failedBeats = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuancelabs/js-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "JavaScript SDK for Nuance Labs conversation video AI avatar",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {