@xmaxai/sdk 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +11 -2
  2. package/README.md +19 -5
  3. package/dist/{chunk-LCPC56BK.js → chunk-B5EAQTBY.js} +497 -136
  4. package/dist/chunk-B5EAQTBY.js.map +1 -0
  5. package/dist/{chunk-V4NR6CPP.js → chunk-ZMLV7YQP.js} +24 -184
  6. package/dist/chunk-ZMLV7YQP.js.map +1 -0
  7. package/dist/index.cjs +53 -273
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +380 -8
  10. package/dist/index.d.ts +380 -8
  11. package/dist/index.js +1004 -9
  12. package/dist/index.js.map +1 -1
  13. package/dist/react/index.cjs +30 -271
  14. package/dist/react/index.cjs.map +1 -1
  15. package/dist/react/index.d.cts +1 -3
  16. package/dist/react/index.d.ts +1 -3
  17. package/dist/react/index.js +6 -26
  18. package/dist/react/index.js.map +1 -1
  19. package/dist/{rtc-manager-gYDkADGw.d.ts → rtc-manager-Bk2lIJ0d.d.cts} +7 -17
  20. package/dist/{rtc-manager-gYDkADGw.d.cts → rtc-manager-Bk2lIJ0d.d.ts} +7 -17
  21. package/dist/video-file-stream-OVPBV7IX.js +3 -0
  22. package/dist/{video-file-stream-K7GQMIJ5.js.map → video-file-stream-OVPBV7IX.js.map} +1 -1
  23. package/package.json +1 -6
  24. package/dist/chunk-4YHBCQ2C.js +0 -432
  25. package/dist/chunk-4YHBCQ2C.js.map +0 -1
  26. package/dist/chunk-LCPC56BK.js.map +0 -1
  27. package/dist/chunk-V4NR6CPP.js.map +0 -1
  28. package/dist/chunk-VLJZ56PT.js +0 -997
  29. package/dist/chunk-VLJZ56PT.js.map +0 -1
  30. package/dist/experimental/index.cjs +0 -3897
  31. package/dist/experimental/index.cjs.map +0 -1
  32. package/dist/experimental/index.d.cts +0 -2
  33. package/dist/experimental/index.d.ts +0 -2
  34. package/dist/experimental/index.js +0 -5
  35. package/dist/experimental/index.js.map +0 -1
  36. package/dist/index-Cw8C8LbO.d.ts +0 -367
  37. package/dist/index-Dg1ElHAj.d.cts +0 -367
  38. package/dist/video-file-stream-K7GQMIJ5.js +0 -4
@@ -1,40 +1,3 @@
1
- // src/i18n/api-error-codes.ts
2
- var SYSTEM_OVERLOADED = {
3
- "zh-CN": "\u5F53\u524D\u4F7F\u7528\u4EBA\u6570\u8FC7\u591A\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
4
- "en-US": "The system is currently overloaded. Please retry after some time."
5
- };
6
- var API_ERROR_MESSAGES = {
7
- "46001": { "zh-CN": "X-Api-Key \u4E0D\u80FD\u4E3A\u7A7A", "en-US": "X-Api-Key cannot be empty" },
8
- "46002": { "zh-CN": "\u65E0\u6548\u7684\u7528\u6237 API Key", "en-US": "Invalid user API Key" },
9
- "46003": { "zh-CN": "model \u4E0D\u80FD\u4E3A\u7A7A", "en-US": "model cannot be empty" },
10
- "46004": { "zh-CN": "sessionUid \u4E0D\u80FD\u4E3A\u7A7A", "en-US": "sessionUid cannot be empty" },
11
- "46005": { "zh-CN": "\u6A21\u578B\u7C7B\u578B\u4E0D\u5B58\u5728", "en-US": "Model type does not exist" },
12
- "46006": SYSTEM_OVERLOADED,
13
- "46007": SYSTEM_OVERLOADED,
14
- "46008": SYSTEM_OVERLOADED,
15
- "46009": SYSTEM_OVERLOADED,
16
- "46010": SYSTEM_OVERLOADED,
17
- "46012": { "zh-CN": "\u4F1A\u8BDD\u4E0D\u5B58\u5728", "en-US": "Session does not exist" },
18
- "46013": { "zh-CN": "\u4F1A\u8BDD\u4E0D\u5C5E\u4E8E\u5F53\u524D\u7528\u6237", "en-US": "Session does not belong to the current user" },
19
- "46014": { "zh-CN": "\u4F1A\u8BDD\u5DF2\u5173\u95ED", "en-US": "Session has been closed" },
20
- "46015": { "zh-CN": "\u4F1A\u8BDD\u5DF2\u8D85\u65F6", "en-US": "Session has timed out" },
21
- "46018": { "zh-CN": "\u4E34\u65F6 API Key \u5DF2\u8FC7\u671F", "en-US": "Temporary API Key has expired" },
22
- "46019": {
23
- "zh-CN": "\u4E34\u65F6 API Key \u5DF2\u8FBE\u5230\u79EF\u5206\u4E0A\u9650",
24
- "en-US": "Temporary API Key has reached its points limit"
25
- },
26
- "46021": {
27
- "zh-CN": "\u4E34\u65F6 API Key \u4E0D\u5141\u8BB8\u518D\u521B\u5EFA\u4E34\u65F6 API Key",
28
- "en-US": "A temporary API Key cannot create another temporary API Key"
29
- }
30
- };
31
- function resolveApiErrorMessage(code, locale) {
32
- if (code === null || code === void 0) return void 0;
33
- const entry = API_ERROR_MESSAGES[String(code)];
34
- if (!entry) return void 0;
35
- return entry[locale] ?? entry["en-US"];
36
- }
37
-
38
1
  // src/i18n/en-US.ts
