@xmaxai/sdk 1.0.0 → 1.0.2

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