@vivix-ai/ivi-frontend-sdk 0.3.10 → 0.3.12

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.
package/README.md CHANGED
@@ -246,11 +246,12 @@ await runtime.start();
246
246
 
247
247
  会被串联的关键 spans 包括:
248
248
 
249
- - `ivi.session.create`:HTTP 创建 IVI Session
250
- - `ivi.runtime.create` / `ivi.runtime.start` / `ivi.runtime.stop`
251
- - `ivi.runtime.state_change`
252
- - `ivi.client.*`:底层 `@vivix-ai/ivi-sdk-ts` WebSocket 连接、发送、接收等 spans
253
- - `ivi.media.trtc.*` / `ivi.media.livekit.*`:远端视频可用、开始渲染、渲染完成等首屏关键点
249
+ - `sdk.session.create`:HTTP 创建 IVI Session
250
+ - `sdk.runtime.create` / `sdk.runtime.start` / `sdk.runtime.stop`
251
+ - `sdk.client.connect` / `sdk.client.connection`:底层 `@vivix-ai/ivi-sdk-ts` WebSocket 连接与聚合消息计数
252
+ - `sdk.media.trtc.join_room` / `sdk.media.livekit.join_room`:媒体进房耗时
253
+ - `sdk.media.trtc.waiting_remote_video_available` / `sdk.media.trtc.start_remote_video` / `sdk.media.trtc.remote_video_rendered`:TRTC 远端视频事件等待、开始渲染、渲染完成等首屏关键点
254
+ - `sdk.media.livekit.*`:LiveKit 远端视频可用等首屏关键点
254
255
 
255
256
  SDK span 不会写入 token、`user_sig`、TRTC `secret_key` 或完整业务 payload。低基数字段可以放在 `telemetry.attributes` 中统一附加。
256
257
 