39
2
  var enUS = {
40
3
  "errors.apiKeyEmpty": "apiKey cannot be empty",
@@ -50,6 +13,8 @@ var enUS = {
50
13
  "errors.sessionJoinInfoIncomplete": "session does not contain complete RTC join info",
51
14
  "errors.sessionUidMissing": "session does not contain sessionUid",
52
15
  "errors.refImageMissing": "missing reference image",
16
+ "errors.imageCheckMissingUrl": "image check response missing url",
17
+ "errors.imageCheckUnsafe": "image did not pass safety check",
53
18
  "errors.captureStreamUnsupported": "captureStream is not supported in this browser",
54
19
  "errors.videoTrackMissing": "failed to create video track from file",
55
20
  "errors.imageTrackMissing": "failed to create video track from image file",
@@ -106,6 +71,8 @@ var zhCN = {
106
71
  "errors.sessionJoinInfoIncomplete": "session \u7F3A\u5C11\u5B8C\u6574\u7684 RTC \u8FDB\u623F\u4FE1\u606F",
107
72
  "errors.sessionUidMissing": "session \u7F3A\u5C11 sessionUid",
108
73
  "errors.refImageMissing": "\u7F3A\u5C11\u53C2\u8003\u56FE",
74
+ "errors.imageCheckMissingUrl": "\u56FE\u7247\u68C0\u67E5\u54CD\u5E94\u7F3A\u5C11 url",
75
+ "errors.imageCheckUnsafe": "\u56FE\u7247\u672A\u901A\u8FC7\u5B89\u5168\u68C0\u67E5",
109
76
  "errors.captureStreamUnsupported": "\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301 captureStream",
110
77
  "errors.videoTrackMissing": "\u672A\u80FD\u4ECE\u89C6\u9891\u6587\u4EF6\u521B\u5EFA video track",
111
78
  "errors.imageTrackMissing": "\u672A\u80FD\u4ECE\u56FE\u7247\u6587\u4EF6\u521B\u5EFA video track",
@@ -147,6 +114,43 @@ var zhCN = {
147
114
  "rtc.getCodecsFailed": "\u83B7\u53D6\u6D4F\u89C8\u5668\u7F16\u89E3\u7801\u652F\u6301\u5931\u8D25\uFF0CSEI gate \u5C06\u4E0D\u4F1A\u751F\u6548\uFF0Coutput \u5C06\u4E0D\u518D\u88AB\u62E6\u622A"
148
115
  };
149
116
 
117
+ // src/i18n/api-error-codes.ts
118
+ var SYSTEM_OVERLOADED = {
119
+ "zh-CN": "\u5F53\u524D\u4F7F\u7528\u4EBA\u6570\u8FC7\u591A\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
120
+ "en-US": "The system is currently overloaded. Please retry after some time."
121
+ };
122
+ var API_ERROR_MESSAGES = {
123
+ "46001": { "zh-CN": "X-Api-Key \u4E0D\u80FD\u4E3A\u7A7A", "en-US": "X-Api-Key cannot be empty" },
124
+ "46002": { "zh-CN": "\u65E0\u6548\u7684\u7528\u6237 API Key", "en-US": "Invalid user API Key" },
125
+ "46003": { "zh-CN": "model \u4E0D\u80FD\u4E3A\u7A7A", "en-US": "model cannot be empty" },
126
+ "46004": { "zh-CN": "sessionUid \u4E0D\u80FD\u4E3A\u7A7A", "en-US": "sessionUid cannot be empty" },
127
+ "46005": { "zh-CN": "\u6A21\u578B\u7C7B\u578B\u4E0D\u5B58\u5728", "en-US": "Model type does not exist" },
128
+ "46006": SYSTEM_OVERLOADED,
129
+ "46007": SYSTEM_OVERLOADED,
130
+ "46008": SYSTEM_OVERLOADED,
131
+ "46009": SYSTEM_OVERLOADED,
132
+ "46010": SYSTEM_OVERLOADED,
133
+ "46012": { "zh-CN": "\u4F1A\u8BDD\u4E0D\u5B58\u5728", "en-US": "Session does not exist" },
134
+ "46013": { "zh-CN": "\u4F1A\u8BDD\u4E0D\u5C5E\u4E8E\u5F53\u524D\u7528\u6237", "en-US": "Session does not belong to the current user" },
135
+ "46014": { "zh-CN": "\u4F1A\u8BDD\u5DF2\u5173\u95ED", "en-US": "Session has been closed" },
136
+ "46015": { "zh-CN": "\u4F1A\u8BDD\u5DF2\u8D85\u65F6", "en-US": "Session has timed out" },
137
+ "46018": { "zh-CN": "\u4E34\u65F6 API Key \u5DF2\u8FC7\u671F", "en-US": "Temporary API Key has expired" },
138
+ "46019": {
139
+ "zh-CN": "\u4E34\u65F6 API Key \u5DF2\u8FBE\u5230\u79EF\u5206\u4E0A\u9650",
140
+ "en-US": "Temporary API Key has reached its points limit"
141
+ },
142
+ "46021": {
143
+ "zh-CN": "\u4E34\u65F6 API Key \u4E0D\u5141\u8BB8\u518D\u521B\u5EFA\u4E34\u65F6 API Key",
144
+ "en-US": "A temporary API Key cannot create another temporary API Key"
145
+ }
146
+ };
147
+ function resolveApiErrorMessage(code, locale) {
148
+ if (code === null || code === void 0) return void 0;
149
+ const entry = API_ERROR_MESSAGES[String(code)];
150
+ if (!entry) return void 0;
151
+ return entry[locale] ?? entry["en-US"];
152
+ }
153
+
150
154
  // src/i18n/index.ts
151
155
  function formatTemplate(template, params) {
152
156
  if (!params) return template;
@@ -174,6 +178,167 @@ function createSDKI18n(input) {
174
178
  };
175
179
  }
176
180
 
181
+ // src/rtc/publish-fps.ts
182
+ var MOBILE_PUBLISH_MAX_WIDTH_PX = 768;
183
+ var RTC_PUBLISH_FPS_WEB = 30;
184
+ var RTC_PUBLISH_FPS_MOBILE = 24;
185
+ function isMobilePublishEnvironment() {
186
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
187
+ return false;
188
+ }
189
+ return window.matchMedia(`(max-width: ${MOBILE_PUBLISH_MAX_WIDTH_PX}px)`).matches;
190
+ }
191
+ function resolveRtcPublishFps(options = {}) {
192
+ if (typeof options.fps === "number" && Number.isFinite(options.fps) && options.fps > 0) {
193
+ return options.fps;
194
+ }
195
+ const mobile = options.mobile ?? isMobilePublishEnvironment();
196
+ return mobile ? RTC_PUBLISH_FPS_MOBILE : RTC_PUBLISH_FPS_WEB;
197
+ }
198
+ function resolveRtcPublishFrameIntervalS(options) {
199
+ return 1 / resolveRtcPublishFps(options);
200
+ }
201
+ var RTC_PUBLISH_FRAME_INTERVAL_S = 1 / RTC_PUBLISH_FPS_WEB;
202
+ var RTC_PUBLISH_FRAME_INTERVAL_MS = 1e3 / RTC_PUBLISH_FPS_WEB;
203
+ var MEDIA_TIME_EPSILON_S = 1e-3;
204
+ var MEDIA_TIME_LOOP_JUMP_S = 0.05;
205
+
206
+ // src/shared/reference-video-size.ts
207
+ var PREVIEW_CONTAINER_ASPECT_RATIO = 16 / 9;
208
+ function resolvePreviewAspectRatio(size) {
209
+ return `${size[0]} / ${size[1]}`;
210
+ }
211
+ var DEFAULT_SESSION_TARGET_SIZE = [1472, 832];
212
+ var UPLOAD_VIDEO_BASE = 832;
213
+ var UPLOAD_SIZE_ALIGN = 32;
214
+ var MAX_LANDSCAPE_ASPECT = 16 / 9;
215
+ var MAX_PORTRAIT_HEIGHT_RATIO = 16 / 9;
216
+ function alignToSizeMultiple(value) {
217
+ return Math.max(UPLOAD_SIZE_ALIGN, Math.round(value / UPLOAD_SIZE_ALIGN) * UPLOAD_SIZE_ALIGN);
218
+ }
219
+ function makeEven(value) {
220
+ const rounded = Math.max(2, Math.round(value));
221
+ return rounded % 2 === 0 ? rounded : rounded - 1;
222
+ }
223
+ function resolveUploadPublishSize(sourceWidth, sourceHeight) {
224
+ if (sourceWidth <= 0 || sourceHeight <= 0) {
225
+ return DEFAULT_SESSION_TARGET_SIZE;
226
+ }
227
+ return [makeEven(sourceWidth), makeEven(sourceHeight)];
228
+ }
229
+ function resolveAdaptiveRtcVideoSize(sourceWidth, sourceHeight, baseSize = DEFAULT_SESSION_TARGET_SIZE) {
230
+ if (sourceWidth <= 0 || sourceHeight <= 0) {
231
+ return baseSize;
232
+ }
233
+ const [baseWidth, baseHeight] = baseSize;
234
+ const sourceAspectRatio = sourceHeight / sourceWidth;
235
+ const baseAspectRatio = baseHeight / baseWidth;
236
+ if (sourceAspectRatio >= baseAspectRatio) {
237
+ return [baseWidth, baseHeight];
238
+ }
239
+ const adaptiveHeight = Math.max(
240
+ UPLOAD_SIZE_ALIGN,
241
+ Math.round(baseWidth * sourceAspectRatio)
242
+ );
243
+ return [baseWidth, makeEven(adaptiveHeight)];
244
+ }
245
+ function resolveUploadVideoSize(sourceWidth, sourceHeight) {
246
+ if (sourceWidth <= 0 || sourceHeight <= 0) {
247
+ return DEFAULT_SESSION_TARGET_SIZE;
248
+ }
249
+ if (sourceWidth === sourceHeight) {
250
+ return [UPLOAD_VIDEO_BASE, UPLOAD_VIDEO_BASE];
251
+ }
252
+ const aspect = sourceWidth / sourceHeight;
253
+ if (sourceHeight > sourceWidth) {
254
+ const width2 = UPLOAD_VIDEO_BASE;
255
+ let height2 = alignToSizeMultiple(width2 / aspect);
256
+ const maxHeight = alignToSizeMultiple(width2 * MAX_PORTRAIT_HEIGHT_RATIO);
257
+ if (height2 > maxHeight) {
258
+ height2 = maxHeight;
259
+ }
260
+ return [width2, height2];
261
+ }
262
+ const height = UPLOAD_VIDEO_BASE;
263
+ let width = alignToSizeMultiple(height * aspect);
264
+ const maxWidth = alignToSizeMultiple(height * MAX_LANDSCAPE_ASPECT);
265
+ if (width > maxWidth) {
266
+ width = maxWidth;
267
+ }
268
+ return [width, height];
269
+ }
270
+ function resolveSessionTargetSize(input) {
271
+ if (input.overrideSize) {
272
+ return input.overrideSize;
273
+ }
274
+ if (input.localVideoSize?.width && input.localVideoSize?.height) {
275
+ return [input.localVideoSize.width, input.localVideoSize.height];
276
+ }
277
+ return DEFAULT_SESSION_TARGET_SIZE;
278
+ }
279
+
280
+ // src/rtc/capture-crop.ts
281
+ function computeCenterCropRect(sourceWidth, sourceHeight, targetAspect) {
282
+ const sourceAspect = sourceWidth / sourceHeight;
283
+ if (sourceAspect > targetAspect) {
284
+ const height2 = sourceHeight;
285
+ const width2 = sourceHeight * targetAspect;
286
+ return {
287
+ x: (sourceWidth - width2) / 2,
288
+ y: 0,
289
+ width: width2,
290
+ height: height2
291
+ };
292
+ }
293
+ const width = sourceWidth;
294
+ const height = sourceWidth / targetAspect;
295
+ return {
296
+ x: 0,
297
+ y: (sourceHeight - height) / 2,
298
+ width,
299
+ height
300
+ };
301
+ }
302
+
303
+ // src/rtc/canvas-frame-draw.ts
304
+ function drawSourceCenterCrop(ctx, source, outputWidth, outputHeight, sourceWidth, sourceHeight) {
305
+ ctx.imageSmoothingEnabled = true;
306
+ ctx.imageSmoothingQuality = "high";
307
+ ctx.clearRect(0, 0, outputWidth, outputHeight);
308
+ const crop = computeCenterCropRect(
309
+ sourceWidth,
310
+ sourceHeight,
311
+ outputWidth / outputHeight
312
+ );
313
+ ctx.drawImage(
314
+ source,
315
+ crop.x,
316
+ crop.y,
317
+ crop.width,
318
+ crop.height,
319
+ 0,
320
+ 0,
321
+ outputWidth,
322
+ outputHeight
323
+ );
324
+ }
325
+
326
+ // src/rtc/capture-host.ts
327
+ var captureHostRoot = null;
328
+ var CAPTURE_VIDEO_STYLE = "position:fixed;left:-10000px;top:0;width:640px;height:480px;opacity:1;pointer-events:none;z-index:-1;";
329
+ function mountCaptureVideo(videoEl) {
330
+ if (!captureHostRoot) {
331
+ captureHostRoot = document.createElement("div");
332
+ captureHostRoot.setAttribute("data-xmax-capture-host", "true");
333
+ captureHostRoot.style.cssText = "position:fixed;left:0;top:0;width:0;height:0;overflow:hidden;pointer-events:none;z-index:-1;";
334
+ document.body.appendChild(captureHostRoot);
335
+ }
336
+ videoEl.setAttribute("playsinline", "true");
337
+ videoEl.setAttribute("webkit-playsinline", "true");
338
+ videoEl.style.cssText = CAPTURE_VIDEO_STYLE;
339
+ captureHostRoot.appendChild(videoEl);
340
+ }
341
+
177
342
  // src/rtc/video-frame-rate.ts
178
343
  var COMMON_VIDEO_FPS = [24, 25, 30, 48, 50, 60];
179
344
  function median(values) {
@@ -293,122 +458,318 @@ function resolvePlaybackDrivenEncoderFps(track, fallback = 24) {
293
458
  return resolveTrackFrameRate(track, fallback);
294
459
  }
295
460
 
296
- // src/rtc/publish-fps.ts
297
- var MOBILE_PUBLISH_MAX_WIDTH_PX = 768;
298
- var RTC_PUBLISH_FPS_WEB = 30;
299
- var RTC_PUBLISH_FPS_MOBILE = 24;
300
- var RTC_PUBLISH_FPS = RTC_PUBLISH_FPS_WEB;
301
- function isMobilePublishEnvironment() {
302
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
461
+ // src/rtc/video-file-stream.ts
462
+ var STALL_CATCHUP_MS = 1500;
463
+ function shouldSampleAtMediaTime(lastMediaTimeS, mediaTimeS) {
464
+ if (!Number.isFinite(mediaTimeS) || mediaTimeS < 0) {
303
465
  return false;
304
466
  }
305
- return window.matchMedia(`(max-width: ${MOBILE_PUBLISH_MAX_WIDTH_PX}px)`).matches;
306
- }
307
- function resolveRtcPublishFps(options = {}) {
308
- if (typeof options.fps === "number" && Number.isFinite(options.fps) && options.fps > 0) {
309
- return options.fps;
467
+ if (lastMediaTimeS < 0) {
468
+ return true;
310
469
  }
311
- const mobile = options.mobile ?? isMobilePublishEnvironment();
312
- return mobile ? RTC_PUBLISH_FPS_MOBILE : RTC_PUBLISH_FPS_WEB;
313
- }
314
- function resolveRtcPublishFrameIntervalS(options) {
315
- return 1 / resolveRtcPublishFps(options);
316
- }
317
- var RTC_PUBLISH_FRAME_INTERVAL_S = 1 / RTC_PUBLISH_FPS_WEB;
318
- var RTC_PUBLISH_FRAME_INTERVAL_MS = 1e3 / RTC_PUBLISH_FPS_WEB;
319
- var MEDIA_TIME_EPSILON_S = 1e-3;
320
- var MEDIA_TIME_LOOP_JUMP_S = 0.05;
321
-
322
- // src/shared/reference-video-size.ts
323
- var PREVIEW_CONTAINER_ASPECT_RATIO = 16 / 9;
324
- function resolvePreviewAspectRatio(size) {
325
- return `${size[0]} / ${size[1]}`;
326
- }
327
- var DEFAULT_SESSION_TARGET_SIZE = [1472, 832];
328
- var UPLOAD_VIDEO_BASE = 832;
329
- var UPLOAD_SIZE_ALIGN = 32;
330
- var MAX_LANDSCAPE_ASPECT = 16 / 9;
331
- var MAX_PORTRAIT_HEIGHT_RATIO = 16 / 9;
332
- function alignToSizeMultiple(value) {
333
- return Math.max(UPLOAD_SIZE_ALIGN, Math.round(value / UPLOAD_SIZE_ALIGN) * UPLOAD_SIZE_ALIGN);
334
- }
335
- function makeEven(value) {
336
- const rounded = Math.max(2, Math.round(value));
337
- return rounded % 2 === 0 ? rounded : rounded - 1;
338
- }
339
- function resolveUploadPublishSize(sourceWidth, sourceHeight) {
340
- if (sourceWidth <= 0 || sourceHeight <= 0) {
341
- return DEFAULT_SESSION_TARGET_SIZE;
470
+ if (mediaTimeS + MEDIA_TIME_LOOP_JUMP_S < lastMediaTimeS) {
471
+ return true;
342
472
  }
343
- return [makeEven(sourceWidth), makeEven(sourceHeight)];
473
+ return mediaTimeS > lastMediaTimeS + MEDIA_TIME_EPSILON_S;
344
474
  }
345
- function resolveAdaptiveRtcVideoSize(sourceWidth, sourceHeight, baseSize = DEFAULT_SESSION_TARGET_SIZE) {
346
- if (sourceWidth <= 0 || sourceHeight <= 0) {
347
- return baseSize;
475
+ async function applyPublishTrackConstraints(track, width, height) {
476
+ if (typeof track.applyConstraints !== "function") {
477
+ return;
348
478
  }
349
- const [baseWidth, baseHeight] = baseSize;
350
- const sourceAspectRatio = sourceHeight / sourceWidth;
351
- const baseAspectRatio = baseHeight / baseWidth;
352
- if (sourceAspectRatio >= baseAspectRatio) {
353
- return [baseWidth, baseHeight];
479
+ try {
480
+ await track.applyConstraints({
481
+ width: { ideal: width, max: width },
482
+ height: { ideal: height, max: height }
483
+ });
484
+ } catch {
354
485
  }
355
- const adaptiveHeight = Math.max(
356
- UPLOAD_SIZE_ALIGN,
357
- Math.round(baseWidth * sourceAspectRatio)
358
- );
359
- return [baseWidth, makeEven(adaptiveHeight)];
360
486
  }
361
- function resolveUploadVideoSize(sourceWidth, sourceHeight) {
362
- if (sourceWidth <= 0 || sourceHeight <= 0) {
363
- return DEFAULT_SESSION_TARGET_SIZE;
487
+ async function createVideoFileStream(fileOrUrl, options = {}) {
488
+ const i18n = options.i18n ?? createSDKI18n();
489
+ const isUrl = typeof fileOrUrl === "string";
490
+ const url = isUrl ? fileOrUrl : URL.createObjectURL(fileOrUrl);
491
+ const videoEl = document.createElement("video");
492
+ videoEl.muted = options.muted ?? true;
493
+ videoEl.playsInline = true;
494
+ if (isUrl) {
495
+ videoEl.crossOrigin = "anonymous";
364
496
  }
365
- if (sourceWidth === sourceHeight) {
366
- return [UPLOAD_VIDEO_BASE, UPLOAD_VIDEO_BASE];
497
+ videoEl.src = url;
498
+ videoEl.loop = options.loop ?? true;
499
+ videoEl.autoplay = true;
500
+ mountCaptureVideo(videoEl);
501
+ try {
502
+ videoEl.load();
503
+ await waitForEvent(videoEl, "loadedmetadata");
504
+ await waitForCanPlay(videoEl);
505
+ } catch (error) {
506
+ if (!isUrl) URL.revokeObjectURL(url);
507
+ throw error;
367
508
  }
368
- const aspect = sourceWidth / sourceHeight;
369
- if (sourceHeight > sourceWidth) {
370
- const width2 = UPLOAD_VIDEO_BASE;
371
- let height2 = alignToSizeMultiple(width2 / aspect);
372
- const maxHeight = alignToSizeMultiple(width2 * MAX_PORTRAIT_HEIGHT_RATIO);
373
- if (height2 > maxHeight) {
374
- height2 = maxHeight;
509
+ if (typeof options.playbackRate === "number") {
510
+ videoEl.playbackRate = options.playbackRate;
511
+ }
512
+ await startVideoPlayback(videoEl, i18n);
513
+ const sourceWidth = videoEl.videoWidth || 0;
514
+ const sourceHeight = videoEl.videoHeight || 0;
515
+ if (!sourceWidth || !sourceHeight) {
516
+ if (!isUrl) URL.revokeObjectURL(url);
517
+ throw new Error(i18n.t("errors.videoTrackMissing"));
518
+ }
519
+ const measuredFps = typeof options.fps === "number" && options.fps > 0 ? options.fps : await estimateVideoFrameRate(videoEl);
520
+ const [outputWidth, outputHeight] = options.targetSize ?? resolveUploadVideoSize(sourceWidth, sourceHeight);
521
+ const canvas = document.createElement("canvas");
522
+ canvas.width = outputWidth;
523
+ canvas.height = outputHeight;
524
+ const ctx = canvas.getContext("2d");
525
+ if (!ctx) {
526
+ if (!isUrl) URL.revokeObjectURL(url);
527
+ throw new Error(i18n.t("errors.captureStreamUnsupported"));
528
+ }
529
+ const captureStreamFn = canvas.captureStream ?? canvas.mozCaptureStream;
530
+ if (!captureStreamFn) {
531
+ if (!isUrl) URL.revokeObjectURL(url);
532
+ throw new Error(i18n.t("errors.captureStreamUnsupported"));
533
+ }
534
+ const previewStream = captureStreamFn.call(canvas);
535
+ const videoTrack = previewStream.getVideoTracks()[0];
536
+ if (!videoTrack) {
537
+ if (!isUrl) URL.revokeObjectURL(url);
538
+ throw new Error(i18n.t("errors.videoTrackMissing"));
539
+ }
540
+ const publishFps = resolvePlaybackDrivenEncoderFps(videoTrack, measuredFps);
541
+ await applyPublishTrackConstraints(videoTrack, outputWidth, outputHeight);
542
+ let destroyed = false;
543
+ let lastAdvanceAt = Date.now();
544
+ let lastDrawMediaTimeS = -1;
545
+ let videoFrameCallbackId = null;
546
+ let rafId = null;
547
+ const drawFrame = () => {
548
+ if (destroyed) {
549
+ return;
375
550
  }
376
- return [width2, height2];
551
+ drawSourceCenterCrop(
552
+ ctx,
553
+ videoEl,
554
+ outputWidth,
555
+ outputHeight,
556
+ sourceWidth,
557
+ sourceHeight
558
+ );
559
+ };
560
+ const onPlaybackSample = (mediaTimeS) => {
561
+ if (destroyed || !shouldSampleAtMediaTime(lastDrawMediaTimeS, mediaTimeS)) {
562
+ return;
563
+ }
564
+ lastDrawMediaTimeS = mediaTimeS;
565
+ lastAdvanceAt = Date.now();
566
+ drawFrame();
567
+ };
568
+ const schedulePlaybackWatch = () => {
569
+ if (destroyed) {
570
+ return;
571
+ }
572
+ const video = videoEl;
573
+ if (video.requestVideoFrameCallback) {
574
+ videoFrameCallbackId = video.requestVideoFrameCallback((_now, metadata) => {
575
+ onPlaybackSample(metadata.mediaTime);
576
+ schedulePlaybackWatch();
577
+ });
578
+ return;
579
+ }
580
+ rafId = window.requestAnimationFrame(() => {
581
+ onPlaybackSample(videoEl.currentTime);
582
+ schedulePlaybackWatch();
583
+ });
584
+ };
585
+ drawFrame();
586
+ schedulePlaybackWatch();
587
+ const ensurePlaying = () => {
588
+ if (destroyed) {
589
+ return;
590
+ }
591
+ if (videoEl.ended && videoEl.loop) {
592
+ videoEl.currentTime = 0;
593
+ lastDrawMediaTimeS = -1;
594
+ }
595
+ if (videoEl.paused) {
596
+ void videoEl.play().catch(() => void 0);
597
+ }
598
+ };
599
+ const markAdvance = () => {
600
+ lastAdvanceAt = Date.now();
601
+ };
602
+ videoEl.addEventListener("timeupdate", markAdvance);
603
+ videoEl.addEventListener("pause", ensurePlaying);
604
+ videoEl.addEventListener("ended", ensurePlaying);
605
+ videoEl.addEventListener("stalled", ensurePlaying);
606
+ videoEl.addEventListener("waiting", ensurePlaying);
607
+ const healthIntervalMs = options.healthIntervalMs ?? 3e3;
608
+ let healthTimer = null;
609
+ if (options.onHealthReport) {
610
+ const reportHealth = options.onHealthReport;
611
+ healthTimer = setInterval(() => {
612
+ ensurePlaying();
613
+ const now = Date.now();
614
+ const currentTime = videoEl.currentTime;
615
+ const duration = videoEl.duration;
616
+ const nearEnd = duration > 0 && currentTime >= duration - 0.2;
617
+ const stalled = !videoEl.paused && !videoEl.ended && !nearEnd && now - lastAdvanceAt >= healthIntervalMs;
618
+ if (stalled && now - lastAdvanceAt >= STALL_CATCHUP_MS) {
619
+ void videoEl.play().catch(() => void 0);
620
+ }
621
+ reportHealth({
622
+ currentTime,
623
+ duration,
624
+ paused: videoEl.paused,
625
+ ended: videoEl.ended,
626
+ readyState: videoEl.readyState,
627
+ trackReadyState: videoTrack.readyState,
628
+ trackMuted: videoTrack.muted,
629
+ stalled,
630
+ timeSinceAdvanceMs: now - lastAdvanceAt
631
+ });
632
+ }, healthIntervalMs);
377
633
  }
378
- const height = UPLOAD_VIDEO_BASE;
379
- let width = alignToSizeMultiple(height * aspect);
380
- const maxWidth = alignToSizeMultiple(height * MAX_LANDSCAPE_ASPECT);
381
- if (width > maxWidth) {
382
- width = maxWidth;
634
+ const destroy = () => {
635
+ destroyed = true;
636
+ videoEl.removeEventListener("timeupdate", markAdvance);
637
+ videoEl.removeEventListener("pause", ensurePlaying);
638
+ videoEl.removeEventListener("ended", ensurePlaying);
639
+ videoEl.removeEventListener("stalled", ensurePlaying);
640
+ videoEl.removeEventListener("waiting", ensurePlaying);
641
+ const video = videoEl;
642
+ if (videoFrameCallbackId !== null) {
643
+ video.cancelVideoFrameCallback?.(videoFrameCallbackId);
644
+ videoFrameCallbackId = null;
645
+ }
646
+ if (rafId !== null) {
647
+ window.cancelAnimationFrame(rafId);
648
+ rafId = null;
649
+ }
650
+ if (healthTimer !== null) {
651
+ clearInterval(healthTimer);
652
+ healthTimer = null;
653
+ }
654
+ try {
655
+ videoEl.pause();
656
+ } catch {
657
+ }
658
+ try {
659
+ videoTrack.stop();
660
+ } catch {
661
+ }
662
+ try {
663
+ previewStream.getTracks().forEach((track) => {
664
+ if (track.readyState !== "ended") {
665
+ track.stop();
666
+ }
667
+ });
668
+ } catch {
669
+ }
670
+ try {
671
+ videoEl.remove();
672
+ } catch {
673
+ }
674
+ try {
675
+ videoEl.src = "";
676
+ videoEl.load();
677
+ } catch {
678
+ }
679
+ if (!isUrl) URL.revokeObjectURL(url);
680
+ };
681
+ return {
682
+ url,
683
+ previewStream,
684
+ videoEl,
685
+ videoTrack,
686
+ fps: publishFps,
687
+ width: outputWidth,
688
+ height: outputHeight,
689
+ sourceWidth,
690
+ sourceHeight,
691
+ destroy
692
+ };
693
+ }
694
+ function waitForEvent(el, eventName) {
695
+ return new Promise((resolve, reject) => {
696
+ const onOk = () => {
697
+ cleanup();
698
+ resolve();
699
+ };
700
+ const onErr = (e) => {
701
+ cleanup();
702
+ const mediaEl = el;
703
+ const code = mediaEl.error ? mediaEl.error.code : "unknown";
704
+ const message = mediaEl.error ? mediaEl.error.message : "no message";
705
+ const src = mediaEl.src || "unknown src";
706
+ reject(new Error(`failed to load video metadata: ${String(eventName)} (code: ${code}, message: ${message}, src: ${src})`));
707
+ };
708
+ const cleanup = () => {
709
+ el.removeEventListener(eventName, onOk);
710
+ el.removeEventListener("error", onErr);
711
+ };
712
+ el.addEventListener(eventName, onOk, { once: true });
713
+ el.addEventListener("error", onErr, { once: true });
714
+ });
715
+ }
716
+ function waitForCanPlay(videoEl) {
717
+ if (videoEl.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
718
+ return Promise.resolve();
383
719
  }
384
- return [width, height];
720
+ return new Promise((resolve) => {
721
+ const onReady = () => {
722
+ cleanup();
723
+ resolve();
724
+ };
725
+ const cleanup = () => {
726
+ videoEl.removeEventListener("canplay", onReady);
727
+ videoEl.removeEventListener("loadeddata", onReady);
728
+ };
729
+ videoEl.addEventListener("canplay", onReady, { once: true });
730
+ videoEl.addEventListener("loadeddata", onReady, { once: true });
731
+ });
385
732
  }
386
- function resolveSessionTargetSize(input) {
387
- if (input.overrideSize) {
388
- return input.overrideSize;
733
+ async function startVideoPlayback(videoEl, i18n) {
734
+ try {
735
+ const playResult = videoEl.play();
736
+ if (playResult && typeof playResult.then === "function") {
737
+ await playResult;
738
+ }
739
+ } catch {
740
+ throw new Error(i18n.t("errors.videoPlaybackFailed"));
389
741
  }
390
- if (input.localVideoSize?.width && input.localVideoSize?.height) {
391
- return [input.localVideoSize.width, input.localVideoSize.height];
742
+ try {
743
+ await waitForPlaying(videoEl);
744
+ } catch {
745
+ throw new Error(i18n.t("errors.videoPlaybackFailed"));
746
+ }
747
+ if (videoEl.paused || videoEl.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
748
+ throw new Error(i18n.t("errors.videoPlaybackFailed"));
392
749
  }
393
- return DEFAULT_SESSION_TARGET_SIZE;
394
750
  }
395
-
396
- // src/rtc/capture-host.ts
397
- var captureHostRoot = null;
398
- var CAPTURE_VIDEO_STYLE = "position:fixed;left:-10000px;top:0;width:640px;height:480px;opacity:1;pointer-events:none;z-index:-1;";
399
- function mountCaptureVideo(videoEl) {
400
- if (!captureHostRoot) {
401
- captureHostRoot = document.createElement("div");
402
- captureHostRoot.setAttribute("data-xmax-capture-host", "true");
403
- captureHostRoot.style.cssText = "position:fixed;left:0;top:0;width:0;height:0;overflow:hidden;pointer-events:none;z-index:-1;";
404
- document.body.appendChild(captureHostRoot);
751
+ function waitForPlaying(videoEl) {
752
+ if (!videoEl.paused && videoEl.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
753
+ return Promise.resolve();
405
754
  }
406
- videoEl.setAttribute("playsinline", "true");
407
- videoEl.setAttribute("webkit-playsinline", "true");
408
- videoEl.style.cssText = CAPTURE_VIDEO_STYLE;
409
- captureHostRoot.appendChild(videoEl);
755
+ return new Promise((resolve, reject) => {
756
+ const onPlaying = () => {
757
+ cleanup();
758
+ resolve();
759
+ };
760
+ const onErr = () => {
761
+ cleanup();
762
+ reject(new Error("video playback failed"));
763
+ };
764
+ const cleanup = () => {
765
+ videoEl.removeEventListener("playing", onPlaying);
766
+ videoEl.removeEventListener("error", onErr);
767
+ };
768
+ videoEl.addEventListener("playing", onPlaying, { once: true });
769
+ videoEl.addEventListener("error", onErr, { once: true });
770
+ });
410
771
  }
411
772
 
412
- export { API_ERROR_MESSAGES, DEFAULT_SESSION_TARGET_SIZE, MEDIA_TIME_EPSILON_S, MEDIA_TIME_LOOP_JUMP_S, MOBILE_PUBLISH_MAX_WIDTH_PX, PREVIEW_CONTAINER_ASPECT_RATIO, RTC_PUBLISH_FPS, RTC_PUBLISH_FPS_MOBILE, RTC_PUBLISH_FPS_WEB, RTC_PUBLISH_FRAME_INTERVAL_MS, RTC_PUBLISH_FRAME_INTERVAL_S, createSDKI18n, estimateVideoFrameRate, getDefaultMessages, isMobilePublishEnvironment, mountCaptureVideo, resolveAdaptiveRtcVideoSize, resolveApiErrorMessage, resolvePlaybackDrivenEncoderFps, resolvePreviewAspectRatio, resolveRtcPublishFps, resolveRtcPublishFrameIntervalS, resolveSessionTargetSize, resolveUploadPublishSize, resolveUploadVideoSize };
413
- //# sourceMappingURL=chunk-LCPC56BK.js.map
414
- //# sourceMappingURL=chunk-LCPC56BK.js.map
773
+ export { API_ERROR_MESSAGES, DEFAULT_SESSION_TARGET_SIZE, MEDIA_TIME_EPSILON_S, MEDIA_TIME_LOOP_JUMP_S, MOBILE_PUBLISH_MAX_WIDTH_PX, PREVIEW_CONTAINER_ASPECT_RATIO, RTC_PUBLISH_FPS_MOBILE, RTC_PUBLISH_FPS_WEB, RTC_PUBLISH_FRAME_INTERVAL_MS, RTC_PUBLISH_FRAME_INTERVAL_S, computeCenterCropRect, createSDKI18n, createVideoFileStream, drawSourceCenterCrop, getDefaultMessages, isMobilePublishEnvironment, mountCaptureVideo, resolveAdaptiveRtcVideoSize, resolveApiErrorMessage, resolvePlaybackDrivenEncoderFps, resolvePreviewAspectRatio, resolveRtcPublishFps, resolveRtcPublishFrameIntervalS, resolveSessionTargetSize, resolveUploadPublishSize, resolveUploadVideoSize };
774
+ //# sourceMappingURL=chunk-B5EAQTBY.js.map
775
+ //# sourceMappingURL=chunk-B5EAQTBY.js.map