@vivix-ai/ivi-frontend-sdk 0.3.9 → 0.3.11

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/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ReceiveConversationItemAddedEvent, ReceiveConversationItemDoneEvent, ReceiveResponseCreatedEvent, IviClient, ReceiveSessionCreatedEvent, ReceiveSessionEndedEvent, ReceiveSessionStageGetResponseEvent, ReceiveSessionStageUpdatedEvent, ReceiveSessionTrackCreatedEvent, ReceiveSessionTrackDeletedEvent, ReceiveSessionTrackTookEvent, ReceiveSessionTrackCuedEvent, ReceiveSessionTrackNextSetEvent, ReceiveSessionTracksListResponseEvent, ReceiveSessionSourceCreatedEvent, ReceiveSessionSourceReadyEvent, ReceiveSessionSourceFailedEvent, ReceiveSessionSourceDeletedEvent, ReceiveSessionSourcePreloadEvent, ReceiveSessionSourceClearPreloadEvent, ReceiveSessionSourcesListResponseEvent, ReceiveSessionSourcePlaybackCompletedEvent, ReceiveSessionStreamCreatedEvent, ReceiveSessionStreamStartedEvent, ReceiveSessionStreamEndedEvent, ReceiveSessionStreamFailedEvent, ReceiveSessionStreamDeletedEvent, ReceiveSessionStreamsListResponseEvent, ReceiveConversationListResponseEvent, ReceiveResponseOutputTextDeltaEvent, ReceiveResponseOutputTextDoneEvent, ReceiveResponseOutputAudioTranscriptDeltaEvent, ReceiveResponseOutputAudioTranscriptDoneEvent, ReceiveResponseDoneEvent } from '@vivix-ai/ivi-sdk-ts';
2
+ import { propagation, SpanKind, trace, SpanStatusCode, context } from '@opentelemetry/api';
2
3
  import { WebSocketTransport } from '@vivix-ai/ivi-sdk-ts/transports/websocket';
3
4
  import { createContext, useState, useEffect, useMemo, useRef, useContext, useCallback, useId } from 'react';
4
5
  import { jsx, jsxs } from 'react/jsx-runtime';
@@ -1139,6 +1140,94 @@ var ConversationManager = class {
1139
1140
  ];
1140
1141
  }
1141
1142
  };
1143
+ var DEFAULT_TRACER_NAME = "@vivix-ai/ivi-frontend-sdk";
1144
+ function getIviTelemetryTracer(options) {
1145
+ return options?.tracer ?? trace.getTracer(DEFAULT_TRACER_NAME);
1146
+ }
1147
+ function getIviTelemetryContext(options) {
1148
+ return options?.context ?? context.active();
1149
+ }
1150
+ function startIviTelemetrySpan(name, options, attributes) {
1151
+ const tracer = getIviTelemetryTracer(options);
1152
+ const parentContext = getIviTelemetryContext(options);
1153
+ const span = tracer.startSpan(
1154
+ name,
1155
+ {
1156
+ kind: SpanKind.INTERNAL,
1157
+ attributes: mergeIviTelemetryAttributes(options?.attributes, attributes)
1158
+ },
1159
+ parentContext
1160
+ );
1161
+ return {
1162
+ span,
1163
+ context: trace.setSpan(parentContext, span)
1164
+ };
1165
+ }
1166
+ function endIviTelemetrySpan(span, attributes) {
1167
+ const cleaned = cleanIviTelemetryAttributes(attributes);
1168
+ if (cleaned) {
1169
+ span.setAttributes(cleaned);
1170
+ }
1171
+ span.setStatus({ code: SpanStatusCode.OK });
1172
+ span.end();
1173
+ }
1174
+ function failIviTelemetrySpan(span, error, attributes) {
1175
+ const cleaned = cleanIviTelemetryAttributes(attributes);
1176
+ if (cleaned) {
1177
+ span.setAttributes(cleaned);
1178
+ }
1179
+ recordIviTelemetryError(span, error);
1180
+ span.end();
1181
+ }
1182
+ function recordIviTelemetrySpan(name, options, attributes, error) {
1183
+ const { span } = startIviTelemetrySpan(name, options, attributes);
1184
+ if (error === void 0) {
1185
+ span.setStatus({ code: SpanStatusCode.OK });
1186
+ } else {
1187
+ recordIviTelemetryError(span, error);
1188
+ }
1189
+ span.end();
1190
+ }
1191
+ function withIviTelemetryContext(options, context, attributes) {
1192
+ return {
1193
+ ...options,
1194
+ context,
1195
+ attributes: mergeIviTelemetryAttributes(options?.attributes, attributes)
1196
+ };
1197
+ }
1198
+ function mergeIviTelemetryAttributes(base, next) {
1199
+ return cleanIviTelemetryAttributes({
1200
+ ...base ?? {},
1201
+ ...next ?? {}
1202
+ });
1203
+ }
1204
+ function cleanIviTelemetryAttributes(attributes) {
1205
+ const cleaned = {};
1206
+ Object.entries(attributes ?? {}).forEach(([key, value]) => {
1207
+ if (isIviTelemetryAttributeValue(value)) {
1208
+ cleaned[key] = value;
1209
+ }
1210
+ });
1211
+ return Object.keys(cleaned).length > 0 ? cleaned : void 0;
1212
+ }
1213
+ function recordIviTelemetryError(span, error) {
1214
+ const message = error instanceof Error ? error.message : String(error);
1215
+ span.recordException(error instanceof Error ? error : message);
1216
+ span.setStatus({
1217
+ code: SpanStatusCode.ERROR,
1218
+ message
1219
+ });
1220
+ }
1221
+ function isIviTelemetryAttributeValue(value) {
1222
+ if (typeof value === "string" || typeof value === "boolean") return true;
1223
+ if (typeof value === "number") return Number.isFinite(value);
1224
+ if (!Array.isArray(value)) return false;
1225
+ const nonNullValues = value.filter((item) => item !== null && item !== void 0);
1226
+ if (nonNullValues.length === 0) return true;
1227
+ const firstType = typeof nonNullValues[0];
1228
+ if (firstType !== "string" && firstType !== "number" && firstType !== "boolean") return false;
1229
+ return nonNullValues.every((item) => typeof item === firstType && (typeof item !== "number" || Number.isFinite(item)));
1230
+ }
1142
1231
 