package/dist/index.cjs CHANGED
@@ -1540,14 +1540,14 @@ var TrtcSourceManager = class {
1540
1540
  const client = TRTC.create();
1541
1541
  const onRemoteVideoAvailable = (event) => {
1542
1542
  this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u53EF\u7528 source=${session.sourceId} userId=${event.userId} streamType=${event.streamType}`);
1543
- recordIviTelemetrySpan(
1544
- "ivi.media.trtc.remote_video_available",
1545
- this.telemetry,
1546
- buildTrtcMediaTelemetryAttributes(session.sourceId, event.userId, event.streamType)
1547
- );
1543
+ const telemetryAttributes = buildTrtcMediaTelemetryAttributes(session.sourceId, event.userId, event.streamType);
1548
1544
  const remoteVideoKey = buildRemoteVideoKey(event.userId, String(event.streamType));
1549
1545
  session.remoteVideoStreams.add(remoteVideoKey);
1550
1546
  if (!session.hasEverReceivedRemoteVideo) {
1547
+ this.endRemoteVideoAvailableWait(session, {
1548
+ ...telemetryAttributes,
1549
+ "ivi.media.trtc.waiting_remote_video_available.result": "available"
1550
+ });
1551
1551
  session.hasEverReceivedRemoteVideo = true;
1552
1552
  for (const waiter of session.remoteVideoWaiters) {
1553
1553
  waiter(true);
@@ -1594,15 +1594,38 @@ var TrtcSourceManager = class {
1594
1594
  client.on(TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, onRemoteAudioAvailable);
1595
1595
  client.on(TRTC.EVENT.REMOTE_AUDIO_UNAVAILABLE, onRemoteAudioUnavailable);
1596
1596
  this.log("info", `\u6B63\u5728\u8FDB\u623F source=${session.sourceId} room=${roomId} sdkAppId=${sdkAppId} userId=${userId}`);
1597
- await client.enterRoom({
1598
- sdkAppId,
1599
- userId,
1600
- userSig,
1601
- scene: TRTC.TYPE.SCENE_LIVE,
1602
- role: TRTC.TYPE.ROLE_AUDIENCE,
1603
- autoReceiveAudio: true,
1604
- ...isStringRoomId ? { strRoomId: roomId } : { roomId: Number(roomId) }
1605
- });
1597
+ session.remoteVideoAvailableSpan = startIviTelemetrySpan(
1598
+ "sdk.media.trtc.waiting_remote_video_available",
1599
+ this.telemetry,
1600
+ buildTrtcWaitingRemoteVideoTelemetryAttributes(session.sourceId, roomId, sdkAppId, isStringRoomId)
1601
+ );
1602
+ const joinRoomSpan = startIviTelemetrySpan(
1603
+ "sdk.media.trtc.join_room",
1604
+ this.telemetry,
1605
+ buildTrtcJoinRoomTelemetryAttributes(session.sourceId, roomId, userId, sdkAppId, isStringRoomId)
1606
+ );
1607
+ try {
1608
+ await client.enterRoom({
1609
+ sdkAppId,
1610
+ userId,
1611
+ userSig,
1612
+ scene: TRTC.TYPE.SCENE_LIVE,
1613
+ role: TRTC.TYPE.ROLE_AUDIENCE,
1614
+ autoReceiveAudio: true,
1615
+ ...isStringRoomId ? { strRoomId: roomId } : { roomId: Number(roomId) }
1616
+ });
1617
+ endIviTelemetrySpan(joinRoomSpan.span, {
1618
+ "ivi.media.trtc.join_room.result": "connected"
1619
+ });
1620
+ } catch (error) {
1621
+ this.failRemoteVideoAvailableWait(session, error, {
1622
+ "ivi.media.trtc.waiting_remote_video_available.result": "join_failed"
1623
+ });
1624
+ failIviTelemetrySpan(joinRoomSpan.span, error, {
1625
+ "ivi.media.trtc.join_room.result": "failed"
1626
+ });
1627
+ throw error;
1628
+ }
1606
1629
  session.TRTC = TRTC;
1607
1630
  session.client = client;
1608
1631
  session.status = "connected";
@@ -1614,6 +1637,9 @@ var TrtcSourceManager = class {
1614
1637
  });
1615
1638
  await this.applyAudioPolicy(session);
1616
1639
  })().catch((error) => {
1640
+ this.failRemoteVideoAvailableWait(session, error, {
1641
+ "ivi.media.trtc.waiting_remote_video_available.result": "connect_failed"
1642
+ });
1617
1643
  session.status = "error";
1618
1644
  session.error = error instanceof Error ? error.message : String(error);
1619
1645
  this.log("error", `\u8FDE\u63A5\u5931\u8D25 source=${session.sourceId} error=${session.error}`);
@@ -1658,7 +1684,7 @@ var TrtcSourceManager = class {
1658
1684
  streamType
1659
1685
  );
1660
1686
  const telemetrySpan = startIviTelemetrySpan(
1661
- "ivi.media.trtc.start_remote_video",
1687
+ "sdk.media.trtc.start_remote_video",
1662
1688
  this.telemetry,
1663
1689
  telemetryAttributes
1664
1690
  );
@@ -1672,7 +1698,7 @@ var TrtcSourceManager = class {
1672
1698
  });
1673
1699
  endIviTelemetrySpan(telemetrySpan.span);
1674
1700
  recordIviTelemetrySpan(
1675
- "ivi.media.trtc.remote_video_rendered",
1701
+ "sdk.media.trtc.remote_video_rendered",
1676
1702
  this.telemetry,
1677
1703
  telemetryAttributes
1678
1704
  );
@@ -1736,6 +1762,22 @@ var TrtcSourceManager = class {
1736
1762
  this.log("warn", `TRTC AIDenoiser \u5F00\u542F\u5931\u8D25 source=${session.sourceId}`, error);
1737
1763
  }
1738
1764
  }
1765
+ endRemoteVideoAvailableWait(session, attributes) {
1766
+ const waitingSpan = session.remoteVideoAvailableSpan;
1767
+ if (!waitingSpan) {
1768
+ return;
1769
+ }
1770
+ session.remoteVideoAvailableSpan = void 0;
1771
+ endIviTelemetrySpan(waitingSpan.span, attributes);
1772
+ }
1773
+ failRemoteVideoAvailableWait(session, error, attributes) {
1774
+ const waitingSpan = session.remoteVideoAvailableSpan;
1775
+ if (!waitingSpan) {
1776
+ return;
1777
+ }
1778
+ session.remoteVideoAvailableSpan = void 0;
1779
+ failIviTelemetrySpan(waitingSpan.span, error, attributes);
1780
+ }
1739
1781
  /**
1740
1782
  * 销毁单个 source 会话:
1741
1783
  * 解绑事件、停止远端视频、退出房间并销毁客户端。
@@ -1749,6 +1791,9 @@ var TrtcSourceManager = class {
1749
1791
  session.remoteVideoStreams.clear();
1750
1792
  session.status = "idle";
1751
1793
  session.error = void 0;
1794
+ this.endRemoteVideoAvailableWait(session, {
1795
+ "ivi.media.trtc.waiting_remote_video_available.result": "cancelled"
1796
+ });
1752
1797
  for (const waiter of session.remoteVideoWaiters) {
1753
1798
  waiter(false);
1754
1799
  }
@@ -1908,6 +1953,23 @@ function buildTrtcMediaTelemetryAttributes(sourceId, userId, streamType) {
1908
1953
  "ivi.trtc.stream_type": String(streamType)
1909
1954
  };
1910
1955
  }
1956
+ function buildTrtcJoinRoomTelemetryAttributes(sourceId, roomId, userId, sdkAppId, isStringRoomId) {
1957
+ return {
1958
+ "ivi.source.id": sourceId,
1959
+ "ivi.trtc.room_id": roomId,
1960
+ "ivi.trtc.user_id": userId,
1961
+ "ivi.trtc.sdk_app_id": sdkAppId,
1962
+ "ivi.trtc.room_id.is_string": isStringRoomId
1963
+ };
1964
+ }
1965
+ function buildTrtcWaitingRemoteVideoTelemetryAttributes(sourceId, roomId, sdkAppId, isStringRoomId) {
1966
+ return {
1967
+ "ivi.source.id": sourceId,
1968
+ "ivi.trtc.room_id": roomId,
1969
+ "ivi.trtc.sdk_app_id": sdkAppId,
1970
+ "ivi.trtc.room_id.is_string": isStringRoomId
1971
+ };
1972
+ }
1911
1973
  function parseRemoteVideoKey(key) {
1912
1974
  const separatorIndex = key.indexOf("::");
1913
1975
  if (separatorIndex < 0) {
@@ -2196,7 +2258,7 @@ var LivekitSourceManager = class {
2196
2258
  });
2197
2259
  if (kind === "video" && !session.hasEverReceivedRemoteVideo) {
2198
2260
  recordIviTelemetrySpan(
2199
- "ivi.media.livekit.remote_video_available",
2261
+ "sdk.media.livekit.remote_video_available",
2200
2262
  this.telemetry,
2201
2263
  {
2202
2264
  "ivi.source.id": session.sourceId,
@@ -2241,7 +2303,22 @@ var LivekitSourceManager = class {
2241
2303
  "info",
2242
2304
  `\u6B63\u5728\u8FDB\u623F source=${session.sourceId} room=${describeLivekitRoom(session.livekit)} ws=${session.livekit.ws_url}`
2243
2305
  );
2244
- await room.connect(session.livekit.ws_url, session.livekit.token);
2306
+ const joinRoomSpan = startIviTelemetrySpan(
2307
+ "sdk.media.livekit.join_room",
2308
+ this.telemetry,
2309
+ buildLivekitJoinRoomTelemetryAttributes(session)
2310
+ );
2311
+ try {
2312
+ await room.connect(session.livekit.ws_url, session.livekit.token);
2313
+ endIviTelemetrySpan(joinRoomSpan.span, {
2314
+ "ivi.media.livekit.join_room.result": "connected"
2315
+ });
2316
+ } catch (error) {
2317
+ failIviTelemetrySpan(joinRoomSpan.span, error, {
2318
+ "ivi.media.livekit.join_room.result": "failed"
2319
+ });
2320
+ throw error;
2321
+ }
2245
2322
  session.room = room;
2246
2323
  session.livekitModule = livekitModule;
2247
2324
  session.status = "connected";
@@ -2376,6 +2453,13 @@ var LivekitSourceManager = class {
2376
2453
  function buildRemoteTrackKey(identity, trackId) {
2377
2454
  return `${identity}::${trackId}`;
2378
2455
  }
2456
+ function buildLivekitJoinRoomTelemetryAttributes(session) {
2457
+ return {
2458
+ "ivi.source.id": session.sourceId,
2459
+ "ivi.livekit.room": session.livekit.room,
2460
+ "ivi.livekit.identity": session.livekit.identity
2461
+ };
2462
+ }
2379
2463
  function enforceContainMedia2(element) {
2380
2464
  element.style.setProperty("width", "100%", "important");
2381
2465
  element.style.setProperty("height", "100%", "important");
@@ -2471,9 +2555,9 @@ var IviRuntimeCoordinator = class {
2471
2555
  * 启动后状态会从 idle/stopped 进入 connecting,连接成功后由事件驱动进入后续状态。
2472
2556
  */
2473
2557
  async start() {
2474
- const telemetrySpan = startIviTelemetrySpan("ivi.runtime.start", this.config.telemetry, {
2558
+ const telemetrySpan = startIviTelemetrySpan("sdk.runtime.start", this.config.telemetry, {
2475
2559
  "ivi.runtime.status": this.state.status,
2476
- "ivi.session.id": this.state.session?.id
2560
+ "session.id": this.state.session?.id
2477
2561
  });
2478
2562
  try {
2479
2563
  this.setState({
@@ -2484,13 +2568,13 @@ var IviRuntimeCoordinator = class {
2484
2568
  await this.client.connect();
2485
2569
  endIviTelemetrySpan(telemetrySpan.span, {
2486
2570
  "ivi.runtime.status": this.state.status,
2487
- "ivi.session.id": this.state.session?.id
2571
+ "session.id": this.state.session?.id
2488
2572
  });
2489
2573
  } catch (error) {
2490
2574
  this.stop();
2491
2575
  failIviTelemetrySpan(telemetrySpan.span, error, {
2492
2576
  "ivi.runtime.status": this.state.status,
2493
- "ivi.session.id": this.state.session?.id
2577
+ "session.id": this.state.session?.id
2494
2578
  });
2495
2579
  throw error;
2496
2580
  }
@@ -2499,9 +2583,9 @@ var IviRuntimeCoordinator = class {
2499
2583
  * 停止协调器并断开实时连接,同时重置运行态数据为 stopped。
2500
2584
  */
2501
2585
  stop() {
2502
- const telemetrySpan = startIviTelemetrySpan("ivi.runtime.stop", this.config.telemetry, {
2586
+ const telemetrySpan = startIviTelemetrySpan("sdk.runtime.stop", this.config.telemetry, {
2503
2587
  "ivi.runtime.status": this.state.status,
2504
- "ivi.session.id": this.state.session?.id
2588
+ "session.id": this.state.session?.id
2505
2589
  });
2506
2590
  try {
2507
2591
  this.dispatcher.stop();
@@ -2513,7 +2597,7 @@ var IviRuntimeCoordinator = class {
2513
2597
  } catch (error) {
2514
2598
  failIviTelemetrySpan(telemetrySpan.span, error, {
2515
2599
  "ivi.runtime.status": this.state.status,
2516
- "ivi.session.id": this.state.session?.id
2600
+ "session.id": this.state.session?.id
2517
2601
  });
2518
2602
  throw error;
2519
2603
  }
@@ -2814,16 +2898,7 @@ var IviRuntimeCoordinator = class {
2814
2898
  if (this.state.status === composedNextState.status && this.state.session === composedNextState.session && this.state.stage === composedNextState.stage && this.state.tracks === composedNextState.tracks && this.state.sources === composedNextState.sources && this.state.streams === composedNextState.streams && this.state.conversationItems === composedNextState.conversationItems && this.state.conversations === composedNextState.conversations && isSameBootstrapState(this.state.bootstrap, composedNextState.bootstrap)) {
2815
2899
  return;
2816
2900
  }
2817
- const previousState = this.state;
2818
2901
  this.state = composedNextState;
2819
- if (previousState.status !== composedNextState.status) {
2820
- recordIviTelemetrySpan("ivi.runtime.state_change", this.config.telemetry, {
2821
- "ivi.runtime.status.previous": previousState.status,
2822
- "ivi.runtime.status.next": composedNextState.status,
2823
- "ivi.runtime.state_change.reason": "state_update",
2824
- "ivi.session.id": composedNextState.session?.id ?? previousState.session?.id
2825
- });
2826
- }
2827
2902
  this.stateListeners.forEach((listener) => listener(this.state));
2828
2903
  }
2829
2904
  composeBootstrapState(nextState) {
@@ -3107,7 +3182,7 @@ async function createIVISession(request, options) {
3107
3182
  throw new Error("fetch is not available. Pass options.fetch to createIVISession.");
3108
3183
  }
3109
3184
  const telemetrySpan = startIviTelemetrySpan(
3110
- "ivi.session.create",
3185
+ "sdk.session.create",
3111
3186
  options.telemetry,
3112
3187
  buildCreateSessionTelemetryAttributes(request)
3113
3188
  );
@@ -3134,7 +3209,7 @@ async function createIVISession(request, options) {
3134
3209
  const result = normalizeCreateIVISessionResponse(responseBody, request, requestBody);
3135
3210
  endIviTelemetrySpan(telemetrySpan.span, {
3136
3211
  "http.response.status_code": response.status,
3137
- "ivi.session.id": result.iviSessionId,
3212
+ "session.id": result.iviSessionId,
3138
3213
  "ivi.prebuild.playback.kind": result.prebuiltPlayback?.kind
3139
3214
  });
3140
3215
  return result;
@@ -3146,14 +3221,18 @@ async function createIVISession(request, options) {
3146
3221
  }
3147
3222
  }
3148
3223
  function createRuntimeFromSession(session, options = {}) {
3149
- const telemetrySpan = startIviTelemetrySpan("ivi.runtime.create", options.telemetry, {
3150
- "ivi.session.id": session.iviSessionId,
3151
- "ivi.prebuild.playback.kind": session.prebuiltPlayback?.kind
3224
+ const fastStartEnabled = options.fastStart !== false;
3225
+ const fastStartApplied = fastStartEnabled && Boolean(session.prebuiltPlayback);
3226
+ const telemetrySpan = startIviTelemetrySpan("sdk.runtime.create", options.telemetry, {
3227
+ "session.id": session.iviSessionId,
3228
+ "ivi.prebuild.playback.kind": session.prebuiltPlayback?.kind,
3229
+ "ivi.fast_start.enabled": fastStartEnabled,
3230
+ "ivi.fast_start.applied": fastStartApplied
3152
3231
  });
3153
3232
  try {
3154
3233
  const websocketUrl = buildWebSocketUrl(session.endpoint, options.websocket);
3155
3234
  const childTelemetry = withIviTelemetryContext(options.telemetry, telemetrySpan.context, {
3156
- "ivi.session.id": session.iviSessionId
3235
+ "session.id": session.iviSessionId
3157
3236
  });
3158
3237
  const clientTelemetry = mergeIviTelemetryOptions(
3159
3238
  options.clientConfig?.telemetry,
@@ -3177,7 +3256,7 @@ function createRuntimeFromSession(session, options = {}) {
3177
3256
  ...options.runtimeConfig ?? {},
3178
3257
  telemetry: runtimeTelemetry
3179
3258
  });
3180
- if (options.fastStart !== false && session.prebuiltPlayback) {
3259
+ if (fastStartEnabled && session.prebuiltPlayback) {
3181
3260
  runtime.setBootstrapSource(toBootstrapSource(session.prebuiltPlayback, options));
3182
3261
  }
3183
3262
  endIviTelemetrySpan(telemetrySpan.span);
@@ -4805,7 +4884,7 @@ function IVIHlsVideo(props) {
4805
4884
  const onVideoError = () => {
4806
4885
  const el = videoRef.current;
4807
4886
  const mediaErr = el?.error;
4808
- recordVideoTelemetry("ivi.media.video.error", telemetry, sourceId, "hls", mediaErr);
4887
+ recordVideoTelemetry("sdk.media.video.error", telemetry, sourceId, "hls", mediaErr);
4809
4888
  emitHlsLog(onLog, "warn", "<video> \u5143\u7D20\u62A5\u9519", {
4810
4889
  code: mediaErr?.code,
4811
4890
  message: mediaErr?.message,
@@ -4816,7 +4895,7 @@ function IVIHlsVideo(props) {
4816
4895
  };
4817
4896
  const onVideoStalled = () => {
4818
4897
  recordVideoTelemetry(
4819
- "ivi.media.video.stalled",
4898
+ "sdk.media.video.stalled",
4820
4899
  telemetry,
4821
4900
  sourceId,
4822
4901
  "hls",
@@ -5212,7 +5291,7 @@ function SlotVideo(props) {
5212
5291
  }, [props.paused]);
5213
5292
  const onError = (event) => {
5214
5293
  recordPlainVideoTelemetry(
5215
- "ivi.media.video.error",
5294
+ "sdk.media.video.error",
5216
5295
  props.telemetry,
5217
5296
  props.sourceId,
5218
5297
  event.currentTarget.error
@@ -5221,7 +5300,7 @@ function SlotVideo(props) {
5221
5300
  };
5222
5301
  const onStalled = (event) => {
5223
5302
  recordPlainVideoTelemetry(
5224
- "ivi.media.video.stalled",
5303
+ "sdk.media.video.stalled",
5225
5304
  props.telemetry,
5226
5305
  props.sourceId,
5227
5306
  new Error("<video> stalled")