@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.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var iviSdkTs = require('@vivix-ai/ivi-sdk-ts');
4
+ var api = require('@opentelemetry/api');
4
5
  var websocket = require('@vivix-ai/ivi-sdk-ts/transports/websocket');
5
6
  var react = require('react');
6
7
  var jsxRuntime = require('react/jsx-runtime');
@@ -1141,6 +1142,94 @@ var ConversationManager = class {
1141
1142
  ];
1142
1143
  }
1143
1144
  };
1145
+ var DEFAULT_TRACER_NAME = "@vivix-ai/ivi-frontend-sdk";
1146
+ function getIviTelemetryTracer(options) {
1147
+ return options?.tracer ?? api.trace.getTracer(DEFAULT_TRACER_NAME);
1148
+ }
1149
+ function getIviTelemetryContext(options) {
1150
+ return options?.context ?? api.context.active();
1151
+ }
1152
+ function startIviTelemetrySpan(name, options, attributes) {
1153
+ const tracer = getIviTelemetryTracer(options);
1154
+ const parentContext = getIviTelemetryContext(options);
1155
+ const span = tracer.startSpan(
1156
+ name,
1157
+ {
1158
+ kind: api.SpanKind.INTERNAL,
1159
+ attributes: mergeIviTelemetryAttributes(options?.attributes, attributes)
1160
+ },
1161
+ parentContext
1162
+ );
1163
+ return {
1164
+ span,
1165
+ context: api.trace.setSpan(parentContext, span)
1166
+ };
1167
+ }
1168
+ function endIviTelemetrySpan(span, attributes) {
1169
+ const cleaned = cleanIviTelemetryAttributes(attributes);
1170
+ if (cleaned) {
1171
+ span.setAttributes(cleaned);
1172
+ }
1173
+ span.setStatus({ code: api.SpanStatusCode.OK });
1174
+ span.end();
1175
+ }
1176
+ function failIviTelemetrySpan(span, error, attributes) {
1177
+ const cleaned = cleanIviTelemetryAttributes(attributes);
1178
+ if (cleaned) {
1179
+ span.setAttributes(cleaned);
1180
+ }
1181
+ recordIviTelemetryError(span, error);
1182
+ span.end();
1183
+ }
1184
+ function recordIviTelemetrySpan(name, options, attributes, error) {
1185
+ const { span } = startIviTelemetrySpan(name, options, attributes);
1186
+ if (error === void 0) {
1187
+ span.setStatus({ code: api.SpanStatusCode.OK });
1188
+ } else {
1189
+ recordIviTelemetryError(span, error);
1190
+ }
1191
+ span.end();
1192
+ }
1193
+ function withIviTelemetryContext(options, context, attributes) {
1194
+ return {
1195
+ ...options,
1196
+ context,
1197
+ attributes: mergeIviTelemetryAttributes(options?.attributes, attributes)
1198
+ };
1199
+ }
1200
+ function mergeIviTelemetryAttributes(base, next) {
1201
+ return cleanIviTelemetryAttributes({
1202
+ ...base ?? {},
1203
+ ...next ?? {}
1204
+ });
1205
+ }
1206
+ function cleanIviTelemetryAttributes(attributes) {
1207
+ const cleaned = {};
1208
+ Object.entries(attributes ?? {}).forEach(([key, value]) => {
1209
+ if (isIviTelemetryAttributeValue(value)) {
1210
+ cleaned[key] = value;
1211
+ }
1212
+ });
1213
+ return Object.keys(cleaned).length > 0 ? cleaned : void 0;
1214
+ }
1215
+ function recordIviTelemetryError(span, error) {
1216
+ const message = error instanceof Error ? error.message : String(error);
1217
+ span.recordException(error instanceof Error ? error : message);
1218
+ span.setStatus({
1219
+ code: api.SpanStatusCode.ERROR,
1220
+ message
1221
+ });
1222
+ }
1223
+ function isIviTelemetryAttributeValue(value) {
1224
+ if (typeof value === "string" || typeof value === "boolean") return true;
1225
+ if (typeof value === "number") return Number.isFinite(value);
1226
+ if (!Array.isArray(value)) return false;
1227
+ const nonNullValues = value.filter((item) => item !== null && item !== void 0);
1228
+ if (nonNullValues.length === 0) return true;
1229
+ const firstType = typeof nonNullValues[0];
1230
+ if (firstType !== "string" && firstType !== "number" && firstType !== "boolean") return false;
1231
+ return nonNullValues.every((item) => typeof item === firstType && (typeof item !== "number" || Number.isFinite(item)));
1232
+ }
1144
1233
 
1145
1234
  // src/runtime/managers/trtc-source-manager.ts