1143
1232
  // src/runtime/managers/trtc-source-manager.ts
1144
1233
  var TAG = "[IVI-TRTC]";
@@ -1149,10 +1238,11 @@ var DEFAULT_DENOISER_OPTIONS = {
1149
1238
  mode: "normal"
1150
1239
  };
1151
1240
  var TrtcSourceManager = class {
1152
- constructor(onLog, onTrtcEvent, aiDenoiser) {
1241
+ constructor(onLog, onTrtcEvent, aiDenoiser, telemetry) {
1153
1242
  this.onLog = onLog;
1154
1243
  this.onTrtcEvent = onTrtcEvent;
1155
1244
  this.aiDenoiser = aiDenoiser;
1245
+ this.telemetry = telemetry;
1156
1246
  this.sessions = /* @__PURE__ */ new Map();
1157
1247
  this.sourceSessionKeys = /* @__PURE__ */ new Map();
1158
1248
  this.listeners = /* @__PURE__ */ new Map();
@@ -1448,9 +1538,14 @@ var TrtcSourceManager = class {
1448
1538
  const client = TRTC.create();
1449
1539
  const onRemoteVideoAvailable = (event) => {
1450
1540
  this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u53EF\u7528 source=${session.sourceId} userId=${event.userId} streamType=${event.streamType}`);
1541
+ const telemetryAttributes = buildTrtcMediaTelemetryAttributes(session.sourceId, event.userId, event.streamType);
1451
1542
  const remoteVideoKey = buildRemoteVideoKey(event.userId, String(event.streamType));
1452
1543
  session.remoteVideoStreams.add(remoteVideoKey);
1453
1544
  if (!session.hasEverReceivedRemoteVideo) {
1545
+ this.endRemoteVideoAvailableWait(session, {
1546
+ ...telemetryAttributes,
1547
+ "ivi.media.trtc.waiting_remote_video_available.result": "available"
1548
+ });
1454
1549
  session.hasEverReceivedRemoteVideo = true;
1455
1550
  for (const waiter of session.remoteVideoWaiters) {
1456
1551
  waiter(true);
@@ -1497,15 +1592,38 @@ var TrtcSourceManager = class {
1497
1592
  client.on(TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, onRemoteAudioAvailable);
1498
1593
  client.on(TRTC.EVENT.REMOTE_AUDIO_UNAVAILABLE, onRemoteAudioUnavailable);
1499
1594
  this.log("info", `\u6B63\u5728\u8FDB\u623F source=${session.sourceId} room=${roomId} sdkAppId=${sdkAppId} userId=${userId}`);
1500
- await client.enterRoom({
1501
- sdkAppId,
1502
- userId,
1503
- userSig,
1504
- scene: TRTC.TYPE.SCENE_LIVE,
1505
- role: TRTC.TYPE.ROLE_AUDIENCE,
1506
- autoReceiveAudio: true,
1507
- ...isStringRoomId ? { strRoomId: roomId } : { roomId: Number(roomId) }
1508
- });
1595
+ session.remoteVideoAvailableSpan = startIviTelemetrySpan(
1596
+ "sdk.media.trtc.waiting_remote_video_available",
1597
+ this.telemetry,
1598
+ buildTrtcWaitingRemoteVideoTelemetryAttributes(session.sourceId, roomId, sdkAppId, isStringRoomId)
1599
+ );
1600
+ const joinRoomSpan = startIviTelemetrySpan(
1601
+ "sdk.media.trtc.join_room",
1602
+ this.telemetry,
1603
+ buildTrtcJoinRoomTelemetryAttributes(session.sourceId, roomId, userId, sdkAppId, isStringRoomId)
1604
+ );
1605
+ try {
1606
+ await client.enterRoom({
1607
+ sdkAppId,
1608
+ userId,
1609
+ userSig,
1610
+ scene: TRTC.TYPE.SCENE_LIVE,
1611
+ role: TRTC.TYPE.ROLE_AUDIENCE,
1612
+ autoReceiveAudio: true,
1613
+ ...isStringRoomId ? { strRoomId: roomId } : { roomId: Number(roomId) }
1614
+ });
1615
+ endIviTelemetrySpan(joinRoomSpan.span, {
1616
+ "ivi.media.trtc.join_room.result": "connected"
1617
+ });
1618
+ } catch (error) {
1619
+ this.failRemoteVideoAvailableWait(session, error, {
1620
+ "ivi.media.trtc.waiting_remote_video_available.result": "join_failed"
1621
+ });
1622
+ failIviTelemetrySpan(joinRoomSpan.span, error, {
1623
+ "ivi.media.trtc.join_room.result": "failed"
1624
+ });
1625
+ throw error;
1626
+ }
1509
1627
  session.TRTC = TRTC;
1510
1628
  session.client = client;
1511
1629
  session.status = "connected";
@@ -1517,6 +1635,9 @@ var TrtcSourceManager = class {
1517
1635
  });
1518
1636
  await this.applyAudioPolicy(session);
1519
1637
  })().catch((error) => {
1638
+ this.failRemoteVideoAvailableWait(session, error, {
1639
+ "ivi.media.trtc.waiting_remote_video_available.result": "connect_failed"
1640
+ });
1520
1641
  session.status = "error";
1521
1642
  session.error = error instanceof Error ? error.message : String(error);
1522
1643
  this.log("error", `\u8FDE\u63A5\u5931\u8D25 source=${session.sourceId} error=${session.error}`);
@@ -1555,6 +1676,16 @@ var TrtcSourceManager = class {
1555
1676
  return;
1556
1677
  }
1557
1678
  binding.startingVideoKeys.add(remoteVideoKey);
1679
+ const telemetryAttributes = buildTrtcMediaTelemetryAttributes(
1680
+ binding.sourceId,
1681
+ userId,
1682
+ streamType
1683
+ );
1684
+ const telemetrySpan = startIviTelemetrySpan(
1685
+ "sdk.media.trtc.start_remote_video",
1686
+ this.telemetry,
1687
+ telemetryAttributes
1688
+ );
1558
1689
  try {
1559
1690
  this.log("info", `\u5F00\u59CB\u6E32\u67D3\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} view=${remoteVideoKey} userId=${userId} streamType=${streamType}`);
1560
1691
  await client.startRemoteVideo({
@@ -1563,14 +1694,24 @@ var TrtcSourceManager = class {
1563
1694
  view: binding.container,
1564
1695
  option: { fillMode: "contain" }
1565
1696
  });
1697
+ endIviTelemetrySpan(telemetrySpan.span);
1698
+ recordIviTelemetrySpan(
1699
+ "sdk.media.trtc.remote_video_rendered",
1700
+ this.telemetry,
1701
+ telemetryAttributes
1702
+ );
1566
1703
  this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5B8C\u6210 source=${binding.sourceId} userId=${userId} streamType=${streamType}`);
1567
1704
  binding.startedVideoKeys.add(remoteVideoKey);
1568
1705
  enforceContainMedia(binding.container);
1569
1706
  } catch (error) {
1570
1707
  if (error instanceof Error && /already started|OPERATION_ABORT/i.test(error.message)) {
1571
1708
  binding.startedVideoKeys.add(remoteVideoKey);
1709
+ endIviTelemetrySpan(telemetrySpan.span, {
1710
+ "ivi.media.trtc.start_remote_video.result": "already_started"
1711
+ });
1572
1712
  return;
1573
1713
  }
1714
+ failIviTelemetrySpan(telemetrySpan.span, error);
1574
1715
  this.log("warn", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5931\u8D25 userId=${userId} streamType=${streamType}`, error);
1575
1716
  } finally {
1576
1717
  binding.startingVideoKeys.delete(remoteVideoKey);
@@ -1619,6 +1760,22 @@ var TrtcSourceManager = class {
1619
1760
  this.log("warn", `TRTC AIDenoiser \u5F00\u542F\u5931\u8D25 source=${session.sourceId}`, error);
1620
1761
  }
1621
1762
  }
1763
+ endRemoteVideoAvailableWait(session, attributes) {
1764
+ const waitingSpan = session.remoteVideoAvailableSpan;
1765
+ if (!waitingSpan) {
1766
+ return;
1767
+ }
1768
+ session.remoteVideoAvailableSpan = void 0;
1769
+ endIviTelemetrySpan(waitingSpan.span, attributes);
1770
+ }
1771
+ failRemoteVideoAvailableWait(session, error, attributes) {
1772
+ const waitingSpan = session.remoteVideoAvailableSpan;
1773
+ if (!waitingSpan) {
1774
+ return;
1775
+ }
1776
+ session.remoteVideoAvailableSpan = void 0;
1777
+ failIviTelemetrySpan(waitingSpan.span, error, attributes);
1778
+ }
1622
1779
  /**
1623
1780
  * 销毁单个 source 会话:
1624
1781
  * 解绑事件、停止远端视频、退出房间并销毁客户端。
@@ -1632,6 +1789,9 @@ var TrtcSourceManager = class {
1632
1789
  session.remoteVideoStreams.clear();
1633
1790
  session.status = "idle";
1634
1791
  session.error = void 0;
1792
+ this.endRemoteVideoAvailableWait(session, {
1793
+ "ivi.media.trtc.waiting_remote_video_available.result": "cancelled"
1794
+ });
1635
1795
  for (const waiter of session.remoteVideoWaiters) {
1636
1796
  waiter(false);
1637
1797
  }
@@ -1784,6 +1944,30 @@ function getAIDenoiserModeValue(mode) {
1784
1944
  function buildRemoteVideoKey(userId, streamType) {
1785
1945
  return `${userId}::${streamType}`;
1786
1946
  }
1947
+ function buildTrtcMediaTelemetryAttributes(sourceId, userId, streamType) {
1948
+ return {
1949
+ "ivi.source.id": sourceId,
1950
+ "ivi.trtc.user_id": userId,
1951
+ "ivi.trtc.stream_type": String(streamType)
1952
+ };
1953
+ }
1954
+ function buildTrtcJoinRoomTelemetryAttributes(sourceId, roomId, userId, sdkAppId, isStringRoomId) {
1955
+ return {
1956
+ "ivi.source.id": sourceId,
1957
+ "ivi.trtc.room_id": roomId,
1958
+ "ivi.trtc.user_id": userId,
1959
+ "ivi.trtc.sdk_app_id": sdkAppId,
1960
+ "ivi.trtc.room_id.is_string": isStringRoomId
1961
+ };
1962
+ }
1963
+ function buildTrtcWaitingRemoteVideoTelemetryAttributes(sourceId, roomId, sdkAppId, isStringRoomId) {
1964
+ return {
1965
+ "ivi.source.id": sourceId,
1966
+ "ivi.trtc.room_id": roomId,
1967
+ "ivi.trtc.sdk_app_id": sdkAppId,
1968
+ "ivi.trtc.room_id.is_string": isStringRoomId
1969
+ };
1970
+ }
1787
1971
  function parseRemoteVideoKey(key) {
1788
1972
  const separatorIndex = key.indexOf("::");
1789
1973
  if (separatorIndex < 0) {
@@ -1846,9 +2030,10 @@ function describeLivekitRoom(livekit) {
1846
2030
  // src/runtime/managers/livekit-source-manager.ts
1847
2031
  var TAG2 = "[IVI-LIVEKIT]";
1848
2032
  var LivekitSourceManager = class {
1849
- constructor(onLog, onRemoteVideoAvailable) {
2033
+ constructor(onLog, onRemoteVideoAvailable, telemetry) {
1850
2034
  this.onLog = onLog;
1851
2035
  this.onRemoteVideoAvailable = onRemoteVideoAvailable;
2036
+ this.telemetry = telemetry;
1852
2037
  this.sessions = /* @__PURE__ */ new Map();
1853
2038
  this.listeners = /* @__PURE__ */ new Map();
1854
2039
  }
@@ -2070,6 +2255,15 @@ var LivekitSourceManager = class {
2070
2255
  kind
2071
2256
  });
2072
2257
  if (kind === "video" && !session.hasEverReceivedRemoteVideo) {
2258
+ recordIviTelemetrySpan(
2259
+ "sdk.media.livekit.remote_video_available",
2260
+ this.telemetry,
2261
+ {
2262
+ "ivi.source.id": session.sourceId,
2263
+ "ivi.livekit.participant_identity": participant.identity,
2264
+ "ivi.livekit.track_sid": track.sid
2265
+ }
2266
+ );
2073
2267
  session.hasEverReceivedRemoteVideo = true;
2074
2268
  for (const waiter of session.remoteVideoWaiters) {
2075
2269
  waiter(true);
@@ -2107,7 +2301,22 @@ var LivekitSourceManager = class {
2107
2301
  "info",
2108
2302
  `\u6B63\u5728\u8FDB\u623F source=${session.sourceId} room=${describeLivekitRoom(session.livekit)} ws=${session.livekit.ws_url}`
2109
2303
  );
2110
- await room.connect(session.livekit.ws_url, session.livekit.token);
2304
+ const joinRoomSpan = startIviTelemetrySpan(
2305
+ "sdk.media.livekit.join_room",
2306
+ this.telemetry,
2307
+ buildLivekitJoinRoomTelemetryAttributes(session)
2308
+ );
2309
+ try {
2310
+ await room.connect(session.livekit.ws_url, session.livekit.token);
2311
+ endIviTelemetrySpan(joinRoomSpan.span, {
2312
+ "ivi.media.livekit.join_room.result": "connected"
2313
+ });
2314
+ } catch (error) {
2315
+ failIviTelemetrySpan(joinRoomSpan.span, error, {
2316
+ "ivi.media.livekit.join_room.result": "failed"
2317
+ });
2318
+ throw error;
2319
+ }
2111
2320
  session.room = room;
2112
2321
  session.livekitModule = livekitModule;
2113
2322
  session.status = "connected";
@@ -2242,6 +2451,13 @@ var LivekitSourceManager = class {
2242
2451
  function buildRemoteTrackKey(identity, trackId) {
2243
2452
  return `${identity}::${trackId}`;
2244
2453
  }
2454
+ function buildLivekitJoinRoomTelemetryAttributes(session) {
2455
+ return {
2456
+ "ivi.source.id": session.sourceId,
2457
+ "ivi.livekit.room": session.livekit.room,
2458
+ "ivi.livekit.identity": session.livekit.identity
2459
+ };
2460
+ }
2245
2461
  function enforceContainMedia2(element) {
2246
2462
  element.style.setProperty("width", "100%", "important");
2247
2463
  element.style.setProperty("height", "100%", "important");
@@ -2291,11 +2507,16 @@ var IviRuntimeCoordinator = class {
2291
2507
  this.trtcSourceManager = new TrtcSourceManager(
2292
2508
  this.config.onLog,
2293
2509
  (event) => this.onTrtcManagerEvent(event),
2294
- this.config.trtcAIDenoiser
2510
+ this.config.trtcAIDenoiser,
2511
+ this.config.telemetry
2512
+ );
2513
+ this.livekitSourceManager = new LivekitSourceManager(
2514
+ this.config.onLog,
2515
+ () => {
2516
+ this.recomposeBootstrapState();
2517
+ },
2518
+ this.config.telemetry
2295
2519
  );
2296
- this.livekitSourceManager = new LivekitSourceManager(this.config.onLog, () => {
2297
- this.recomposeBootstrapState();
2298
- });
2299
2520
  this.sessionHandler = new SessionEventHandler(this.sessionManager, {
2300
2521
  onSessionCreated: (event) => this.onSessionCreated(event),
2301
2522
  onSessionEnded: (event) => this.onSessionEnded(event)
@@ -2332,15 +2553,27 @@ var IviRuntimeCoordinator = class {
2332
2553
  * 启动后状态会从 idle/stopped 进入 connecting,连接成功后由事件驱动进入后续状态。
2333
2554
  */
2334
2555
  async start() {
2335
- this.setState({
2336
- ...this.state,
2337
- status: "connecting"
2556
+ const telemetrySpan = startIviTelemetrySpan("sdk.runtime.start", this.config.telemetry, {
2557
+ "ivi.runtime.status": this.state.status,
2558
+ "ivi.session.id": this.state.session?.id
2338
2559
  });
2339
- this.dispatcher.start();
2340
2560
  try {
2561
+ this.setState({
2562
+ ...this.state,
2563
+ status: "connecting"
2564
+ });
2565
+ this.dispatcher.start();
2341
2566
  await this.client.connect();
2567
+ endIviTelemetrySpan(telemetrySpan.span, {
2568
+ "ivi.runtime.status": this.state.status,
2569
+ "ivi.session.id": this.state.session?.id
2570
+ });
2342
2571
  } catch (error) {
2343
2572
  this.stop();
2573
+ failIviTelemetrySpan(telemetrySpan.span, error, {
2574
+ "ivi.runtime.status": this.state.status,
2575
+ "ivi.session.id": this.state.session?.id
2576
+ });
2344
2577
  throw error;
2345
2578
  }
2346
2579
  }
@@ -2348,13 +2581,31 @@ var IviRuntimeCoordinator = class {
2348
2581
  * 停止协调器并断开实时连接,同时重置运行态数据为 stopped。
2349
2582
  */
2350
2583
  stop() {
2351
- this.dispatcher.stop();
2352
- this.client.disconnect();
2353
- this.resetStoppedState();
2584
+ const telemetrySpan = startIviTelemetrySpan("sdk.runtime.stop", this.config.telemetry, {
2585
+ "ivi.runtime.status": this.state.status,
2586
+ "ivi.session.id": this.state.session?.id
2587
+ });
2588
+ try {
2589
+ this.dispatcher.stop();
2590
+ this.client.disconnect();
2591
+ this.resetStoppedState();
2592
+ endIviTelemetrySpan(telemetrySpan.span, {
2593
+ "ivi.runtime.status": this.state.status
2594
+ });
2595
+ } catch (error) {
2596
+ failIviTelemetrySpan(telemetrySpan.span, error, {
2597
+ "ivi.runtime.status": this.state.status,
2598
+ "ivi.session.id": this.state.session?.id
2599
+ });
2600
+ throw error;
2601
+ }
2354
2602
  }
2355
2603
  getState() {
2356
2604
  return this.state;
2357
2605
  }
2606
+ getTelemetryOptions() {
2607
+ return this.config.telemetry;
2608
+ }
2358
2609
  onStateChange(listener) {
2359
2610
  this.stateListeners.add(listener);
2360
2611
  return () => {
@@ -2928,44 +3179,101 @@ async function createIVISession(request, options) {
2928
3179
  if (!fetchImpl) {
2929
3180
  throw new Error("fetch is not available. Pass options.fetch to createIVISession.");
2930
3181
  }
2931
- const requestBody = toCpCreateIVISessionRequestBody(request);
2932
- const response = await fetchImpl(resolveCreateIVISessionUrl(options), {
2933
- method: "POST",
2934
- headers: await buildHeaders(options.headers),
2935
- body: JSON.stringify(requestBody),
2936
- credentials: options.credentials,
2937
- signal: options.signal
2938
- });
2939
- const responseBody = await readJsonResponse(response);
2940
- if (!response.ok) {
2941
- throw new IviCreateIVISessionError(response, responseBody);
3182
+ const telemetrySpan = startIviTelemetrySpan(
3183
+ "sdk.session.create",
3184
+ options.telemetry,
3185
+ buildCreateSessionTelemetryAttributes(request)
3186
+ );
3187
+ try {
3188
+ const requestBody = toCpCreateIVISessionRequestBody(request);
3189
+ const headers = await buildHeaders(options.headers);
3190
+ injectIviTelemetryHeaders(headers, telemetrySpan.context);
3191
+ const response = await fetchImpl(resolveCreateIVISessionUrl(options), {
3192
+ method: "POST",
3193
+ headers,
3194
+ body: JSON.stringify(requestBody),
3195
+ credentials: options.credentials,
3196
+ signal: options.signal
3197
+ });
3198
+ const responseBody = await readJsonResponse(response);
3199
+ if (!response.ok) {
3200
+ const error = new IviCreateIVISessionError(response, responseBody);
3201
+ failIviTelemetrySpan(telemetrySpan.span, error, {
3202
+ "http.response.status_code": response.status,
3203
+ "http.response.status_text": response.statusText
3204
+ });
3205
+ throw error;
3206
+ }
3207
+ const result = normalizeCreateIVISessionResponse(responseBody, request, requestBody);
3208
+ endIviTelemetrySpan(telemetrySpan.span, {
3209
+ "http.response.status_code": response.status,
3210
+ "ivi.session.id": result.iviSessionId,
3211
+ "ivi.prebuild.playback.kind": result.prebuiltPlayback?.kind
3212
+ });
3213
+ return result;
3214
+ } catch (error) {
3215
+ if (!(error instanceof IviCreateIVISessionError)) {
3216
+ failIviTelemetrySpan(telemetrySpan.span, error);
3217
+ }
3218
+ throw error;
2942
3219
  }
2943
- return normalizeCreateIVISessionResponse(responseBody, request, requestBody);
2944
3220
  }
2945
3221
  function createRuntimeFromSession(session, options = {}) {
2946
- const websocketUrl = buildWebSocketUrl(session.endpoint, options.websocket);
2947
- const client = new IviClient({
2948
- ...options.clientConfig ?? {},
2949
- transport: new WebSocketTransport({
2950
- url: websocketUrl,
2951
- sessionId: session.iviSessionId,
2952
- protocols: options.websocket?.protocols,
2953
- socketFactory: options.websocket?.socketFactory
2954
- })
3222
+ const telemetrySpan = startIviTelemetrySpan("sdk.runtime.create", options.telemetry, {
3223
+ "ivi.session.id": session.iviSessionId,
3224
+ "ivi.prebuild.playback.kind": session.prebuiltPlayback?.kind
2955
3225
  });
2956
- const runtime = new IviRuntimeCoordinator(client, options.runtimeConfig);
2957
- if (options.fastStart !== false && session.prebuiltPlayback) {
2958
- runtime.setBootstrapSource(toBootstrapSource(session.prebuiltPlayback, options));
3226
+ try {
3227
+ const websocketUrl = buildWebSocketUrl(session.endpoint, options.websocket);
3228
+ const childTelemetry = withIviTelemetryContext(options.telemetry, telemetrySpan.context, {
3229
+ "ivi.session.id": session.iviSessionId
3230
+ });
3231
+ const clientTelemetry = mergeIviTelemetryOptions(
3232
+ options.clientConfig?.telemetry,
3233
+ childTelemetry
3234
+ );
3235
+ const runtimeTelemetry = mergeIviTelemetryOptions(
3236
+ options.runtimeConfig?.telemetry,
3237
+ childTelemetry
3238
+ );
3239
+ const client = new IviClient({
3240
+ ...options.clientConfig ?? {},
3241
+ telemetry: clientTelemetry,
3242
+ transport: new WebSocketTransport({
3243
+ url: websocketUrl,
3244
+ sessionId: session.iviSessionId,
3245
+ protocols: options.websocket?.protocols,
3246
+ socketFactory: options.websocket?.socketFactory
3247
+ })
3248
+ });
3249
+ const runtime = new IviRuntimeCoordinator(client, {
3250
+ ...options.runtimeConfig ?? {},
3251
+ telemetry: runtimeTelemetry
3252
+ });
3253
+ if (options.fastStart !== false && session.prebuiltPlayback) {
3254
+ runtime.setBootstrapSource(toBootstrapSource(session.prebuiltPlayback, options));
3255
+ }
3256
+ endIviTelemetrySpan(telemetrySpan.span);
3257
+ return {
3258
+ session,
3259
+ client,
3260
+ runtime
3261
+ };
3262
+ } catch (error) {
3263
+ failIviTelemetrySpan(telemetrySpan.span, error);
3264
+ throw error;
2959
3265
  }
2960
- return {
2961
- session,
2962
- client,
2963
- runtime
2964
- };
2965
3266
  }
2966
3267
  async function createIVISessionRuntime(request, options) {
2967
- const session = await createIVISession(request, options.http);
2968
- return createRuntimeFromSession(session, options);
3268
+ const telemetry = mergeIviTelemetryOptions(options.telemetry, options.http.telemetry);
3269
+ const session = await createIVISession(request, {
3270
+ ...options.http,
3271
+ telemetry
3272
+ });
3273
+ return createRuntimeFromSession(session, {
3274
+ ...options,
3275
+ telemetry
3276
+ });
2969
3277
  }
2970
3278
  function toCpCreateIVISessionRequestBody(request) {
2971
3279
  const body = { ...request };
@@ -3051,6 +3359,25 @@ function resolveCreateIVISessionUrl(options) {
3051
3359
  const path = options.path ?? "/v1/ivi/sessions";
3052
3360
  return new URL(path, ensureTrailingSlash(options.baseUrl)).toString();
3053
3361
  }
3362
+ function buildCreateSessionTelemetryAttributes(request) {
3363
+ return {
3364
+ "ivi.version": request.iviVersion,
3365
+ "ivi.stream.type": request.streamType,
3366
+ "ivi.prebuild.enabled": Boolean(
3367
+ request.prebuiltStream || request.prebuiltCharacters && request.prebuiltCharacters.length > 0
3368
+ )
3369
+ };
3370
+ }
3371
+ function mergeIviTelemetryOptions(explicit, inherited) {
3372
+ if (!explicit && !inherited) {
3373
+ return void 0;
3374
+ }
3375
+ return {
3376
+ tracer: explicit?.tracer ?? inherited?.tracer,
3377
+ context: explicit?.context ?? inherited?.context,
3378
+ attributes: mergeIviTelemetryAttributes(inherited?.attributes, explicit?.attributes)
3379
+ };
3380
+ }
3054
3381
  async function buildHeaders(headers) {
3055
3382
  const resolvedHeaders = typeof headers === "function" ? await headers() : headers;
3056
3383
  const next = new Headers(resolvedHeaders);
@@ -3059,6 +3386,14 @@ async function buildHeaders(headers) {
3059
3386
  }
3060
3387
  return next;
3061
3388
  }
3389
+ var headersTextMapSetter = {
3390
+ set(carrier, key, value) {
3391
+ carrier.set(key, value);
3392
+ }
3393
+ };
3394
+ function injectIviTelemetryHeaders(headers, context) {
3395
+ propagation.inject(context, headers, headersTextMapSetter);
3396
+ }
3062
3397
  async function readJsonResponse(response) {
3063
3398
  const text = await response.text();
3064
3399
  if (!text) {
@@ -3232,32 +3567,71 @@ var IviFrontendSdk = class {
3232
3567
  this.config = config;
3233
3568
  }
3234
3569
  createClient(config) {
3235
- return new IviClient(config);
3570
+ return new IviClient({
3571
+ ...config,
3572
+ telemetry: mergeSdkTelemetryOptions(config.telemetry, this.config.telemetry)
3573
+ });
3236
3574
  }
3237
3575
  createRuntimeCoordinator(clientConfig, runtimeConfig) {
3238
3576
  const client = this.createClient(clientConfig);
3239
- return new IviRuntimeCoordinator(client, runtimeConfig);
3577
+ return new IviRuntimeCoordinator(client, {
3578
+ ...runtimeConfig ?? {},
3579
+ telemetry: mergeSdkTelemetryOptions(runtimeConfig?.telemetry, this.config.telemetry)
3580
+ });
3240
3581
  }
3241
3582
  createIVISession(request, options) {
3583
+ const telemetry = mergeSdkTelemetryChain(
3584
+ this.config.telemetry,
3585
+ this.config.http?.telemetry,
3586
+ options?.telemetry
3587
+ );
3242
3588
  return createIVISession(request, {
3243
3589
  ...this.config.http ?? {},
3244
- ...options ?? {}
3590
+ ...options ?? {},
3591
+ telemetry
3245
3592
  });
3246
3593
  }
3247
3594
  createRuntimeFromSession(session, options) {
3248
- return createRuntimeFromSession(session, options);
3595
+ return createRuntimeFromSession(session, {
3596
+ ...options ?? {},
3597
+ telemetry: mergeSdkTelemetryChain(this.config.telemetry, options?.telemetry)
3598
+ });
3249
3599
  }
3250
3600
  createIVISessionRuntime(request, options) {
3601
+ const telemetry = mergeSdkTelemetryChain(
3602
+ this.config.telemetry,
3603
+ this.config.http?.telemetry,
3604
+ options?.http?.telemetry,
3605
+ options?.telemetry
3606
+ );
3251
3607
  const http = {
3252
3608
  ...this.config.http ?? {},
3253
- ...options?.http ?? {}
3609
+ ...options?.http ?? {},
3610
+ telemetry
3254
3611
  };
3255
3612
  return createIVISessionRuntime(request, {
3256
3613
  ...options ?? {},
3614
+ telemetry,
3257
3615
  http
3258
3616
  });
3259
3617
  }
3260
3618
  };
3619
+ function mergeSdkTelemetryChain(...items) {
3620
+ return items.reduce(
3621
+ (merged, item) => mergeSdkTelemetryOptions(item, merged),
3622
+ void 0
3623
+ );
3624
+ }
3625
+ function mergeSdkTelemetryOptions(explicit, inherited) {
3626
+ if (!explicit && !inherited) {
3627
+ return void 0;
3628
+ }
3629
+ return {
3630
+ tracer: explicit?.tracer ?? inherited?.tracer,
3631
+ context: explicit?.context ?? inherited?.context,
3632
+ attributes: mergeIviTelemetryAttributes(inherited?.attributes, explicit?.attributes)
3633
+ };
3634
+ }
3261
3635
  var IviStageViewContext = createContext(null);
3262
3636
  var EMPTY_RUNTIME_STATE = {
3263
3637
  status: "idle",
@@ -4458,7 +4832,16 @@ function makeLoadPolicy(label, onLog) {
4458
4832
  };
4459
4833
  }
4460
4834
  function IVIHlsVideo(props) {
4461
- const { url, videoProps, style, aggressivePreload = false, paused = false, onLog } = props;
4835
+ const {
4836
+ url,
4837
+ sourceId,
4838
+ videoProps,
4839
+ style,
4840
+ aggressivePreload = false,
4841
+ paused = false,
4842
+ onLog,
4843
+ telemetry
4844
+ } = props;
4462
4845
  const videoRef = useRef(null);
4463
4846
  const pausedRef = useRef(paused);
4464
4847
  useEffect(() => {
@@ -4495,6 +4878,7 @@ function IVIHlsVideo(props) {
4495
4878
  const onVideoError = () => {
4496
4879
  const el = videoRef.current;
4497
4880
  const mediaErr = el?.error;
4881
+ recordVideoTelemetry("sdk.media.video.error", telemetry, sourceId, "hls", mediaErr);
4498
4882
  emitHlsLog(onLog, "warn", "<video> \u5143\u7D20\u62A5\u9519", {
4499
4883
  code: mediaErr?.code,
4500
4884
  message: mediaErr?.message,
@@ -4504,6 +4888,13 @@ function IVIHlsVideo(props) {
4504
4888
  });
4505
4889
  };
4506
4890
  const onVideoStalled = () => {
4891
+ recordVideoTelemetry(
4892
+ "sdk.media.video.stalled",
4893
+ telemetry,
4894
+ sourceId,
4895
+ "hls",
4896
+ new Error("<video> stalled")
4897
+ );
4507
4898
  emitHlsLog(onLog, "warn", "<video> stalled\uFF08\u7F13\u51B2\u505C\u6EDE\uFF09", {
4508
4899
  currentTime: videoRef.current?.currentTime,
4509
4900
  readyState: videoRef.current?.readyState
@@ -4646,7 +5037,7 @@ function IVIHlsVideo(props) {
4646
5037
  hlsInstance?.destroy();
4647
5038
  hlsInstance = null;
4648
5039
  };
4649
- }, [url, aggressivePreload, onLog]);
5040
+ }, [url, sourceId, aggressivePreload, onLog, telemetry]);
4650
5041
  return /* @__PURE__ */ jsx(
4651
5042
  "video",
4652
5043
  {
@@ -4659,6 +5050,17 @@ function IVIHlsVideo(props) {
4659
5050
  }
4660
5051
  );
4661
5052
  }
5053
+ function recordVideoTelemetry(name, telemetry, sourceId, kind, error) {
5054
+ recordIviTelemetrySpan(
5055
+ name,
5056
+ telemetry,
5057
+ {
5058
+ "ivi.source.id": sourceId,
5059
+ "ivi.media.kind": kind
5060
+ },
5061
+ error
5062
+ );
5063
+ }
4662
5064
  function isM3u8Url(url) {
4663
5065
  return /\.m3u8(?:$|[?#])/i.test(url);
4664
5066
  }
@@ -4724,6 +5126,7 @@ function TrackSlotMediaContent(props) {
4724
5126
  source,
4725
5127
  isPreloading: !isActive
4726
5128
  };
5129
+ const telemetry = runtime?.getTelemetryOptions();
4727
5130
  const mediaStyle = buildAdaptiveMediaStyle(adaptToSourceSize, fitStrategy);
4728
5131
  const shouldMute = !isActive;
4729
5132
  if (renderMedia) return renderMedia(renderContext);
@@ -4795,9 +5198,11 @@ function TrackSlotMediaContent(props) {
4795
5198
  IVIHlsVideo,
4796
5199
  {
4797
5200
  url: playbackUrl,
5201
+ sourceId: slotSourceId,
4798
5202
  videoProps: mergedVideoProps,
4799
5203
  style: videoStyle,
4800
- paused: shouldPause
5204
+ paused: shouldPause,
5205
+ telemetry
4801
5206
  }
4802
5207
  );
4803
5208
  }
@@ -4819,6 +5224,7 @@ function TrackSlotMediaContent(props) {
4819
5224
  SlotVideo,
4820
5225
  {
4821
5226
  src: playbackUrl,
5227
+ sourceId: slotSourceId,
4822
5228
  paused: shouldPause,
4823
5229
  videoProps: {
4824
5230
  ...mergedVideoProps,
@@ -4826,7 +5232,8 @@ function TrackSlotMediaContent(props) {
4826
5232
  playsInline: videoProps?.playsInline ?? true,
4827
5233
  controls: isActive ? videoProps?.controls ?? true : false
4828
5234
  },
4829
- style: videoStyle
5235
+ style: videoStyle,
5236
+ telemetry
4830
5237
  }
4831
5238
  );
4832
5239
  }
@@ -4876,17 +5283,48 @@ function SlotVideo(props) {
4876
5283
  video.play().catch(() => void 0);
4877
5284
  }
4878
5285
  }, [props.paused]);
5286
+ const onError = (event) => {
5287
+ recordPlainVideoTelemetry(
5288
+ "sdk.media.video.error",
5289
+ props.telemetry,
5290
+ props.sourceId,
5291
+ event.currentTarget.error
5292
+ );
5293
+ props.videoProps.onError?.(event);
5294
+ };
5295
+ const onStalled = (event) => {
5296
+ recordPlainVideoTelemetry(
5297
+ "sdk.media.video.stalled",
5298
+ props.telemetry,
5299
+ props.sourceId,
5300
+ new Error("<video> stalled")
5301
+ );
5302
+ props.videoProps.onStalled?.(event);
5303
+ };
4879
5304
  return /* @__PURE__ */ jsx(
4880
5305
  "video",
4881
5306
  {
4882
5307
  ref: videoRef,
4883
5308
  src: props.src,
4884
5309
  ...props.videoProps,
5310
+ onError,
5311
+ onStalled,
4885
5312
  autoPlay: props.paused ? false : props.videoProps.autoPlay ?? true,
4886
5313
  style: props.style
4887
5314
  }
4888
5315
  );
4889
5316
  }
5317
+ function recordPlainVideoTelemetry(name, telemetry, sourceId, error) {
5318
+ recordIviTelemetrySpan(
5319
+ name,
5320
+ telemetry,
5321
+ {
5322
+ "ivi.source.id": sourceId,
5323
+ "ivi.media.kind": "video"
5324
+ },
5325
+ error
5326
+ );
5327
+ }
4890
5328
 
4891
5329
  // src/react/internal/use-multi-preload-sources.ts
4892
5330
  function useMultiPreloadSources(sources, activeSourceId) {