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

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,6 +1538,11 @@ 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
+ recordIviTelemetrySpan(
1542
+ "ivi.media.trtc.remote_video_available",
1543
+ this.telemetry,
1544
+ buildTrtcMediaTelemetryAttributes(session.sourceId, event.userId, event.streamType)
1545
+ );
1451
1546
  const remoteVideoKey = buildRemoteVideoKey(event.userId, String(event.streamType));
1452
1547
  session.remoteVideoStreams.add(remoteVideoKey);
1453
1548
  if (!session.hasEverReceivedRemoteVideo) {
@@ -1555,6 +1650,16 @@ var TrtcSourceManager = class {
1555
1650
  return;
1556
1651
  }
1557
1652
  binding.startingVideoKeys.add(remoteVideoKey);
1653
+ const telemetryAttributes = buildTrtcMediaTelemetryAttributes(
1654
+ binding.sourceId,
1655
+ userId,
1656
+ streamType
1657
+ );
1658
+ const telemetrySpan = startIviTelemetrySpan(
1659
+ "ivi.media.trtc.start_remote_video",
1660
+ this.telemetry,
1661
+ telemetryAttributes
1662
+ );
1558
1663
  try {
1559
1664
  this.log("info", `\u5F00\u59CB\u6E32\u67D3\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} view=${remoteVideoKey} userId=${userId} streamType=${streamType}`);
1560
1665
  await client.startRemoteVideo({
@@ -1563,14 +1668,24 @@ var TrtcSourceManager = class {
1563
1668
  view: binding.container,
1564
1669
  option: { fillMode: "contain" }
1565
1670
  });
1671
+ endIviTelemetrySpan(telemetrySpan.span);
1672
+ recordIviTelemetrySpan(
1673
+ "ivi.media.trtc.remote_video_rendered",
1674
+ this.telemetry,
1675
+ telemetryAttributes
1676
+ );
1566
1677
  this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5B8C\u6210 source=${binding.sourceId} userId=${userId} streamType=${streamType}`);
1567
1678
  binding.startedVideoKeys.add(remoteVideoKey);
1568
1679
  enforceContainMedia(binding.container);
1569
1680
  } catch (error) {
1570
1681
  if (error instanceof Error && /already started|OPERATION_ABORT/i.test(error.message)) {
1571
1682
  binding.startedVideoKeys.add(remoteVideoKey);
1683
+ endIviTelemetrySpan(telemetrySpan.span, {
1684
+ "ivi.media.trtc.start_remote_video.result": "already_started"
1685
+ });
1572
1686
  return;
1573
1687
  }
1688
+ failIviTelemetrySpan(telemetrySpan.span, error);
1574
1689
  this.log("warn", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5931\u8D25 userId=${userId} streamType=${streamType}`, error);
1575
1690
  } finally {
1576
1691
  binding.startingVideoKeys.delete(remoteVideoKey);
@@ -1784,6 +1899,13 @@ function getAIDenoiserModeValue(mode) {
1784
1899
  function buildRemoteVideoKey(userId, streamType) {
1785
1900
  return `${userId}::${streamType}`;
1786
1901
  }
1902
+ function buildTrtcMediaTelemetryAttributes(sourceId, userId, streamType) {
1903
+ return {
1904
+ "ivi.source.id": sourceId,
1905
+ "ivi.trtc.user_id": userId,
1906
+ "ivi.trtc.stream_type": String(streamType)
1907
+ };
1908
+ }
1787
1909
  function parseRemoteVideoKey(key) {
1788
1910
  const separatorIndex = key.indexOf("::");
1789
1911
  if (separatorIndex < 0) {
@@ -1846,9 +1968,10 @@ function describeLivekitRoom(livekit) {
1846
1968
  // src/runtime/managers/livekit-source-manager.ts
1847
1969
  var TAG2 = "[IVI-LIVEKIT]";
1848
1970
  var LivekitSourceManager = class {
1849
- constructor(onLog, onRemoteVideoAvailable) {
1971
+ constructor(onLog, onRemoteVideoAvailable, telemetry) {
1850
1972
  this.onLog = onLog;
1851
1973
  this.onRemoteVideoAvailable = onRemoteVideoAvailable;
1974
+ this.telemetry = telemetry;
1852
1975
  this.sessions = /* @__PURE__ */ new Map();
1853
1976
  this.listeners = /* @__PURE__ */ new Map();
1854
1977
  }
@@ -2070,6 +2193,15 @@ var LivekitSourceManager = class {
2070
2193
  kind
2071
2194
  });
2072
2195
  if (kind === "video" && !session.hasEverReceivedRemoteVideo) {
2196
+ recordIviTelemetrySpan(
2197
+ "ivi.media.livekit.remote_video_available",
2198
+ this.telemetry,
2199
+ {
2200
+ "ivi.source.id": session.sourceId,
2201
+ "ivi.livekit.participant_identity": participant.identity,
2202
+ "ivi.livekit.track_sid": track.sid
2203
+ }
2204
+ );
2073
2205
  session.hasEverReceivedRemoteVideo = true;
2074
2206
  for (const waiter of session.remoteVideoWaiters) {
2075
2207
  waiter(true);
@@ -2291,11 +2423,16 @@ var IviRuntimeCoordinator = class {
2291
2423
  this.trtcSourceManager = new TrtcSourceManager(
2292
2424
  this.config.onLog,
2293
2425
  (event) => this.onTrtcManagerEvent(event),
2294
- this.config.trtcAIDenoiser
2426
+ this.config.trtcAIDenoiser,
2427
+ this.config.telemetry
2428
+ );
2429
+ this.livekitSourceManager = new LivekitSourceManager(
2430
+ this.config.onLog,
2431
+ () => {
2432
+ this.recomposeBootstrapState();
2433
+ },
2434
+ this.config.telemetry
2295
2435
  );
2296
- this.livekitSourceManager = new LivekitSourceManager(this.config.onLog, () => {
2297
- this.recomposeBootstrapState();
2298
- });
2299
2436
  this.sessionHandler = new SessionEventHandler(this.sessionManager, {
2300
2437
  onSessionCreated: (event) => this.onSessionCreated(event),
2301
2438
  onSessionEnded: (event) => this.onSessionEnded(event)
@@ -2332,15 +2469,27 @@ var IviRuntimeCoordinator = class {
2332
2469
  * 启动后状态会从 idle/stopped 进入 connecting,连接成功后由事件驱动进入后续状态。
2333
2470
  */
2334
2471
  async start() {
2335
- this.setState({
2336
- ...this.state,
2337
- status: "connecting"
2472
+ const telemetrySpan = startIviTelemetrySpan("ivi.runtime.start", this.config.telemetry, {
2473
+ "ivi.runtime.status": this.state.status,
2474
+ "ivi.session.id": this.state.session?.id
2338
2475
  });
2339
- this.dispatcher.start();
2340
2476
  try {
2477
+ this.setState({
2478
+ ...this.state,
2479
+ status: "connecting"
2480
+ });
2481
+ this.dispatcher.start();
2341
2482
  await this.client.connect();
2483
+ endIviTelemetrySpan(telemetrySpan.span, {
2484
+ "ivi.runtime.status": this.state.status,
2485
+ "ivi.session.id": this.state.session?.id
2486
+ });
2342
2487
  } catch (error) {
2343
2488
  this.stop();
2489
+ failIviTelemetrySpan(telemetrySpan.span, error, {
2490
+ "ivi.runtime.status": this.state.status,
2491
+ "ivi.session.id": this.state.session?.id
2492
+ });
2344
2493
  throw error;
2345
2494
  }
2346
2495
  }
@@ -2348,13 +2497,31 @@ var IviRuntimeCoordinator = class {
2348
2497
  * 停止协调器并断开实时连接,同时重置运行态数据为 stopped。
2349
2498
  */
2350
2499
  stop() {
2351
- this.dispatcher.stop();
2352
- this.client.disconnect();
2353
- this.resetStoppedState();
2500
+ const telemetrySpan = startIviTelemetrySpan("ivi.runtime.stop", this.config.telemetry, {
2501
+ "ivi.runtime.status": this.state.status,
2502
+ "ivi.session.id": this.state.session?.id
2503
+ });
2504
+ try {
2505
+ this.dispatcher.stop();
2506
+ this.client.disconnect();
2507
+ this.resetStoppedState();
2508
+ endIviTelemetrySpan(telemetrySpan.span, {
2509
+ "ivi.runtime.status": this.state.status
2510
+ });
2511
+ } catch (error) {
2512
+ failIviTelemetrySpan(telemetrySpan.span, error, {
2513
+ "ivi.runtime.status": this.state.status,
2514
+ "ivi.session.id": this.state.session?.id
2515
+ });
2516
+ throw error;
2517
+ }
2354
2518
  }
2355
2519
  getState() {
2356
2520
  return this.state;
2357
2521
  }
2522
+ getTelemetryOptions() {
2523
+ return this.config.telemetry;
2524
+ }
2358
2525
  onStateChange(listener) {
2359
2526
  this.stateListeners.add(listener);
2360
2527
  return () => {
@@ -2645,7 +2812,16 @@ var IviRuntimeCoordinator = class {
2645
2812
  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)) {
2646
2813
  return;
2647
2814
  }
2815
+ const previousState = this.state;
2648
2816
  this.state = composedNextState;
2817
+ if (previousState.status !== composedNextState.status) {
2818
+ recordIviTelemetrySpan("ivi.runtime.state_change", this.config.telemetry, {
2819
+ "ivi.runtime.status.previous": previousState.status,
2820
+ "ivi.runtime.status.next": composedNextState.status,
2821
+ "ivi.runtime.state_change.reason": "state_update",
2822
+ "ivi.session.id": composedNextState.session?.id ?? previousState.session?.id
2823
+ });
2824
+ }
2649
2825
  this.stateListeners.forEach((listener) => listener(this.state));
2650
2826
  }
2651
2827
  composeBootstrapState(nextState) {
@@ -2928,44 +3104,101 @@ async function createIVISession(request, options) {
2928
3104
  if (!fetchImpl) {
2929
3105
  throw new Error("fetch is not available. Pass options.fetch to createIVISession.");
2930
3106
  }
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);
3107
+ const telemetrySpan = startIviTelemetrySpan(
3108
+ "ivi.session.create",
3109
+ options.telemetry,
3110
+ buildCreateSessionTelemetryAttributes(request)
3111
+ );
3112
+ try {
3113
+ const requestBody = toCpCreateIVISessionRequestBody(request);
3114
+ const headers = await buildHeaders(options.headers);
3115
+ injectIviTelemetryHeaders(headers, telemetrySpan.context);
3116
+ const response = await fetchImpl(resolveCreateIVISessionUrl(options), {
3117
+ method: "POST",
3118
+ headers,
3119
+ body: JSON.stringify(requestBody),
3120
+ credentials: options.credentials,
3121
+ signal: options.signal
3122
+ });
3123
+ const responseBody = await readJsonResponse(response);
3124
+ if (!response.ok) {
3125
+ const error = new IviCreateIVISessionError(response, responseBody);
3126
+ failIviTelemetrySpan(telemetrySpan.span, error, {
3127
+ "http.response.status_code": response.status,
3128
+ "http.response.status_text": response.statusText
3129
+ });
3130
+ throw error;
3131
+ }
3132
+ const result = normalizeCreateIVISessionResponse(responseBody, request, requestBody);
3133
+ endIviTelemetrySpan(telemetrySpan.span, {
3134
+ "http.response.status_code": response.status,
3135
+ "ivi.session.id": result.iviSessionId,
3136
+ "ivi.prebuild.playback.kind": result.prebuiltPlayback?.kind
3137
+ });
3138
+ return result;
3139
+ } catch (error) {
3140
+ if (!(error instanceof IviCreateIVISessionError)) {
3141
+ failIviTelemetrySpan(telemetrySpan.span, error);
3142
+ }
3143
+ throw error;
2942
3144
  }
2943
- return normalizeCreateIVISessionResponse(responseBody, request, requestBody);
2944
3145
  }
2945
3146
  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
- })
3147
+ const telemetrySpan = startIviTelemetrySpan("ivi.runtime.create", options.telemetry, {
3148
+ "ivi.session.id": session.iviSessionId,
3149
+ "ivi.prebuild.playback.kind": session.prebuiltPlayback?.kind
2955
3150
  });
2956
- const runtime = new IviRuntimeCoordinator(client, options.runtimeConfig);
2957
- if (options.fastStart !== false && session.prebuiltPlayback) {
2958
- runtime.setBootstrapSource(toBootstrapSource(session.prebuiltPlayback, options));
3151
+ try {
3152
+ const websocketUrl = buildWebSocketUrl(session.endpoint, options.websocket);
3153
+ const childTelemetry = withIviTelemetryContext(options.telemetry, telemetrySpan.context, {
3154
+ "ivi.session.id": session.iviSessionId
3155
+ });
3156
+ const clientTelemetry = mergeIviTelemetryOptions(
3157
+ options.clientConfig?.telemetry,
3158
+ childTelemetry
3159
+ );
3160
+ const runtimeTelemetry = mergeIviTelemetryOptions(
3161
+ options.runtimeConfig?.telemetry,
3162
+ childTelemetry
3163
+ );
3164
+ const client = new IviClient({
3165
+ ...options.clientConfig ?? {},
3166
+ telemetry: clientTelemetry,
3167
+ transport: new WebSocketTransport({
3168
+ url: websocketUrl,
3169
+ sessionId: session.iviSessionId,
3170
+ protocols: options.websocket?.protocols,
3171
+ socketFactory: options.websocket?.socketFactory
3172
+ })
3173
+ });
3174
+ const runtime = new IviRuntimeCoordinator(client, {
3175
+ ...options.runtimeConfig ?? {},
3176
+ telemetry: runtimeTelemetry
3177
+ });
3178
+ if (options.fastStart !== false && session.prebuiltPlayback) {
3179
+ runtime.setBootstrapSource(toBootstrapSource(session.prebuiltPlayback, options));
3180
+ }
3181
+ endIviTelemetrySpan(telemetrySpan.span);
3182
+ return {
3183
+ session,
3184
+ client,
3185
+ runtime
3186
+ };
3187
+ } catch (error) {
3188
+ failIviTelemetrySpan(telemetrySpan.span, error);
3189
+ throw error;
2959
3190
  }
2960
- return {
2961
- session,
2962
- client,
2963
- runtime
2964
- };
2965
3191
  }
2966
3192
  async function createIVISessionRuntime(request, options) {
2967
- const session = await createIVISession(request, options.http);
2968
- return createRuntimeFromSession(session, options);
3193
+ const telemetry = mergeIviTelemetryOptions(options.telemetry, options.http.telemetry);
3194
+ const session = await createIVISession(request, {
3195
+ ...options.http,
3196
+ telemetry
3197
+ });
3198
+ return createRuntimeFromSession(session, {
3199
+ ...options,
3200
+ telemetry
3201
+ });
2969
3202
  }
2970
3203
  function toCpCreateIVISessionRequestBody(request) {
2971
3204
  const body = { ...request };
@@ -3051,6 +3284,25 @@ function resolveCreateIVISessionUrl(options) {
3051
3284
  const path = options.path ?? "/v1/ivi/sessions";
3052
3285
  return new URL(path, ensureTrailingSlash(options.baseUrl)).toString();
3053
3286
  }
3287
+ function buildCreateSessionTelemetryAttributes(request) {
3288
+ return {
3289
+ "ivi.version": request.iviVersion,
3290
+ "ivi.stream.type": request.streamType,
3291
+ "ivi.prebuild.enabled": Boolean(
3292
+ request.prebuiltStream || request.prebuiltCharacters && request.prebuiltCharacters.length > 0
3293
+ )
3294
+ };
3295
+ }
3296
+ function mergeIviTelemetryOptions(explicit, inherited) {
3297
+ if (!explicit && !inherited) {
3298
+ return void 0;
3299
+ }
3300
+ return {
3301
+ tracer: explicit?.tracer ?? inherited?.tracer,
3302
+ context: explicit?.context ?? inherited?.context,
3303
+ attributes: mergeIviTelemetryAttributes(inherited?.attributes, explicit?.attributes)
3304
+ };
3305
+ }
3054
3306
  async function buildHeaders(headers) {
3055
3307
  const resolvedHeaders = typeof headers === "function" ? await headers() : headers;
3056
3308
  const next = new Headers(resolvedHeaders);
@@ -3059,6 +3311,14 @@ async function buildHeaders(headers) {
3059
3311
  }
3060
3312
  return next;
3061
3313
  }
3314
+ var headersTextMapSetter = {
3315
+ set(carrier, key, value) {
3316
+ carrier.set(key, value);
3317
+ }
3318
+ };
3319
+ function injectIviTelemetryHeaders(headers, context) {
3320
+ propagation.inject(context, headers, headersTextMapSetter);
3321
+ }
3062
3322
  async function readJsonResponse(response) {
3063
3323
  const text = await response.text();
3064
3324
  if (!text) {
@@ -3232,32 +3492,71 @@ var IviFrontendSdk = class {
3232
3492
  this.config = config;
3233
3493
  }
3234
3494
  createClient(config) {
3235
- return new IviClient(config);
3495
+ return new IviClient({
3496
+ ...config,
3497
+ telemetry: mergeSdkTelemetryOptions(config.telemetry, this.config.telemetry)
3498
+ });
3236
3499
  }
3237
3500
  createRuntimeCoordinator(clientConfig, runtimeConfig) {
3238
3501
  const client = this.createClient(clientConfig);
3239
- return new IviRuntimeCoordinator(client, runtimeConfig);
3502
+ return new IviRuntimeCoordinator(client, {
3503
+ ...runtimeConfig ?? {},
3504
+ telemetry: mergeSdkTelemetryOptions(runtimeConfig?.telemetry, this.config.telemetry)
3505
+ });
3240
3506
  }
3241
3507
  createIVISession(request, options) {
3508
+ const telemetry = mergeSdkTelemetryChain(
3509
+ this.config.telemetry,
3510
+ this.config.http?.telemetry,
3511
+ options?.telemetry
3512
+ );
3242
3513
  return createIVISession(request, {
3243
3514
  ...this.config.http ?? {},
3244
- ...options ?? {}
3515
+ ...options ?? {},
3516
+ telemetry
3245
3517
  });
3246
3518
  }
3247
3519
  createRuntimeFromSession(session, options) {
3248
- return createRuntimeFromSession(session, options);
3520
+ return createRuntimeFromSession(session, {
3521
+ ...options ?? {},
3522
+ telemetry: mergeSdkTelemetryChain(this.config.telemetry, options?.telemetry)
3523
+ });
3249
3524
  }
3250
3525
  createIVISessionRuntime(request, options) {
3526
+ const telemetry = mergeSdkTelemetryChain(
3527
+ this.config.telemetry,
3528
+ this.config.http?.telemetry,
3529
+ options?.http?.telemetry,
3530
+ options?.telemetry
3531
+ );
3251
3532
  const http = {
3252
3533
  ...this.config.http ?? {},
3253
- ...options?.http ?? {}
3534
+ ...options?.http ?? {},
3535
+ telemetry
3254
3536
  };
3255
3537
  return createIVISessionRuntime(request, {
3256
3538
  ...options ?? {},
3539
+ telemetry,
3257
3540
  http
3258
3541
  });
3259
3542
  }
3260
3543
  };
3544
+ function mergeSdkTelemetryChain(...items) {
3545
+ return items.reduce(
3546
+ (merged, item) => mergeSdkTelemetryOptions(item, merged),
3547
+ void 0
3548
+ );
3549
+ }
3550
+ function mergeSdkTelemetryOptions(explicit, inherited) {
3551
+ if (!explicit && !inherited) {
3552
+ return void 0;
3553
+ }
3554
+ return {
3555
+ tracer: explicit?.tracer ?? inherited?.tracer,
3556
+ context: explicit?.context ?? inherited?.context,
3557
+ attributes: mergeIviTelemetryAttributes(inherited?.attributes, explicit?.attributes)
3558
+ };
3559
+ }
3261
3560
  var IviStageViewContext = createContext(null);
3262
3561
  var EMPTY_RUNTIME_STATE = {
3263
3562
  status: "idle",
@@ -4458,7 +4757,16 @@ function makeLoadPolicy(label, onLog) {
4458
4757
  };
4459
4758
  }
4460
4759
  function IVIHlsVideo(props) {
4461
- const { url, videoProps, style, aggressivePreload = false, paused = false, onLog } = props;
4760
+ const {
4761
+ url,
4762
+ sourceId,
4763
+ videoProps,
4764
+ style,
4765
+ aggressivePreload = false,
4766
+ paused = false,
4767
+ onLog,
4768
+ telemetry
4769
+ } = props;
4462
4770
  const videoRef = useRef(null);
4463
4771
  const pausedRef = useRef(paused);
4464
4772
  useEffect(() => {
@@ -4495,6 +4803,7 @@ function IVIHlsVideo(props) {
4495
4803
  const onVideoError = () => {
4496
4804
  const el = videoRef.current;
4497
4805
  const mediaErr = el?.error;
4806
+ recordVideoTelemetry("ivi.media.video.error", telemetry, sourceId, "hls", mediaErr);
4498
4807
  emitHlsLog(onLog, "warn", "<video> \u5143\u7D20\u62A5\u9519", {
4499
4808
  code: mediaErr?.code,
4500
4809
  message: mediaErr?.message,
@@ -4504,6 +4813,13 @@ function IVIHlsVideo(props) {
4504
4813
  });
4505
4814
  };
4506
4815
  const onVideoStalled = () => {
4816
+ recordVideoTelemetry(
4817
+ "ivi.media.video.stalled",
4818
+ telemetry,
4819
+ sourceId,
4820
+ "hls",
4821
+ new Error("<video> stalled")
4822
+ );
4507
4823
  emitHlsLog(onLog, "warn", "<video> stalled\uFF08\u7F13\u51B2\u505C\u6EDE\uFF09", {
4508
4824
  currentTime: videoRef.current?.currentTime,
4509
4825
  readyState: videoRef.current?.readyState
@@ -4646,7 +4962,7 @@ function IVIHlsVideo(props) {
4646
4962
  hlsInstance?.destroy();
4647
4963
  hlsInstance = null;
4648
4964
  };
4649
- }, [url, aggressivePreload, onLog]);
4965
+ }, [url, sourceId, aggressivePreload, onLog, telemetry]);
4650
4966
  return /* @__PURE__ */ jsx(
4651
4967
  "video",
4652
4968
  {
@@ -4659,6 +4975,17 @@ function IVIHlsVideo(props) {
4659
4975
  }
4660
4976
  );
4661
4977
  }
4978
+ function recordVideoTelemetry(name, telemetry, sourceId, kind, error) {
4979
+ recordIviTelemetrySpan(
4980
+ name,
4981
+ telemetry,
4982
+ {
4983
+ "ivi.source.id": sourceId,
4984
+ "ivi.media.kind": kind
4985
+ },
4986
+ error
4987
+ );
4988
+ }
4662
4989
  function isM3u8Url(url) {
4663
4990
  return /\.m3u8(?:$|[?#])/i.test(url);
4664
4991
  }
@@ -4724,6 +5051,7 @@ function TrackSlotMediaContent(props) {
4724
5051
  source,
4725
5052
  isPreloading: !isActive
4726
5053
  };
5054
+ const telemetry = runtime?.getTelemetryOptions();
4727
5055
  const mediaStyle = buildAdaptiveMediaStyle(adaptToSourceSize, fitStrategy);
4728
5056
  const shouldMute = !isActive;
4729
5057
  if (renderMedia) return renderMedia(renderContext);
@@ -4795,9 +5123,11 @@ function TrackSlotMediaContent(props) {
4795
5123
  IVIHlsVideo,
4796
5124
  {
4797
5125
  url: playbackUrl,
5126
+ sourceId: slotSourceId,
4798
5127
  videoProps: mergedVideoProps,
4799
5128
  style: videoStyle,
4800
- paused: shouldPause
5129
+ paused: shouldPause,
5130
+ telemetry
4801
5131
  }
4802
5132
  );
4803
5133
  }
@@ -4819,6 +5149,7 @@ function TrackSlotMediaContent(props) {
4819
5149
  SlotVideo,
4820
5150
  {
4821
5151
  src: playbackUrl,
5152
+ sourceId: slotSourceId,
4822
5153
  paused: shouldPause,
4823
5154
  videoProps: {
4824
5155
  ...mergedVideoProps,
@@ -4826,7 +5157,8 @@ function TrackSlotMediaContent(props) {
4826
5157
  playsInline: videoProps?.playsInline ?? true,
4827
5158
  controls: isActive ? videoProps?.controls ?? true : false
4828
5159
  },
4829
- style: videoStyle
5160
+ style: videoStyle,
5161
+ telemetry
4830
5162
  }
4831
5163
  );
4832
5164
  }
@@ -4876,17 +5208,48 @@ function SlotVideo(props) {
4876
5208
  video.play().catch(() => void 0);
4877
5209
  }
4878
5210
  }, [props.paused]);
5211
+ const onError = (event) => {
5212
+ recordPlainVideoTelemetry(
5213
+ "ivi.media.video.error",
5214
+ props.telemetry,
5215
+ props.sourceId,
5216
+ event.currentTarget.error
5217
+ );
5218
+ props.videoProps.onError?.(event);
5219
+ };
5220
+ const onStalled = (event) => {
5221
+ recordPlainVideoTelemetry(
5222
+ "ivi.media.video.stalled",
5223
+ props.telemetry,
5224
+ props.sourceId,
5225
+ new Error("<video> stalled")
5226
+ );
5227
+ props.videoProps.onStalled?.(event);
5228
+ };
4879
5229
  return /* @__PURE__ */ jsx(
4880
5230
  "video",
4881
5231
  {
4882
5232
  ref: videoRef,
4883
5233
  src: props.src,
4884
5234
  ...props.videoProps,
5235
+ onError,
5236
+ onStalled,
4885
5237
  autoPlay: props.paused ? false : props.videoProps.autoPlay ?? true,
4886
5238
  style: props.style
4887
5239
  }
4888
5240
  );
4889
5241
  }
5242
+ function recordPlainVideoTelemetry(name, telemetry, sourceId, error) {
5243
+ recordIviTelemetrySpan(
5244
+ name,
5245
+ telemetry,
5246
+ {
5247
+ "ivi.source.id": sourceId,
5248
+ "ivi.media.kind": "video"
5249
+ },
5250
+ error
5251
+ );
5252
+ }
4890
5253
 
4891
5254
  // src/react/internal/use-multi-preload-sources.ts
4892
5255
  function useMultiPreloadSources(sources, activeSourceId) {