1146
1235
  var TAG = "[IVI-TRTC]";
@@ -1151,10 +1240,11 @@ var DEFAULT_DENOISER_OPTIONS = {
1151
1240
  mode: "normal"
1152
1241
  };
1153
1242
  var TrtcSourceManager = class {
1154
- constructor(onLog, onTrtcEvent, aiDenoiser) {
1243
+ constructor(onLog, onTrtcEvent, aiDenoiser, telemetry) {
1155
1244
  this.onLog = onLog;
1156
1245
  this.onTrtcEvent = onTrtcEvent;
1157
1246
  this.aiDenoiser = aiDenoiser;
1247
+ this.telemetry = telemetry;
1158
1248
  this.sessions = /* @__PURE__ */ new Map();
1159
1249
  this.sourceSessionKeys = /* @__PURE__ */ new Map();
1160
1250
  this.listeners = /* @__PURE__ */ new Map();
@@ -1450,9 +1540,14 @@ var TrtcSourceManager = class {
1450
1540
  const client = TRTC.create();
1451
1541
  const onRemoteVideoAvailable = (event) => {
1452
1542
  this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u53EF\u7528 source=${session.sourceId} userId=${event.userId} streamType=${event.streamType}`);
1543
+ const telemetryAttributes = buildTrtcMediaTelemetryAttributes(session.sourceId, event.userId, event.streamType);
1453
1544
  const remoteVideoKey = buildRemoteVideoKey(event.userId, String(event.streamType));
1454
1545
  session.remoteVideoStreams.add(remoteVideoKey);
1455
1546
  if (!session.hasEverReceivedRemoteVideo) {
1547
+ this.endRemoteVideoAvailableWait(session, {
1548
+ ...telemetryAttributes,
1549
+ "ivi.media.trtc.waiting_remote_video_available.result": "available"
1550
+ });
1456
1551
  session.hasEverReceivedRemoteVideo = true;
1457
1552
  for (const waiter of session.remoteVideoWaiters) {
1458
1553
  waiter(true);
@@ -1499,15 +1594,38 @@ var TrtcSourceManager = class {
1499
1594
  client.on(TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, onRemoteAudioAvailable);
1500
1595
  client.on(TRTC.EVENT.REMOTE_AUDIO_UNAVAILABLE, onRemoteAudioUnavailable);
1501
1596
  this.log("info", `\u6B63\u5728\u8FDB\u623F source=${session.sourceId} room=${roomId} sdkAppId=${sdkAppId} userId=${userId}`);
1502
- await client.enterRoom({
1503
- sdkAppId,
1504
- userId,
1505
- userSig,
1506
- scene: TRTC.TYPE.SCENE_LIVE,
1507
- role: TRTC.TYPE.ROLE_AUDIENCE,
1508
- autoReceiveAudio: true,
1509
- ...isStringRoomId ? { strRoomId: roomId } : { roomId: Number(roomId) }
1510
- });
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
+ }
1511
1629
  session.TRTC = TRTC;
1512
1630
  session.client = client;
1513
1631
  session.status = "connected";
@@ -1519,6 +1637,9 @@ var TrtcSourceManager = class {
1519
1637
  });
1520
1638
  await this.applyAudioPolicy(session);
1521
1639
  })().catch((error) => {
1640
+ this.failRemoteVideoAvailableWait(session, error, {
1641
+ "ivi.media.trtc.waiting_remote_video_available.result": "connect_failed"
1642
+ });
1522
1643
  session.status = "error";
1523
1644
  session.error = error instanceof Error ? error.message : String(error);
1524
1645
  this.log("error", `\u8FDE\u63A5\u5931\u8D25 source=${session.sourceId} error=${session.error}`);
@@ -1557,6 +1678,16 @@ var TrtcSourceManager = class {
1557
1678
  return;
1558
1679
  }
1559
1680
  binding.startingVideoKeys.add(remoteVideoKey);
1681
+ const telemetryAttributes = buildTrtcMediaTelemetryAttributes(
1682
+ binding.sourceId,
1683
+ userId,
1684
+ streamType
1685
+ );
1686
+ const telemetrySpan = startIviTelemetrySpan(
1687
+ "sdk.media.trtc.start_remote_video",
1688
+ this.telemetry,
1689
+ telemetryAttributes
1690
+ );
1560
1691
  try {
1561
1692
  this.log("info", `\u5F00\u59CB\u6E32\u67D3\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} view=${remoteVideoKey} userId=${userId} streamType=${streamType}`);
1562
1693
  await client.startRemoteVideo({
@@ -1565,14 +1696,24 @@ var TrtcSourceManager = class {
1565
1696
  view: binding.container,
1566
1697
  option: { fillMode: "contain" }
1567
1698
  });
1699
+ endIviTelemetrySpan(telemetrySpan.span);
1700
+ recordIviTelemetrySpan(
1701
+ "sdk.media.trtc.remote_video_rendered",
1702
+ this.telemetry,
1703
+ telemetryAttributes
1704
+ );
1568
1705
  this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5B8C\u6210 source=${binding.sourceId} userId=${userId} streamType=${streamType}`);
1569
1706
  binding.startedVideoKeys.add(remoteVideoKey);
1570
1707
  enforceContainMedia(binding.container);
1571
1708
  } catch (error) {
1572
1709
  if (error instanceof Error && /already started|OPERATION_ABORT/i.test(error.message)) {
1573
1710
  binding.startedVideoKeys.add(remoteVideoKey);
1711
+ endIviTelemetrySpan(telemetrySpan.span, {
1712
+ "ivi.media.trtc.start_remote_video.result": "already_started"
1713
+ });
1574
1714
  return;
1575
1715
  }
1716
+ failIviTelemetrySpan(telemetrySpan.span, error);
1576
1717
  this.log("warn", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5931\u8D25 userId=${userId} streamType=${streamType}`, error);
1577
1718
  } finally {
1578
1719
  binding.startingVideoKeys.delete(remoteVideoKey);
@@ -1621,6 +1762,22 @@ var TrtcSourceManager = class {
1621
1762
  this.log("warn", `TRTC AIDenoiser \u5F00\u542F\u5931\u8D25 source=${session.sourceId}`, error);
1622
1763
  }
1623
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
+ }
1624
1781
  /**
1625
1782
  * 销毁单个 source 会话:
1626
1783
  * 解绑事件、停止远端视频、退出房间并销毁客户端。
@@ -1634,6 +1791,9 @@ var TrtcSourceManager = class {
1634
1791
  session.remoteVideoStreams.clear();
1635
1792
  session.status = "idle";
1636
1793
  session.error = void 0;
1794
+ this.endRemoteVideoAvailableWait(session, {
1795
+ "ivi.media.trtc.waiting_remote_video_available.result": "cancelled"
1796
+ });
1637
1797
  for (const waiter of session.remoteVideoWaiters) {
1638
1798
  waiter(false);
1639
1799
  }
@@ -1786,6 +1946,30 @@ function getAIDenoiserModeValue(mode) {
1786
1946
  function buildRemoteVideoKey(userId, streamType) {
1787
1947
  return `${userId}::${streamType}`;
1788
1948
  }
1949
+ function buildTrtcMediaTelemetryAttributes(sourceId, userId, streamType) {
1950
+ return {
1951
+ "ivi.source.id": sourceId,
1952
+ "ivi.trtc.user_id": userId,
1953
+ "ivi.trtc.stream_type": String(streamType)
1954
+ };
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
+ }
1789
1973
  function parseRemoteVideoKey(key) {
1790
1974
  const separatorIndex = key.indexOf("::");
1791
1975
  if (separatorIndex < 0) {
@@ -1848,9 +2032,10 @@ function describeLivekitRoom(livekit) {
1848
2032
  // src/runtime/managers/livekit-source-manager.ts
1849
2033
  var TAG2 = "[IVI-LIVEKIT]";
1850
2034
  var LivekitSourceManager = class {
1851
- constructor(onLog, onRemoteVideoAvailable) {
2035
+ constructor(onLog, onRemoteVideoAvailable, telemetry) {
1852
2036
  this.onLog = onLog;
1853
2037
  this.onRemoteVideoAvailable = onRemoteVideoAvailable;
2038
+ this.telemetry = telemetry;
1854
2039
  this.sessions = /* @__PURE__ */ new Map();
1855
2040
  this.listeners = /* @__PURE__ */ new Map();
1856
2041
  }
@@ -2072,6 +2257,15 @@ var LivekitSourceManager = class {
2072
2257
  kind
2073
2258
  });
2074
2259
  if (kind === "video" && !session.hasEverReceivedRemoteVideo) {
2260
+ recordIviTelemetrySpan(
2261
+ "sdk.media.livekit.remote_video_available",
2262
+ this.telemetry,
2263
+ {
2264
+ "ivi.source.id": session.sourceId,
2265
+ "ivi.livekit.participant_identity": participant.identity,
2266
+ "ivi.livekit.track_sid": track.sid
2267
+ }
2268
+ );
2075
2269
  session.hasEverReceivedRemoteVideo = true;
2076
2270
  for (const waiter of session.remoteVideoWaiters) {
2077
2271
  waiter(true);
@@ -2109,7 +2303,22 @@ var LivekitSourceManager = class {
2109
2303
  "info",
2110
2304
  `\u6B63\u5728\u8FDB\u623F source=${session.sourceId} room=${describeLivekitRoom(session.livekit)} ws=${session.livekit.ws_url}`
2111
2305
  );
2112
- 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
+ }
2113
2322
  session.room = room;
2114
2323
  session.livekitModule = livekitModule;
2115
2324
  session.status = "connected";
@@ -2244,6 +2453,13 @@ var LivekitSourceManager = class {
2244
2453
  function buildRemoteTrackKey(identity, trackId) {
2245
2454
  return `${identity}::${trackId}`;
2246
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
+ }
2247
2463
  function enforceContainMedia2(element) {
2248
2464
  element.style.setProperty("width", "100%", "important");
2249
2465
  element.style.setProperty("height", "100%", "important");
@@ -2293,11 +2509,16 @@ var IviRuntimeCoordinator = class {
2293
2509
  this.trtcSourceManager = new TrtcSourceManager(
2294
2510
  this.config.onLog,
2295
2511
  (event) => this.onTrtcManagerEvent(event),
2296
- this.config.trtcAIDenoiser
2512
+ this.config.trtcAIDenoiser,
2513
+ this.config.telemetry
2514
+ );
2515
+ this.livekitSourceManager = new LivekitSourceManager(
2516
+ this.config.onLog,
2517
+ () => {
2518
+ this.recomposeBootstrapState();
2519
+ },
2520
+ this.config.telemetry
2297
2521
  );
2298
- this.livekitSourceManager = new LivekitSourceManager(this.config.onLog, () => {
2299
- this.recomposeBootstrapState();
2300
- });
2301
2522
  this.sessionHandler = new SessionEventHandler(this.sessionManager, {
2302
2523
  onSessionCreated: (event) => this.onSessionCreated(event),
2303
2524
  onSessionEnded: (event) => this.onSessionEnded(event)
@@ -2334,15 +2555,27 @@ var IviRuntimeCoordinator = class {
2334
2555
  * 启动后状态会从 idle/stopped 进入 connecting,连接成功后由事件驱动进入后续状态。
2335
2556
  */
2336
2557
  async start() {
2337
- this.setState({
2338
- ...this.state,
2339
- status: "connecting"
2558
+ const telemetrySpan = startIviTelemetrySpan("sdk.runtime.start", this.config.telemetry, {
2559
+ "ivi.runtime.status": this.state.status,
2560
+ "ivi.session.id": this.state.session?.id
2340
2561
  });
2341
- this.dispatcher.start();
2342
2562
  try {
2563
+ this.setState({
2564
+ ...this.state,
2565
+ status: "connecting"
2566
+ });
2567
+ this.dispatcher.start();
2343
2568
  await this.client.connect();
2569
+ endIviTelemetrySpan(telemetrySpan.span, {
2570
+ "ivi.runtime.status": this.state.status,
2571
+ "ivi.session.id": this.state.session?.id
2572
+ });
2344
2573
  } catch (error) {
2345
2574
  this.stop();
2575
+ failIviTelemetrySpan(telemetrySpan.span, error, {
2576
+ "ivi.runtime.status": this.state.status,
2577
+ "ivi.session.id": this.state.session?.id
2578
+ });
2346
2579
  throw error;
2347
2580
  }
2348
2581
  }
@@ -2350,13 +2583,31 @@ var IviRuntimeCoordinator = class {
2350
2583
  * 停止协调器并断开实时连接,同时重置运行态数据为 stopped。
2351
2584
  */
2352
2585
  stop() {
2353
- this.dispatcher.stop();
2354
- this.client.disconnect();
2355
- this.resetStoppedState();
2586
+ const telemetrySpan = startIviTelemetrySpan("sdk.runtime.stop", this.config.telemetry, {
2587
+ "ivi.runtime.status": this.state.status,
2588
+ "ivi.session.id": this.state.session?.id
2589
+ });
2590
+ try {
2591
+ this.dispatcher.stop();
2592
+ this.client.disconnect();
2593
+ this.resetStoppedState();
2594
+ endIviTelemetrySpan(telemetrySpan.span, {
2595
+ "ivi.runtime.status": this.state.status
2596
+ });
2597
+ } catch (error) {
2598
+ failIviTelemetrySpan(telemetrySpan.span, error, {
2599
+ "ivi.runtime.status": this.state.status,
2600
+ "ivi.session.id": this.state.session?.id
2601
+ });
2602
+ throw error;
2603
+ }
2356
2604
  }
2357
2605
  getState() {
2358
2606
  return this.state;
2359
2607
  }
2608
+ getTelemetryOptions() {
2609
+ return this.config.telemetry;
2610
+ }
2360
2611
  onStateChange(listener) {
2361
2612
  this.stateListeners.add(listener);
2362
2613
  return () => {
@@ -2930,44 +3181,101 @@ async function createIVISession(request, options) {
2930
3181
  if (!fetchImpl) {
2931
3182
  throw new Error("fetch is not available. Pass options.fetch to createIVISession.");
2932
3183
  }
2933
- const requestBody = toCpCreateIVISessionRequestBody(request);
2934
- const response = await fetchImpl(resolveCreateIVISessionUrl(options), {
2935
- method: "POST",
2936
- headers: await buildHeaders(options.headers),
2937
- body: JSON.stringify(requestBody),
2938
- credentials: options.credentials,
2939
- signal: options.signal
2940
- });
2941
- const responseBody = await readJsonResponse(response);
2942
- if (!response.ok) {
2943
- throw new IviCreateIVISessionError(response, responseBody);
3184
+ const telemetrySpan = startIviTelemetrySpan(
3185
+ "sdk.session.create",
3186
+ options.telemetry,
3187
+ buildCreateSessionTelemetryAttributes(request)
3188
+ );
3189
+ try {
3190
+ const requestBody = toCpCreateIVISessionRequestBody(request);
3191
+ const headers = await buildHeaders(options.headers);
3192
+ injectIviTelemetryHeaders(headers, telemetrySpan.context);
3193
+ const response = await fetchImpl(resolveCreateIVISessionUrl(options), {
3194
+ method: "POST",
3195
+ headers,
3196
+ body: JSON.stringify(requestBody),
3197
+ credentials: options.credentials,
3198
+ signal: options.signal
3199
+ });
3200
+ const responseBody = await readJsonResponse(response);
3201
+ if (!response.ok) {
3202
+ const error = new IviCreateIVISessionError(response, responseBody);
3203
+ failIviTelemetrySpan(telemetrySpan.span, error, {
3204
+ "http.response.status_code": response.status,
3205
+ "http.response.status_text": response.statusText
3206
+ });
3207
+ throw error;
3208
+ }
3209
+ const result = normalizeCreateIVISessionResponse(responseBody, request, requestBody);
3210
+ endIviTelemetrySpan(telemetrySpan.span, {
3211
+ "http.response.status_code": response.status,
3212
+ "ivi.session.id": result.iviSessionId,
3213
+ "ivi.prebuild.playback.kind": result.prebuiltPlayback?.kind
3214
+ });
3215
+ return result;
3216
+ } catch (error) {
3217
+ if (!(error instanceof IviCreateIVISessionError)) {
3218
+ failIviTelemetrySpan(telemetrySpan.span, error);
3219
+ }
3220
+ throw error;
2944
3221
  }
2945
- return normalizeCreateIVISessionResponse(responseBody, request, requestBody);
2946
3222
  }
2947
3223
  function createRuntimeFromSession(session, options = {}) {
2948
- const websocketUrl = buildWebSocketUrl(session.endpoint, options.websocket);
2949
- const client = new iviSdkTs.IviClient({
2950
- ...options.clientConfig ?? {},
2951
- transport: new websocket.WebSocketTransport({
2952
- url: websocketUrl,
2953
- sessionId: session.iviSessionId,
2954
- protocols: options.websocket?.protocols,
2955
- socketFactory: options.websocket?.socketFactory
2956
- })
3224
+ const telemetrySpan = startIviTelemetrySpan("sdk.runtime.create", options.telemetry, {
3225
+ "ivi.session.id": session.iviSessionId,
3226
+ "ivi.prebuild.playback.kind": session.prebuiltPlayback?.kind
2957
3227
  });
2958
- const runtime = new IviRuntimeCoordinator(client, options.runtimeConfig);
2959
- if (options.fastStart !== false && session.prebuiltPlayback) {
2960
- runtime.setBootstrapSource(toBootstrapSource(session.prebuiltPlayback, options));
3228
+ try {
3229
+ const websocketUrl = buildWebSocketUrl(session.endpoint, options.websocket);
3230
+ const childTelemetry = withIviTelemetryContext(options.telemetry, telemetrySpan.context, {
3231
+ "ivi.session.id": session.iviSessionId
3232
+ });
3233
+ const clientTelemetry = mergeIviTelemetryOptions(
3234
+ options.clientConfig?.telemetry,
3235
+ childTelemetry
3236
+ );
3237
+ const runtimeTelemetry = mergeIviTelemetryOptions(
3238
+ options.runtimeConfig?.telemetry,
3239
+ childTelemetry
3240
+ );
3241
+ const client = new iviSdkTs.IviClient({
3242
+ ...options.clientConfig ?? {},
3243
+ telemetry: clientTelemetry,
3244
+ transport: new websocket.WebSocketTransport({
3245
+ url: websocketUrl,
3246
+ sessionId: session.iviSessionId,
3247
+ protocols: options.websocket?.protocols,
3248
+ socketFactory: options.websocket?.socketFactory
3249
+ })
3250
+ });
3251
+ const runtime = new IviRuntimeCoordinator(client, {
3252
+ ...options.runtimeConfig ?? {},
3253
+ telemetry: runtimeTelemetry
3254
+ });
3255
+ if (options.fastStart !== false && session.prebuiltPlayback) {
3256
+ runtime.setBootstrapSource(toBootstrapSource(session.prebuiltPlayback, options));
3257
+ }
3258
+ endIviTelemetrySpan(telemetrySpan.span);
3259
+ return {
3260
+ session,
3261
+ client,
3262
+ runtime
3263
+ };
3264
+ } catch (error) {
3265
+ failIviTelemetrySpan(telemetrySpan.span, error);
3266
+ throw error;
2961
3267
  }
2962
- return {
2963
- session,
2964
- client,
2965
- runtime
2966
- };
2967
3268
  }
2968
3269
  async function createIVISessionRuntime(request, options) {
2969
- const session = await createIVISession(request, options.http);
2970
- return createRuntimeFromSession(session, options);
3270
+ const telemetry = mergeIviTelemetryOptions(options.telemetry, options.http.telemetry);
3271
+ const session = await createIVISession(request, {
3272
+ ...options.http,
3273
+ telemetry
3274
+ });
3275
+ return createRuntimeFromSession(session, {
3276
+ ...options,
3277
+ telemetry
3278
+ });
2971
3279
  }
2972
3280
  function toCpCreateIVISessionRequestBody(request) {
2973
3281
  const body = { ...request };
@@ -3053,6 +3361,25 @@ function resolveCreateIVISessionUrl(options) {
3053
3361
  const path = options.path ?? "/v1/ivi/sessions";
3054
3362
  return new URL(path, ensureTrailingSlash(options.baseUrl)).toString();
3055
3363
  }
3364
+ function buildCreateSessionTelemetryAttributes(request) {
3365
+ return {
3366
+ "ivi.version": request.iviVersion,
3367
+ "ivi.stream.type": request.streamType,
3368
+ "ivi.prebuild.enabled": Boolean(
3369
+ request.prebuiltStream || request.prebuiltCharacters && request.prebuiltCharacters.length > 0
3370
+ )
3371
+ };
3372
+ }
3373
+ function mergeIviTelemetryOptions(explicit, inherited) {
3374
+ if (!explicit && !inherited) {
3375
+ return void 0;
3376
+ }
3377
+ return {
3378
+ tracer: explicit?.tracer ?? inherited?.tracer,
3379
+ context: explicit?.context ?? inherited?.context,
3380
+ attributes: mergeIviTelemetryAttributes(inherited?.attributes, explicit?.attributes)
3381
+ };
3382
+ }
3056
3383
  async function buildHeaders(headers) {
3057
3384
  const resolvedHeaders = typeof headers === "function" ? await headers() : headers;
3058
3385
  const next = new Headers(resolvedHeaders);
@@ -3061,6 +3388,14 @@ async function buildHeaders(headers) {
3061
3388
  }
3062
3389
  return next;
3063
3390
  }
3391
+ var headersTextMapSetter = {
3392
+ set(carrier, key, value) {
3393
+ carrier.set(key, value);
3394
+ }
3395
+ };
3396
+ function injectIviTelemetryHeaders(headers, context) {
3397
+ api.propagation.inject(context, headers, headersTextMapSetter);
3398
+ }
3064
3399
  async function readJsonResponse(response) {
3065
3400
  const text = await response.text();
3066
3401
  if (!text) {
@@ -3234,32 +3569,71 @@ var IviFrontendSdk = class {
3234
3569
  this.config = config;
3235
3570
  }
3236
3571
  createClient(config) {
3237
- return new iviSdkTs.IviClient(config);
3572
+ return new iviSdkTs.IviClient({
3573
+ ...config,
3574
+ telemetry: mergeSdkTelemetryOptions(config.telemetry, this.config.telemetry)
3575
+ });
3238
3576
  }
3239
3577
  createRuntimeCoordinator(clientConfig, runtimeConfig) {
3240
3578
  const client = this.createClient(clientConfig);
3241
- return new IviRuntimeCoordinator(client, runtimeConfig);
3579
+ return new IviRuntimeCoordinator(client, {
3580
+ ...runtimeConfig ?? {},
3581
+ telemetry: mergeSdkTelemetryOptions(runtimeConfig?.telemetry, this.config.telemetry)
3582
+ });
3242
3583
  }
3243
3584
  createIVISession(request, options) {
3585
+ const telemetry = mergeSdkTelemetryChain(
3586
+ this.config.telemetry,
3587
+ this.config.http?.telemetry,
3588
+ options?.telemetry
3589
+ );
3244
3590
  return createIVISession(request, {
3245
3591
  ...this.config.http ?? {},
3246
- ...options ?? {}
3592
+ ...options ?? {},
3593
+ telemetry
3247
3594
  });
3248
3595
  }
3249
3596
  createRuntimeFromSession(session, options) {
3250
- return createRuntimeFromSession(session, options);
3597
+ return createRuntimeFromSession(session, {
3598
+ ...options ?? {},
3599
+ telemetry: mergeSdkTelemetryChain(this.config.telemetry, options?.telemetry)
3600
+ });
3251
3601
  }
3252
3602
  createIVISessionRuntime(request, options) {
3603
+ const telemetry = mergeSdkTelemetryChain(
3604
+ this.config.telemetry,
3605
+ this.config.http?.telemetry,
3606
+ options?.http?.telemetry,
3607
+ options?.telemetry
3608
+ );
3253
3609
  const http = {
3254
3610
  ...this.config.http ?? {},
3255
- ...options?.http ?? {}
3611
+ ...options?.http ?? {},
3612
+ telemetry
3256
3613
  };
3257
3614
  return createIVISessionRuntime(request, {
3258
3615
  ...options ?? {},
3616
+ telemetry,
3259
3617
  http
3260
3618
  });
3261
3619
  }
3262
3620
  };
3621
+ function mergeSdkTelemetryChain(...items) {
3622
+ return items.reduce(
3623
+ (merged, item) => mergeSdkTelemetryOptions(item, merged),
3624
+ void 0
3625
+ );
3626
+ }
3627
+ function mergeSdkTelemetryOptions(explicit, inherited) {
3628
+ if (!explicit && !inherited) {
3629
+ return void 0;
3630
+ }
3631
+ return {
3632
+ tracer: explicit?.tracer ?? inherited?.tracer,
3633
+ context: explicit?.context ?? inherited?.context,
3634
+ attributes: mergeIviTelemetryAttributes(inherited?.attributes, explicit?.attributes)
3635
+ };
3636
+ }
3263
3637
  var IviStageViewContext = react.createContext(null);
3264
3638
  var EMPTY_RUNTIME_STATE = {
3265
3639
  status: "idle",
@@ -4460,7 +4834,16 @@ function makeLoadPolicy(label, onLog) {
4460
4834
  };
4461
4835
  }
4462
4836
  function IVIHlsVideo(props) {
4463
- const { url, videoProps, style, aggressivePreload = false, paused = false, onLog } = props;
4837
+ const {
4838
+ url,
4839
+ sourceId,
4840
+ videoProps,
4841
+ style,
4842
+ aggressivePreload = false,
4843
+ paused = false,
4844
+ onLog,
4845
+ telemetry
4846
+ } = props;
4464
4847
  const videoRef = react.useRef(null);
4465
4848
  const pausedRef = react.useRef(paused);
4466
4849
  react.useEffect(() => {
@@ -4497,6 +4880,7 @@ function IVIHlsVideo(props) {
4497
4880
  const onVideoError = () => {
4498
4881
  const el = videoRef.current;
4499
4882
  const mediaErr = el?.error;
4883
+ recordVideoTelemetry("sdk.media.video.error", telemetry, sourceId, "hls", mediaErr);
4500
4884
  emitHlsLog(onLog, "warn", "<video> \u5143\u7D20\u62A5\u9519", {
4501
4885
  code: mediaErr?.code,
4502
4886
  message: mediaErr?.message,
@@ -4506,6 +4890,13 @@ function IVIHlsVideo(props) {
4506
4890
  });
4507
4891
  };
4508
4892
  const onVideoStalled = () => {
4893
+ recordVideoTelemetry(
4894
+ "sdk.media.video.stalled",
4895
+ telemetry,
4896
+ sourceId,
4897
+ "hls",
4898
+ new Error("<video> stalled")
4899
+ );
4509
4900
  emitHlsLog(onLog, "warn", "<video> stalled\uFF08\u7F13\u51B2\u505C\u6EDE\uFF09", {
4510
4901
  currentTime: videoRef.current?.currentTime,
4511
4902
  readyState: videoRef.current?.readyState
@@ -4648,7 +5039,7 @@ function IVIHlsVideo(props) {
4648
5039
  hlsInstance?.destroy();
4649
5040
  hlsInstance = null;
4650
5041
  };
4651
- }, [url, aggressivePreload, onLog]);
5042
+ }, [url, sourceId, aggressivePreload, onLog, telemetry]);
4652
5043
  return /* @__PURE__ */ jsxRuntime.jsx(
4653
5044
  "video",
4654
5045
  {
@@ -4661,6 +5052,17 @@ function IVIHlsVideo(props) {
4661
5052
  }
4662
5053
  );
4663
5054
  }
5055
+ function recordVideoTelemetry(name, telemetry, sourceId, kind, error) {
5056
+ recordIviTelemetrySpan(
5057
+ name,
5058
+ telemetry,
5059
+ {
5060
+ "ivi.source.id": sourceId,
5061
+ "ivi.media.kind": kind
5062
+ },
5063
+ error
5064
+ );
5065
+ }
4664
5066
  function isM3u8Url(url) {
4665
5067
  return /\.m3u8(?:$|[?#])/i.test(url);
4666
5068
  }
@@ -4726,6 +5128,7 @@ function TrackSlotMediaContent(props) {
4726
5128
  source,
4727
5129
  isPreloading: !isActive
4728
5130
  };
5131
+ const telemetry = runtime?.getTelemetryOptions();
4729
5132
  const mediaStyle = buildAdaptiveMediaStyle(adaptToSourceSize, fitStrategy);
4730
5133
  const shouldMute = !isActive;
4731
5134
  if (renderMedia) return renderMedia(renderContext);
@@ -4797,9 +5200,11 @@ function TrackSlotMediaContent(props) {
4797
5200
  IVIHlsVideo,
4798
5201
  {
4799
5202
  url: playbackUrl,
5203
+ sourceId: slotSourceId,
4800
5204
  videoProps: mergedVideoProps,
4801
5205
  style: videoStyle,
4802
- paused: shouldPause
5206
+ paused: shouldPause,
5207
+ telemetry
4803
5208
  }
4804
5209
  );
4805
5210
  }
@@ -4821,6 +5226,7 @@ function TrackSlotMediaContent(props) {
4821
5226
  SlotVideo,
4822
5227
  {
4823
5228
  src: playbackUrl,
5229
+ sourceId: slotSourceId,
4824
5230
  paused: shouldPause,
4825
5231
  videoProps: {
4826
5232
  ...mergedVideoProps,
@@ -4828,7 +5234,8 @@ function TrackSlotMediaContent(props) {
4828
5234
  playsInline: videoProps?.playsInline ?? true,
4829
5235
  controls: isActive ? videoProps?.controls ?? true : false
4830
5236
  },
4831
- style: videoStyle
5237
+ style: videoStyle,
5238
+ telemetry
4832
5239
  }
4833
5240
  );
4834
5241
  }
@@ -4878,17 +5285,48 @@ function SlotVideo(props) {
4878
5285
  video.play().catch(() => void 0);
4879
5286
  }
4880
5287
  }, [props.paused]);
5288
+ const onError = (event) => {
5289
+ recordPlainVideoTelemetry(
5290
+ "sdk.media.video.error",
5291
+ props.telemetry,
5292
+ props.sourceId,
5293
+ event.currentTarget.error
5294
+ );
5295
+ props.videoProps.onError?.(event);
5296
+ };
5297
+ const onStalled = (event) => {
5298
+ recordPlainVideoTelemetry(
5299
+ "sdk.media.video.stalled",
5300
+ props.telemetry,
5301
+ props.sourceId,
5302
+ new Error("<video> stalled")
5303
+ );
5304
+ props.videoProps.onStalled?.(event);
5305
+ };
4881
5306
  return /* @__PURE__ */ jsxRuntime.jsx(
4882
5307
  "video",
4883
5308
  {
4884
5309
  ref: videoRef,
4885
5310
  src: props.src,
4886
5311
  ...props.videoProps,
5312
+ onError,
5313
+ onStalled,
4887
5314
  autoPlay: props.paused ? false : props.videoProps.autoPlay ?? true,
4888
5315
  style: props.style
4889
5316
  }
4890
5317
  );
4891
5318
  }
5319
+ function recordPlainVideoTelemetry(name, telemetry, sourceId, error) {
5320
+ recordIviTelemetrySpan(
5321
+ name,
5322
+ telemetry,
5323
+ {
5324
+ "ivi.source.id": sourceId,
5325
+ "ivi.media.kind": "video"
5326
+ },
5327
+ error
5328
+ );
5329
+ }
4892
5330
 
4893
5331
  // src/react/internal/use-multi-preload-sources.ts
4894
5332
  function useMultiPreloadSources(sources, activeSourceId) {