@xmaxai/web-sdk 1.0.0 → 1.0.1
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/{VideoContentMode-BXkqM4XG.d.cts → VideoContentMode-BM97_H8V.d.cts} +96 -21
- package/dist/{VideoContentMode-BXkqM4XG.d.ts → VideoContentMode-BM97_H8V.d.ts} +96 -21
- package/dist/{chunk-JJWUZ3AW.cjs → chunk-FH3OPC6F.cjs} +1486 -544
- package/dist/chunk-FH3OPC6F.cjs.map +1 -0
- package/dist/{chunk-MJ72WWQX.js → chunk-NW6YF4JT.js} +1449 -507
- package/dist/chunk-NW6YF4JT.js.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +624 -162
- package/dist/index.d.ts +624 -162
- package/dist/index.js +1 -1
- package/dist/react/index.cjs +3 -3
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +31 -11
- package/dist/react/index.d.ts +31 -11
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +1 -1
- package/package.json +7 -8
- package/README.md +0 -430
- package/dist/chunk-JJWUZ3AW.cjs.map +0 -1
- package/dist/chunk-MJ72WWQX.js.map +0 -1
|
@@ -9,7 +9,9 @@ var XmaxEnvironment = /* @__PURE__ */ ((XmaxEnvironment2) => {
|
|
|
9
9
|
return XmaxEnvironment2;
|
|
10
10
|
})(XmaxEnvironment || {});
|
|
11
11
|
var API_BASE_URLS = {
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* 临时联调环境。
|
|
14
|
+
*/
|
|
13
15
|
["china" /* china */]: "https://dev.xmaxai.com/open/api/v1",
|
|
14
16
|
["global" /* global */]: "https://api.xmax.cloud/open/api/v1"
|
|
15
17
|
};
|
|
@@ -38,8 +40,13 @@ var CONSOLE_METHOD = {
|
|
|
38
40
|
var BRAND_BADGE_STYLE = "background:#2563eb;color:#fff;border-radius:3px;padding:1px 4px;font-weight:600;";
|
|
39
41
|
var CATEGORY_BADGE_STYLE = "background:#475569;color:#fff;border-radius:3px;padding:1px 4px;font-weight:600;";
|
|
40
42
|
var _XmaxLogger = class _XmaxLogger {
|
|
43
|
+
/**
|
|
44
|
+
* 创建固定业务分类的日志入口,共享全局日志选项和语言配置。
|
|
45
|
+
*/
|
|
41
46
|
constructor(category) {
|
|
42
|
-
|
|
47
|
+
/**
|
|
48
|
+
* 日志类别
|
|
49
|
+
*/
|
|
43
50
|
__publicField(this, "category");
|
|
44
51
|
this.category = category;
|
|
45
52
|
}
|
|
@@ -53,31 +60,45 @@ var _XmaxLogger = class _XmaxLogger {
|
|
|
53
60
|
state.options = options;
|
|
54
61
|
state.environment = environment;
|
|
55
62
|
}
|
|
56
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* 选择日志细项文案;日志标题保持原有中英双语。
|
|
65
|
+
*/
|
|
57
66
|
static localized(chinese, english) {
|
|
58
67
|
return state.environment === "china" /* china */ ? chinese : english;
|
|
59
68
|
}
|
|
60
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* 判断指定日志类型是否已开启。
|
|
71
|
+
*/
|
|
61
72
|
static isEnabled(option) {
|
|
62
73
|
return (state.options & option) !== 0;
|
|
63
74
|
}
|
|
64
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* 输出调试日志。
|
|
77
|
+
*/
|
|
65
78
|
debug(message, option = 1 /* business */) {
|
|
66
79
|
this.log("debug", message, option);
|
|
67
80
|
}
|
|
68
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* 输出信息日志。
|
|
83
|
+
*/
|
|
69
84
|
info(message, option = 1 /* business */) {
|
|
70
85
|
this.log("info", message, option);
|
|
71
86
|
}
|
|
72
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* 输出警告日志。
|
|
89
|
+
*/
|
|
73
90
|
warning(message, option = 1 /* business */) {
|
|
74
91
|
this.log("warning", message, option);
|
|
75
92
|
}
|
|
76
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* 输出错误日志。
|
|
95
|
+
*/
|
|
77
96
|
error(message, option = 1 /* business */) {
|
|
78
97
|
this.log("error", message, option);
|
|
79
98
|
}
|
|
80
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* 按日志类型开关输出到控制台;未开启时不求值日志内容。
|
|
101
|
+
*/
|
|
81
102
|
log(level, message, option) {
|
|
82
103
|
if (!_XmaxLogger.isEnabled(option)) {
|
|
83
104
|
return;
|
|
@@ -98,7 +119,9 @@ var _XmaxLogger = class _XmaxLogger {
|
|
|
98
119
|
console[CONSOLE_METHOD[level]](`[Xmax][${this.category}] ${text}`);
|
|
99
120
|
}
|
|
100
121
|
};
|
|
101
|
-
|
|
122
|
+
/**
|
|
123
|
+
* 分类日志
|
|
124
|
+
*/
|
|
102
125
|
__publicField(_XmaxLogger, "realtime", new _XmaxLogger("Realtime"));
|
|
103
126
|
__publicField(_XmaxLogger, "rtc", new _XmaxLogger("RTC"));
|
|
104
127
|
__publicField(_XmaxLogger, "media", new _XmaxLogger("Media"));
|
|
@@ -142,12 +165,20 @@ var XmaxError = class _XmaxError extends Error {
|
|
|
142
165
|
*/
|
|
143
166
|
constructor(code, message, options) {
|
|
144
167
|
super(message);
|
|
145
|
-
|
|
146
|
-
|
|
168
|
+
/**
|
|
169
|
+
* 错误信息
|
|
170
|
+
*/
|
|
171
|
+
/**
|
|
172
|
+
* SDK 统一错误码。
|
|
173
|
+
*/
|
|
147
174
|
__publicField(this, "code");
|
|
148
|
-
/**
|
|
175
|
+
/**
|
|
176
|
+
* Xmax API 返回的业务错误码;非 API 业务错误时为空。
|
|
177
|
+
*/
|
|
149
178
|
__publicField(this, "apiCode");
|
|
150
|
-
/**
|
|
179
|
+
/**
|
|
180
|
+
* HTTP 响应状态码;请求未获得响应时为空。
|
|
181
|
+
*/
|
|
151
182
|
__publicField(this, "httpStatus");
|
|
152
183
|
this.name = "XmaxError";
|
|
153
184
|
this.code = code;
|
|
@@ -170,9 +201,11 @@ var XmaxError = class _XmaxError extends Error {
|
|
|
170
201
|
};
|
|
171
202
|
|
|
172
203
|
// src/Foundation/Runtime/RuntimeInfo.ts
|
|
173
|
-
var XMAX_SDK_VERSION = "1.0.
|
|
204
|
+
var XMAX_SDK_VERSION = "1.0.1";
|
|
174
205
|
var _RuntimeInfo = class _RuntimeInfo {
|
|
175
|
-
/**
|
|
206
|
+
/**
|
|
207
|
+
* 序列化为接口传输使用的 snake_case 字段。
|
|
208
|
+
*/
|
|
176
209
|
static toJSON(snapshot = _RuntimeInfo.current) {
|
|
177
210
|
return {
|
|
178
211
|
platform: snapshot.platform,
|
|
@@ -181,7 +214,9 @@ var _RuntimeInfo = class _RuntimeInfo {
|
|
|
181
214
|
device_model: snapshot.deviceModel
|
|
182
215
|
};
|
|
183
216
|
}
|
|
184
|
-
/**
|
|
217
|
+
/**
|
|
218
|
+
* 读取浏览器 User-Agent;非浏览器环境返回 unknown。
|
|
219
|
+
*/
|
|
185
220
|
static currentOSVersion() {
|
|
186
221
|
if (typeof navigator !== "undefined" && navigator.userAgent) {
|
|
187
222
|
return navigator.userAgent;
|
|
@@ -189,7 +224,12 @@ var _RuntimeInfo = class _RuntimeInfo {
|
|
|
189
224
|
return "unknown";
|
|
190
225
|
}
|
|
191
226
|
};
|
|
192
|
-
/**
|
|
227
|
+
/**
|
|
228
|
+
* 运行环境快照
|
|
229
|
+
*/
|
|
230
|
+
/**
|
|
231
|
+
* 当前运行环境快照。
|
|
232
|
+
*/
|
|
193
233
|
__publicField(_RuntimeInfo, "current", {
|
|
194
234
|
platform: "web",
|
|
195
235
|
osVersion: _RuntimeInfo.currentOSVersion(),
|
|
@@ -209,17 +249,24 @@ var _ApiService = class _ApiService {
|
|
|
209
249
|
* @param options.fetchImpl fetch 实现(可替换,测试用)。
|
|
210
250
|
*/
|
|
211
251
|
constructor(options) {
|
|
212
|
-
|
|
252
|
+
/**
|
|
253
|
+
* 请求身份、地址与超时配置
|
|
254
|
+
*/
|
|
213
255
|
__publicField(this, "apiKey");
|
|
214
256
|
__publicField(this, "baseURL");
|
|
215
257
|
__publicField(this, "timeoutMs");
|
|
258
|
+
/**
|
|
259
|
+
* 网络传输依赖
|
|
260
|
+
*/
|
|
216
261
|
__publicField(this, "fetchImpl");
|
|
217
262
|
this.apiKey = options.apiKey.trim();
|
|
218
263
|
this.baseURL = options.baseURL;
|
|
219
264
|
this.timeoutMs = options.timeoutMs ?? _ApiService.defaultTimeoutMs;
|
|
220
265
|
this.fetchImpl = options.fetchImpl ?? ((url, init) => fetch(url, init));
|
|
221
266
|
}
|
|
222
|
-
/**
|
|
267
|
+
/**
|
|
268
|
+
* 发送请求并返回统一响应中的数据。
|
|
269
|
+
*/
|
|
223
270
|
async request(method, path, body) {
|
|
224
271
|
this.validateConfiguration();
|
|
225
272
|
const request = this.makeRequest(method, path, body);
|
|
@@ -248,23 +295,33 @@ var _ApiService = class _ApiService {
|
|
|
248
295
|
throw mapped;
|
|
249
296
|
}
|
|
250
297
|
}
|
|
251
|
-
/**
|
|
298
|
+
/**
|
|
299
|
+
* 发送 GET 请求。
|
|
300
|
+
*/
|
|
252
301
|
get(path) {
|
|
253
302
|
return this.request("GET" /* get */, path);
|
|
254
303
|
}
|
|
255
|
-
/**
|
|
304
|
+
/**
|
|
305
|
+
* 发送 POST 请求。
|
|
306
|
+
*/
|
|
256
307
|
post(path, body) {
|
|
257
308
|
return this.request("POST" /* post */, path, body);
|
|
258
309
|
}
|
|
259
|
-
/**
|
|
310
|
+
/**
|
|
311
|
+
* 发送 PUT 请求。
|
|
312
|
+
*/
|
|
260
313
|
put(path, body) {
|
|
261
314
|
return this.request("PUT" /* put */, path, body);
|
|
262
315
|
}
|
|
263
|
-
/**
|
|
316
|
+
/**
|
|
317
|
+
* 发送 DELETE 请求。
|
|
318
|
+
*/
|
|
264
319
|
delete(path) {
|
|
265
320
|
return this.request("DELETE" /* delete */, path);
|
|
266
321
|
}
|
|
267
|
-
/**
|
|
322
|
+
/**
|
|
323
|
+
* 校验 API Key 与 Base URL 配置。
|
|
324
|
+
*/
|
|
268
325
|
validateConfiguration() {
|
|
269
326
|
if (this.apiKey.length === 0) {
|
|
270
327
|
throw new XmaxError("INVALID_API_KEY" /* invalidAPIKey */, "API key cannot be empty");
|
|
@@ -287,7 +344,9 @@ var _ApiService = class _ApiService {
|
|
|
287
344
|
);
|
|
288
345
|
}
|
|
289
346
|
}
|
|
290
|
-
/**
|
|
347
|
+
/**
|
|
348
|
+
* 构造请求地址、请求头与请求体;路径必须相对且与 Base URL 同源。
|
|
349
|
+
*/
|
|
291
350
|
makeRequest(method, path, body) {
|
|
292
351
|
const normalizedPath = path.trim();
|
|
293
352
|
if (normalizedPath.length === 0 || normalizedPath.includes("://") || normalizedPath.startsWith("//")) {
|
|
@@ -333,7 +392,9 @@ var _ApiService = class _ApiService {
|
|
|
333
392
|
}
|
|
334
393
|
};
|
|
335
394
|
}
|
|
336
|
-
/**
|
|
395
|
+
/**
|
|
396
|
+
* 解析统一响应信封并返回数据;业务失败或数据缺失时抛出错误。
|
|
397
|
+
*/
|
|
337
398
|
static parseResponse(text, status) {
|
|
338
399
|
let envelope;
|
|
339
400
|
try {
|
|
@@ -348,7 +409,7 @@ var _ApiService = class _ApiService {
|
|
|
348
409
|
const message = envelope.message?.trim();
|
|
349
410
|
throw new XmaxError(
|
|
350
411
|
"API_ERROR" /* apiError */,
|
|
351
|
-
message
|
|
412
|
+
message || "Xmax API request failed",
|
|
352
413
|
{ apiCode: envelope.code, httpStatus: status }
|
|
353
414
|
);
|
|
354
415
|
}
|
|
@@ -361,7 +422,9 @@ var _ApiService = class _ApiService {
|
|
|
361
422
|
}
|
|
362
423
|
return envelope.data;
|
|
363
424
|
}
|
|
364
|
-
/**
|
|
425
|
+
/**
|
|
426
|
+
* 将传输层异常映射为 SDK 统一错误。
|
|
427
|
+
*/
|
|
365
428
|
static transportError(error) {
|
|
366
429
|
if (error instanceof XmaxError) {
|
|
367
430
|
return error;
|
|
@@ -378,7 +441,9 @@ var _ApiService = class _ApiService {
|
|
|
378
441
|
`HTTP request failed: ${message}`
|
|
379
442
|
);
|
|
380
443
|
}
|
|
381
|
-
/**
|
|
444
|
+
/**
|
|
445
|
+
* 输出失败日志。
|
|
446
|
+
*/
|
|
382
447
|
logFailure(method, path, error, startedAt) {
|
|
383
448
|
XmaxLogger.api.error(
|
|
384
449
|
() => `API \u8BF7\u6C42\u5931\u8D25 (API Request Failed)
|
|
@@ -387,8 +452,12 @@ var _ApiService = class _ApiService {
|
|
|
387
452
|
);
|
|
388
453
|
}
|
|
389
454
|
};
|
|
390
|
-
|
|
391
|
-
|
|
455
|
+
/**
|
|
456
|
+
* API 配置
|
|
457
|
+
*/
|
|
458
|
+
/**
|
|
459
|
+
* 默认请求超时时间(毫秒)。
|
|
460
|
+
*/
|
|
392
461
|
__publicField(_ApiService, "defaultTimeoutMs", 15e3);
|
|
393
462
|
var ApiService = _ApiService;
|
|
394
463
|
|
|
@@ -410,11 +479,35 @@ var RealtimeVideoFormat = class _RealtimeVideoFormat {
|
|
|
410
479
|
* @param init.encoderPreference 上传编码策略偏好,默认值为 maintainFramerate。
|
|
411
480
|
*/
|
|
412
481
|
constructor(init) {
|
|
482
|
+
/**
|
|
483
|
+
* 视频规格
|
|
484
|
+
*/
|
|
485
|
+
/**
|
|
486
|
+
* 视频宽度,单位为像素。
|
|
487
|
+
*/
|
|
413
488
|
__publicField(this, "width");
|
|
489
|
+
/**
|
|
490
|
+
* 视频高度,单位为像素。
|
|
491
|
+
*/
|
|
414
492
|
__publicField(this, "height");
|
|
493
|
+
/**
|
|
494
|
+
* 视频帧率,单位为 fps。
|
|
495
|
+
*/
|
|
415
496
|
__publicField(this, "fps");
|
|
497
|
+
/**
|
|
498
|
+
* 上传编码配置
|
|
499
|
+
*/
|
|
500
|
+
/**
|
|
501
|
+
* 最低上传码率,单位为 kbps;未指定时由 SDK 计算,0 表示不设最低码率。
|
|
502
|
+
*/
|
|
416
503
|
__publicField(this, "minimumBitrate");
|
|
504
|
+
/**
|
|
505
|
+
* 最高上传码率,单位为 kbps;未指定时由 SDK 计算。
|
|
506
|
+
*/
|
|
417
507
|
__publicField(this, "maximumBitrate");
|
|
508
|
+
/**
|
|
509
|
+
* 弱网下优先保障帧率或分辨率的编码策略。
|
|
510
|
+
*/
|
|
418
511
|
__publicField(this, "encoderPreference");
|
|
419
512
|
this.width = init.width;
|
|
420
513
|
this.height = init.height;
|
|
@@ -453,7 +546,9 @@ var RealtimeVideoFormat = class _RealtimeVideoFormat {
|
|
|
453
546
|
);
|
|
454
547
|
}
|
|
455
548
|
}
|
|
456
|
-
/**
|
|
549
|
+
/**
|
|
550
|
+
* 调整尺寸,保留帧率和上传编码配置。
|
|
551
|
+
*/
|
|
457
552
|
resized(width, height) {
|
|
458
553
|
return new _RealtimeVideoFormat({
|
|
459
554
|
width,
|
|
@@ -468,16 +563,25 @@ var RealtimeVideoFormat = class _RealtimeVideoFormat {
|
|
|
468
563
|
|
|
469
564
|
// src/Service/Realtime/RealtimeModel.ts
|
|
470
565
|
var RealtimeModel = /* @__PURE__ */ ((RealtimeModel2) => {
|
|
566
|
+
RealtimeModel2["x2_0"] = "x2.0";
|
|
567
|
+
RealtimeModel2["x2_0_pro"] = "x2.0-pro";
|
|
471
568
|
RealtimeModel2["x2_fast_1080p"] = "x2-fast-1080p";
|
|
472
569
|
return RealtimeModel2;
|
|
473
570
|
})(RealtimeModel || {});
|
|
474
571
|
var RESOLUTION_BUCKETS = {
|
|
572
|
+
["x2.0" /* x2_0 */]: [],
|
|
573
|
+
["x2.0-pro" /* x2_0_pro */]: [
|
|
574
|
+
{ width: 1024, height: 1920 },
|
|
575
|
+
{ width: 1920, height: 1024 }
|
|
576
|
+
],
|
|
475
577
|
["x2-fast-1080p" /* x2_fast_1080p */]: [
|
|
476
578
|
{ width: 1024, height: 1920 },
|
|
477
579
|
{ width: 1920, height: 1024 }
|
|
478
580
|
]
|
|
479
581
|
};
|
|
480
582
|
var MAXIMUM_INPUT_PIXELS = {
|
|
583
|
+
["x2.0" /* x2_0 */]: 128e4,
|
|
584
|
+
["x2.0-pro" /* x2_0_pro */]: 21e5,
|
|
481
585
|
["x2-fast-1080p" /* x2_fast_1080p */]: 21e5
|
|
482
586
|
};
|
|
483
587
|
function resolutionBuckets(model) {
|
|
@@ -495,7 +599,10 @@ function inputSizeAlignment(_model) {
|
|
|
495
599
|
function defaultFrameRate(_model) {
|
|
496
600
|
return 30;
|
|
497
601
|
}
|
|
498
|
-
function defaultCameraVideoFormat(
|
|
602
|
+
function defaultCameraVideoFormat(model) {
|
|
603
|
+
if (model === "x2.0" /* x2_0 */) {
|
|
604
|
+
return new RealtimeVideoFormat({ width: 832, height: 1472, fps: 30 });
|
|
605
|
+
}
|
|
499
606
|
return new RealtimeVideoFormat({ width: 1024, height: 1920, fps: 30 });
|
|
500
607
|
}
|
|
501
608
|
|
|
@@ -521,11 +628,15 @@ var MediaService = class {
|
|
|
521
628
|
* @param model 媒体输入规则使用的模型,默认为 `x2-fast-1080p`。
|
|
522
629
|
*/
|
|
523
630
|
constructor(model = "x2-fast-1080p" /* x2_fast_1080p */) {
|
|
524
|
-
|
|
631
|
+
/**
|
|
632
|
+
* 模型约束
|
|
633
|
+
*/
|
|
525
634
|
__publicField(this, "model");
|
|
526
635
|
this.model = model;
|
|
527
636
|
}
|
|
528
|
-
/**
|
|
637
|
+
/**
|
|
638
|
+
* Web 插帧保留原始尺寸;/16 对齐由 GPU 内部补边处理,不缩小回传视频。
|
|
639
|
+
*/
|
|
529
640
|
resolveFrameInterpolationSize(size) {
|
|
530
641
|
const { width, height } = size;
|
|
531
642
|
if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width <= 0 || height <= 0) {
|
|
@@ -533,10 +644,15 @@ var MediaService = class {
|
|
|
533
644
|
}
|
|
534
645
|
return { width, height };
|
|
535
646
|
}
|
|
536
|
-
/**
|
|
647
|
+
/**
|
|
648
|
+
* WebGPU 适配器探测是异步的;实际性能和运行状态由渲染管线判断。
|
|
649
|
+
*/
|
|
537
650
|
async supportsFrameInterpolation(size) {
|
|
538
651
|
return await frameInterpolationAdapter(size) !== null;
|
|
539
652
|
}
|
|
653
|
+
/**
|
|
654
|
+
* 按模型分辨率档位或像素预算解析输入尺寸,保持宽高比例并对齐;不支持的规格抛错。
|
|
655
|
+
*/
|
|
540
656
|
resolveModelInputSize(size) {
|
|
541
657
|
const buckets = resolutionBuckets(this.model);
|
|
542
658
|
if (buckets.length > 0) {
|
|
@@ -586,7 +702,9 @@ var MediaService = class {
|
|
|
586
702
|
validated.height * scale
|
|
587
703
|
);
|
|
588
704
|
}
|
|
589
|
-
/**
|
|
705
|
+
/**
|
|
706
|
+
* 在对齐网格上选择满足面积限制且最接近目标的尺寸。
|
|
707
|
+
*/
|
|
590
708
|
boundedAlignedSize(width, height) {
|
|
591
709
|
const alignment = inputSizeAlignment(this.model);
|
|
592
710
|
const unitPixels = alignment * alignment;
|
|
@@ -614,7 +732,9 @@ var MediaService = class {
|
|
|
614
732
|
}
|
|
615
733
|
return bestSize;
|
|
616
734
|
}
|
|
617
|
-
/**
|
|
735
|
+
/**
|
|
736
|
+
* 校验尺寸为有限正数,并归一化为不小于 1 的整数。
|
|
737
|
+
*/
|
|
618
738
|
validatedSize(size) {
|
|
619
739
|
if (!Number.isFinite(size.width) || !Number.isFinite(size.height) || size.width <= 0 || size.height <= 0) {
|
|
620
740
|
throw new XmaxError(
|
|
@@ -639,11 +759,20 @@ var StoredFile = class {
|
|
|
639
759
|
* @param init.etag 对象存储返回的 ETag。
|
|
640
760
|
*/
|
|
641
761
|
constructor(init) {
|
|
642
|
-
/**
|
|
762
|
+
/**
|
|
763
|
+
* 存储结果
|
|
764
|
+
*/
|
|
765
|
+
/**
|
|
766
|
+
* 文件的访问地址。
|
|
767
|
+
*/
|
|
643
768
|
__publicField(this, "url");
|
|
644
|
-
/**
|
|
769
|
+
/**
|
|
770
|
+
* 对象存储中的对象键。
|
|
771
|
+
*/
|
|
645
772
|
__publicField(this, "objectKey");
|
|
646
|
-
/**
|
|
773
|
+
/**
|
|
774
|
+
* 对象存储返回的 ETag;未返回时为空。
|
|
775
|
+
*/
|
|
647
776
|
__publicField(this, "etag");
|
|
648
777
|
this.url = init.url;
|
|
649
778
|
this.objectKey = init.objectKey;
|
|
@@ -672,11 +801,17 @@ var StorageService = class _StorageService {
|
|
|
672
801
|
* @param options.identifierGenerator 标识来源(可替换,测试用)。
|
|
673
802
|
*/
|
|
674
803
|
constructor(options) {
|
|
675
|
-
|
|
804
|
+
/**
|
|
805
|
+
* 服务层组件
|
|
806
|
+
*/
|
|
676
807
|
__publicField(this, "apiService");
|
|
677
|
-
|
|
808
|
+
/**
|
|
809
|
+
* 平台资源
|
|
810
|
+
*/
|
|
678
811
|
__publicField(this, "clientFactory");
|
|
679
|
-
|
|
812
|
+
/**
|
|
813
|
+
* 标识生成
|
|
814
|
+
*/
|
|
680
815
|
__publicField(this, "dateGenerator");
|
|
681
816
|
__publicField(this, "identifierGenerator");
|
|
682
817
|
this.apiService = options.apiService;
|
|
@@ -684,7 +819,9 @@ var StorageService = class _StorageService {
|
|
|
684
819
|
this.dateGenerator = options.dateGenerator ?? (() => /* @__PURE__ */ new Date());
|
|
685
820
|
this.identifierGenerator = options.identifierGenerator ?? (() => crypto.randomUUID().toLowerCase());
|
|
686
821
|
}
|
|
687
|
-
/**
|
|
822
|
+
/**
|
|
823
|
+
* 上传图片并返回访问地址。
|
|
824
|
+
*/
|
|
688
825
|
async uploadImage(options) {
|
|
689
826
|
const startedAt = Date.now();
|
|
690
827
|
try {
|
|
@@ -734,7 +871,9 @@ var StorageService = class _StorageService {
|
|
|
734
871
|
);
|
|
735
872
|
}
|
|
736
873
|
}
|
|
737
|
-
/**
|
|
874
|
+
/**
|
|
875
|
+
* 获取临时存储配置;字段不完整时抛出 API 错误。
|
|
876
|
+
*/
|
|
738
877
|
async fetchStorageConfiguration() {
|
|
739
878
|
const payload = await this.apiService.get("/cos/sts");
|
|
740
879
|
const message = "Invalid storage credential payload";
|
|
@@ -755,12 +894,16 @@ var StorageService = class _StorageService {
|
|
|
755
894
|
credentials: { accessKeyID, secretAccessKey, sessionToken }
|
|
756
895
|
};
|
|
757
896
|
}
|
|
758
|
-
/**
|
|
897
|
+
/**
|
|
898
|
+
* 生成对象键:`${prefix}${毫秒时间戳}_${标识}_${文件名}`。
|
|
899
|
+
*/
|
|
759
900
|
makeObjectKey(prefix, fileName) {
|
|
760
901
|
const milliseconds = Math.round(this.dateGenerator().getTime());
|
|
761
902
|
return `${prefix}${milliseconds}_${this.identifierGenerator()}_${fileName}`;
|
|
762
903
|
}
|
|
763
|
-
/**
|
|
904
|
+
/**
|
|
905
|
+
* 生产环境 COS 客户端工厂:动态加载 cos-js-sdk-v5 并按临时凭证创建客户端。
|
|
906
|
+
*/
|
|
764
907
|
static async cosClientFactory(configuration) {
|
|
765
908
|
const module = await import("cos-js-sdk-v5");
|
|
766
909
|
const COS = module.default;
|
|
@@ -770,7 +913,9 @@ var StorageService = class _StorageService {
|
|
|
770
913
|
SecurityToken: configuration.credentials.sessionToken
|
|
771
914
|
});
|
|
772
915
|
}
|
|
773
|
-
/**
|
|
916
|
+
/**
|
|
917
|
+
* 调用对象存储上传并包装为 Promise。
|
|
918
|
+
*/
|
|
774
919
|
static putObject(client, params) {
|
|
775
920
|
return new Promise((resolve, reject) => {
|
|
776
921
|
client.putObject(params, (error, data) => {
|
|
@@ -806,7 +951,9 @@ var StorageService = class _StorageService {
|
|
|
806
951
|
}
|
|
807
952
|
return `https://${configuration.bucket}.cos.${configuration.region}.myqcloud.com/${objectKey}`;
|
|
808
953
|
}
|
|
809
|
-
/**
|
|
954
|
+
/**
|
|
955
|
+
* 统一构造上传体。
|
|
956
|
+
*/
|
|
810
957
|
static makeBody(data, contentType) {
|
|
811
958
|
if (data instanceof Blob) {
|
|
812
959
|
return data;
|
|
@@ -814,7 +961,9 @@ var StorageService = class _StorageService {
|
|
|
814
961
|
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
815
962
|
return new Blob([bytes], { type: contentType });
|
|
816
963
|
}
|
|
817
|
-
/**
|
|
964
|
+
/**
|
|
965
|
+
* 校验文件名非空且不包含路径分隔符。
|
|
966
|
+
*/
|
|
818
967
|
static validateFileName(fileName) {
|
|
819
968
|
const safeName = fileName.trim();
|
|
820
969
|
if (!safeName || safeName.includes("/") || safeName.includes("\\")) {
|
|
@@ -825,12 +974,16 @@ var StorageService = class _StorageService {
|
|
|
825
974
|
}
|
|
826
975
|
return safeName;
|
|
827
976
|
}
|
|
828
|
-
/**
|
|
977
|
+
/**
|
|
978
|
+
* 按文件名后缀推断内容类型。
|
|
979
|
+
*/
|
|
829
980
|
static inferContentType(fileName) {
|
|
830
981
|
const extension = fileName.split(".").pop()?.toLowerCase() ?? "";
|
|
831
982
|
return CONTENT_TYPES[extension] ?? "application/octet-stream";
|
|
832
983
|
}
|
|
833
|
-
/**
|
|
984
|
+
/**
|
|
985
|
+
* 格式化字节数用于日志输出。
|
|
986
|
+
*/
|
|
834
987
|
static formatByteCount(bytes) {
|
|
835
988
|
if (bytes < 1024) {
|
|
836
989
|
return `${bytes} B`;
|
|
@@ -840,10 +993,12 @@ var StorageService = class _StorageService {
|
|
|
840
993
|
}
|
|
841
994
|
return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
|
|
842
995
|
}
|
|
843
|
-
/**
|
|
996
|
+
/**
|
|
997
|
+
* 归一化可选字符串:去除首尾空白后为空时返回 `undefined`。
|
|
998
|
+
*/
|
|
844
999
|
static nonEmpty(value) {
|
|
845
1000
|
const normalized = value?.trim();
|
|
846
|
-
return normalized
|
|
1001
|
+
return normalized || void 0;
|
|
847
1002
|
}
|
|
848
1003
|
};
|
|
849
1004
|
|
|
@@ -856,12 +1011,23 @@ var CameraPosition = /* @__PURE__ */ ((CameraPosition3) => {
|
|
|
856
1011
|
|
|
857
1012
|
// src/Foundation/RTC/RtcEngineManager.ts
|
|
858
1013
|
var RtcEngineLease = class {
|
|
1014
|
+
/**
|
|
1015
|
+
* 为已创建的引擎生成独占租约标识,供引擎管理器核对释放请求。
|
|
1016
|
+
*/
|
|
859
1017
|
constructor(engine) {
|
|
860
|
-
|
|
861
|
-
|
|
1018
|
+
/**
|
|
1019
|
+
* RTC 资源
|
|
1020
|
+
*/
|
|
1021
|
+
/**
|
|
1022
|
+
* 租约持有的 TRTC 引擎实例。
|
|
1023
|
+
*/
|
|
862
1024
|
__publicField(this, "engine");
|
|
863
|
-
|
|
864
|
-
|
|
1025
|
+
/**
|
|
1026
|
+
* 运行标识
|
|
1027
|
+
*/
|
|
1028
|
+
/**
|
|
1029
|
+
* 租约标识。
|
|
1030
|
+
*/
|
|
865
1031
|
__publicField(this, "id");
|
|
866
1032
|
this.engine = engine;
|
|
867
1033
|
this.id = `lease-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -874,11 +1040,17 @@ var _RtcEngineManager = class _RtcEngineManager {
|
|
|
874
1040
|
* @param makeEngine 引擎工厂(可替换,测试用)。
|
|
875
1041
|
*/
|
|
876
1042
|
constructor(makeEngine = _RtcEngineManager.defaultMakeEngine) {
|
|
877
|
-
|
|
1043
|
+
/**
|
|
1044
|
+
* 依赖
|
|
1045
|
+
*/
|
|
878
1046
|
__publicField(this, "makeEngine");
|
|
879
|
-
|
|
1047
|
+
/**
|
|
1048
|
+
* RTC 资源
|
|
1049
|
+
*/
|
|
880
1050
|
__publicField(this, "activeLease");
|
|
881
|
-
|
|
1051
|
+
/**
|
|
1052
|
+
* 运行状态
|
|
1053
|
+
*/
|
|
882
1054
|
__publicField(this, "requests", []);
|
|
883
1055
|
this.makeEngine = makeEngine;
|
|
884
1056
|
}
|
|
@@ -901,7 +1073,9 @@ var _RtcEngineManager = class _RtcEngineManager {
|
|
|
901
1073
|
});
|
|
902
1074
|
});
|
|
903
1075
|
}
|
|
904
|
-
/**
|
|
1076
|
+
/**
|
|
1077
|
+
* 释放有效租约、销毁 Engine,并把独占权交给下一个等待者。
|
|
1078
|
+
*/
|
|
905
1079
|
release(lease) {
|
|
906
1080
|
if (this.activeLease?.id !== lease.id) {
|
|
907
1081
|
return;
|
|
@@ -913,13 +1087,17 @@ var _RtcEngineManager = class _RtcEngineManager {
|
|
|
913
1087
|
}
|
|
914
1088
|
void this.fulfillNextRequest();
|
|
915
1089
|
}
|
|
916
|
-
/**
|
|
1090
|
+
/**
|
|
1091
|
+
* 动态加载 TRTC SDK 并创建引擎实例。
|
|
1092
|
+
*/
|
|
917
1093
|
static async defaultMakeEngine() {
|
|
918
1094
|
const module = await import("trtc-sdk-v5");
|
|
919
1095
|
module.default.setLogLevel(5);
|
|
920
1096
|
return module.default.create();
|
|
921
1097
|
}
|
|
922
|
-
/**
|
|
1098
|
+
/**
|
|
1099
|
+
* 创建新引擎租约。
|
|
1100
|
+
*/
|
|
923
1101
|
async createLease() {
|
|
924
1102
|
const engine = await this.makeEngine();
|
|
925
1103
|
if (!engine) {
|
|
@@ -930,7 +1108,9 @@ var _RtcEngineManager = class _RtcEngineManager {
|
|
|
930
1108
|
}
|
|
931
1109
|
return new RtcEngineLease(engine);
|
|
932
1110
|
}
|
|
933
|
-
/**
|
|
1111
|
+
/**
|
|
1112
|
+
* 满足下一个排队请求;创建失败时拒绝该请求并继续处理后续请求。
|
|
1113
|
+
*/
|
|
934
1114
|
async fulfillNextRequest() {
|
|
935
1115
|
while (!this.activeLease && this.requests.length > 0) {
|
|
936
1116
|
const request = this.requests.shift();
|
|
@@ -947,12 +1127,20 @@ var _RtcEngineManager = class _RtcEngineManager {
|
|
|
947
1127
|
}
|
|
948
1128
|
}
|
|
949
1129
|
};
|
|
950
|
-
/**
|
|
1130
|
+
/**
|
|
1131
|
+
* 共享管理入口
|
|
1132
|
+
*/
|
|
1133
|
+
/**
|
|
1134
|
+
* 共享实例。
|
|
1135
|
+
*/
|
|
951
1136
|
__publicField(_RtcEngineManager, "shared", new _RtcEngineManager());
|
|
952
1137
|
var RtcEngineManager = _RtcEngineManager;
|
|
953
1138
|
|
|
954
1139
|
// src/Foundation/RTC/RtcStatsLogger.ts
|
|
955
1140
|
var RtcStatsLogger = class {
|
|
1141
|
+
/**
|
|
1142
|
+
* 按上下行及音视频分组输出白名单统计指标,仅在 performance 日志启用时格式化。
|
|
1143
|
+
*/
|
|
956
1144
|
static logStatistics(stats) {
|
|
957
1145
|
XmaxLogger.rtc.info(() => {
|
|
958
1146
|
const blocks = [this.block("RTC \u8FD0\u884C\u7EDF\u8BA1 (RTC Statistics)", [
|
|
@@ -981,12 +1169,18 @@ var RtcStatsLogger = class {
|
|
|
981
1169
|
return blocks.join("\n\n");
|
|
982
1170
|
}, 2 /* performance */);
|
|
983
1171
|
}
|
|
1172
|
+
/**
|
|
1173
|
+
* 输出上下行网络质量等级、RTT 和丢包率,遵循 performance 日志开关。
|
|
1174
|
+
*/
|
|
984
1175
|
static logNetworkQuality(stats) {
|
|
985
1176
|
XmaxLogger.rtc.info(() => this.block("\u7F51\u7EDC\u8D28\u91CF (Network Quality Metrics)", [
|
|
986
1177
|
`${XmaxLogger.localized("\u672C\u5730\u4E0A\u884C\uFF1A", "Local Uplink: ")}${this.quality(stats.uplinkNetworkQuality)}, RTT ${this.value(stats.uplinkRTT, "ms")}, ${XmaxLogger.localized("\u4E22\u5305\u7387 ", "Loss ")}${this.value(stats.uplinkLoss, "%")}`,
|
|
987
1178
|
`${XmaxLogger.localized("\u672C\u5730\u4E0B\u884C\uFF08\u5E73\u5747\uFF09\uFF1A", "Local Downlink (Average): ")}${this.quality(stats.downlinkNetworkQuality)}, RTT ${this.value(stats.downlinkRTT, "ms")}, ${XmaxLogger.localized("\u4E22\u5305\u7387 ", "Loss ")}${this.value(stats.downlinkLoss, "%")}`
|
|
988
1179
|
]), 2 /* performance */);
|
|
989
1180
|
}
|
|
1181
|
+
/**
|
|
1182
|
+
* 格式化音频码率和音量;远端音频额外包含播放延迟。
|
|
1183
|
+
*/
|
|
990
1184
|
static audioBlock(title, stats, remote) {
|
|
991
1185
|
return this.block(title, [
|
|
992
1186
|
`${XmaxLogger.localized("\u7801\u7387\uFF1A", "Bitrate: ")}${this.value(stats.bitrate, "kbps")}`,
|
|
@@ -994,6 +1188,9 @@ var RtcStatsLogger = class {
|
|
|
994
1188
|
...remote ? this.playbackLines(stats) : []
|
|
995
1189
|
]);
|
|
996
1190
|
}
|
|
1191
|
+
/**
|
|
1192
|
+
* 格式化视频流类型、尺寸、帧率和码率;远端视频额外包含播放延迟。
|
|
1193
|
+
*/
|
|
997
1194
|
static videoBlock(title, stats, remote) {
|
|
998
1195
|
return this.block(title, [
|
|
999
1196
|
`${XmaxLogger.localized("\u6D41\u7C7B\u578B\uFF1A", "Stream Type: ")}${stats.videoType ?? "\u2014"}`,
|
|
@@ -1003,22 +1200,33 @@ var RtcStatsLogger = class {
|
|
|
1003
1200
|
...remote ? this.playbackLines(stats) : []
|
|
1004
1201
|
]);
|
|
1005
1202
|
}
|
|
1203
|
+
/**
|
|
1204
|
+
* 格式化 TRTC 播放缓冲及估算端到端延迟,缺失或无效指标显示占位符。
|
|
1205
|
+
*/
|
|
1006
1206
|
static playbackLines(stats) {
|
|
1007
1207
|
return [
|
|
1008
1208
|
`${XmaxLogger.localized("\u64AD\u653E\u7F13\u51B2\u5EF6\u8FDF\uFF1A", "Jitter Buffer Delay: ")}${this.value(stats.jitterBufferDelay, "ms")}`,
|
|
1009
1209
|
`${XmaxLogger.localized("RTC \u7AEF\u5230\u7AEF\u5EF6\u8FDF\uFF08\u4F30\u7B97\uFF09\uFF1A", "RTC End-to-End Delay (Estimated): ")}${this.value(stats.point2pointDelay, "ms")}`
|
|
1010
1210
|
];
|
|
1011
1211
|
}
|
|
1212
|
+
/**
|
|
1213
|
+
* 为指标列表添加标题和树形行前缀,生成一个控制台日志分组。
|
|
1214
|
+
*/
|
|
1012
1215
|
static block(title, lines) {
|
|
1013
1216
|
return [title, ...lines.map((line, index) => `${index === lines.length - 1 ? "\u2514\u2500" : "\u251C\u2500"} ${line}`)].join("\n");
|
|
1014
1217
|
}
|
|
1015
|
-
/**
|
|
1218
|
+
/**
|
|
1219
|
+
* TRTC 丢包率已是百分数,无需再乘 100。
|
|
1220
|
+
*/
|
|
1016
1221
|
static value(value, unit = "") {
|
|
1017
1222
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
1018
1223
|
return "\u2014";
|
|
1019
1224
|
}
|
|
1020
1225
|
return `${Number(value.toFixed(2))}${unit ? `${unit === "%" ? "" : " "}${unit}` : ""}`;
|
|
1021
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* 将 TRTC 质量等级映射为本地化文案;未知等级回退为“未知”。
|
|
1229
|
+
*/
|
|
1022
1230
|
static quality(value) {
|
|
1023
1231
|
const names = [
|
|
1024
1232
|
["\u672A\u77E5", "Unknown"],
|
|
@@ -1057,25 +1265,39 @@ var RtcManager = class {
|
|
|
1057
1265
|
* @param engineManager 引擎租约管理组件(可替换,测试用)。
|
|
1058
1266
|
*/
|
|
1059
1267
|
constructor(engineManager = RtcEngineManager.shared) {
|
|
1060
|
-
|
|
1268
|
+
/**
|
|
1269
|
+
* 依赖
|
|
1270
|
+
*/
|
|
1061
1271
|
__publicField(this, "engineManager");
|
|
1062
|
-
|
|
1272
|
+
/**
|
|
1273
|
+
* RTC 资源
|
|
1274
|
+
*/
|
|
1063
1275
|
__publicField(this, "lease");
|
|
1064
|
-
|
|
1276
|
+
/**
|
|
1277
|
+
* 采集状态
|
|
1278
|
+
*/
|
|
1065
1279
|
__publicField(this, "isCapturing", false);
|
|
1066
1280
|
__publicField(this, "isAudioCapturing", false);
|
|
1067
|
-
|
|
1281
|
+
/**
|
|
1282
|
+
* 房间状态
|
|
1283
|
+
*/
|
|
1068
1284
|
__publicField(this, "isInRoom", false);
|
|
1069
|
-
|
|
1285
|
+
/**
|
|
1286
|
+
* 事件监听
|
|
1287
|
+
*/
|
|
1070
1288
|
__publicField(this, "eventListener");
|
|
1071
1289
|
__publicField(this, "removeStatsListeners");
|
|
1072
1290
|
this.engineManager = engineManager;
|
|
1073
1291
|
}
|
|
1074
|
-
/**
|
|
1292
|
+
/**
|
|
1293
|
+
* RTC 引擎是否已初始化。
|
|
1294
|
+
*/
|
|
1075
1295
|
get isInitialized() {
|
|
1076
1296
|
return this.lease !== void 0;
|
|
1077
1297
|
}
|
|
1078
|
-
/**
|
|
1298
|
+
/**
|
|
1299
|
+
* 初始化 RTC 引擎(获取独占租约)并注册事件桥接;已初始化时重复调用不产生效果。
|
|
1300
|
+
*/
|
|
1079
1301
|
async initialize() {
|
|
1080
1302
|
if (this.lease) {
|
|
1081
1303
|
return;
|
|
@@ -1084,7 +1306,9 @@ var RtcManager = class {
|
|
|
1084
1306
|
this.registerEventBridge(lease.engine);
|
|
1085
1307
|
this.lease = lease;
|
|
1086
1308
|
}
|
|
1087
|
-
/**
|
|
1309
|
+
/**
|
|
1310
|
+
* 停止采集与发布、销毁 RTC 引擎并释放租约;未初始化时重复调用不产生效果。
|
|
1311
|
+
*/
|
|
1088
1312
|
async destroy() {
|
|
1089
1313
|
const lease = this.lease;
|
|
1090
1314
|
this.lease = void 0;
|
|
@@ -1177,7 +1401,9 @@ var RtcManager = class {
|
|
|
1177
1401
|
throw this.mapError(error);
|
|
1178
1402
|
}
|
|
1179
1403
|
}
|
|
1180
|
-
/**
|
|
1404
|
+
/**
|
|
1405
|
+
* 停止摄像头采集;引擎未初始化或采集未启动时不产生效果。
|
|
1406
|
+
*/
|
|
1181
1407
|
async stopCameraCapture() {
|
|
1182
1408
|
const engine = this.lease?.engine;
|
|
1183
1409
|
this.isCapturing = false;
|
|
@@ -1225,7 +1451,9 @@ var RtcManager = class {
|
|
|
1225
1451
|
throw this.mapError(error);
|
|
1226
1452
|
}
|
|
1227
1453
|
}
|
|
1228
|
-
/**
|
|
1454
|
+
/**
|
|
1455
|
+
* 离开当前 RTC 房间;未在房间中或引擎未初始化时不产生效果,退房失败仅记录日志。
|
|
1456
|
+
*/
|
|
1229
1457
|
async leaveRoom() {
|
|
1230
1458
|
const engine = this.lease?.engine;
|
|
1231
1459
|
if (!engine || !this.isInRoom) {
|
|
@@ -1292,7 +1520,9 @@ var RtcManager = class {
|
|
|
1292
1520
|
throw this.mapError(error);
|
|
1293
1521
|
}
|
|
1294
1522
|
}
|
|
1295
|
-
/**
|
|
1523
|
+
/**
|
|
1524
|
+
* 停止发布本地视频流;采集保持运行,本地预览不受影响。
|
|
1525
|
+
*/
|
|
1296
1526
|
async unpublishLocalVideo() {
|
|
1297
1527
|
const engine = this.lease?.engine;
|
|
1298
1528
|
if (!engine || !this.isCapturing) {
|
|
@@ -1322,7 +1552,9 @@ var RtcManager = class {
|
|
|
1322
1552
|
throw this.mapError(error);
|
|
1323
1553
|
}
|
|
1324
1554
|
}
|
|
1325
|
-
/**
|
|
1555
|
+
/**
|
|
1556
|
+
* 停止发布本地音频流;麦克风采集保持运行。
|
|
1557
|
+
*/
|
|
1326
1558
|
async unpublishLocalAudio() {
|
|
1327
1559
|
const engine = this.lease?.engine;
|
|
1328
1560
|
if (!engine || !this.isAudioCapturing) {
|
|
@@ -1413,11 +1645,15 @@ var RtcManager = class {
|
|
|
1413
1645
|
data: encoded.buffer
|
|
1414
1646
|
});
|
|
1415
1647
|
}
|
|
1416
|
-
/**
|
|
1648
|
+
/**
|
|
1649
|
+
* 设置 RTC 事件监听器,传入空值时清除监听器。
|
|
1650
|
+
*/
|
|
1417
1651
|
setEventListener(listener) {
|
|
1418
1652
|
this.eventListener = listener;
|
|
1419
1653
|
}
|
|
1420
|
-
/**
|
|
1654
|
+
/**
|
|
1655
|
+
* 注册引擎事件桥接:媒体、消息及性能日志。
|
|
1656
|
+
*/
|
|
1421
1657
|
registerEventBridge(engine) {
|
|
1422
1658
|
const source = engine;
|
|
1423
1659
|
const onStatistics = (stats) => {
|
|
@@ -1499,7 +1735,9 @@ var RtcManager = class {
|
|
|
1499
1735
|
this.eventListener?.onCustomMessageReceived(event.userId, message);
|
|
1500
1736
|
});
|
|
1501
1737
|
}
|
|
1502
|
-
/**
|
|
1738
|
+
/**
|
|
1739
|
+
* 返回当前引擎实例;未初始化时抛出错误。
|
|
1740
|
+
*/
|
|
1503
1741
|
requireEngine() {
|
|
1504
1742
|
const engine = this.lease?.engine;
|
|
1505
1743
|
if (!engine) {
|
|
@@ -1510,7 +1748,9 @@ var RtcManager = class {
|
|
|
1510
1748
|
}
|
|
1511
1749
|
return engine;
|
|
1512
1750
|
}
|
|
1513
|
-
/**
|
|
1751
|
+
/**
|
|
1752
|
+
* 将 TRTC 错误映射为 SDK 统一错误。
|
|
1753
|
+
*/
|
|
1514
1754
|
mapError(error) {
|
|
1515
1755
|
if (error instanceof XmaxError) {
|
|
1516
1756
|
return error;
|
|
@@ -1528,28 +1768,16 @@ var RtcManager = class {
|
|
|
1528
1768
|
}
|
|
1529
1769
|
};
|
|
1530
1770
|
|
|
1531
|
-
// src/Foundation/Media/Camera/
|
|
1532
|
-
var
|
|
1533
|
-
var
|
|
1534
|
-
var
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
__publicField(this, "count", 0);
|
|
1540
|
-
}
|
|
1541
|
-
reset() {
|
|
1542
|
-
this.baseline = void 0;
|
|
1543
|
-
this.lastTime = void 0;
|
|
1544
|
-
this.count = 0;
|
|
1545
|
-
}
|
|
1546
|
-
/** 只接受新的采样帧;中央区域需足够亮,并在至少 400 ms 内保持稳定。 */
|
|
1547
|
-
accept(pixels, time) {
|
|
1771
|
+
// src/Foundation/Media/Camera/CameraFrameValidator.ts
|
|
1772
|
+
var FALLBACK_POLL_INTERVAL_MS = 16;
|
|
1773
|
+
var TIMEOUT_MS = 2e3;
|
|
1774
|
+
var CameraFrameValidator = class {
|
|
1775
|
+
/**
|
|
1776
|
+
* 单帧中央区域亮度达标即放行,不等待连续帧或曝光稳定。
|
|
1777
|
+
*/
|
|
1778
|
+
accept(pixels) {
|
|
1548
1779
|
const size = pixels.length / 4;
|
|
1549
|
-
if (!size || !Number.isInteger(size))
|
|
1550
|
-
this.reset();
|
|
1551
|
-
return false;
|
|
1552
|
-
}
|
|
1780
|
+
if (!size || !Number.isInteger(size)) return false;
|
|
1553
1781
|
const histogram = new Uint32Array(256);
|
|
1554
1782
|
let total = 0;
|
|
1555
1783
|
let dark = 0;
|
|
@@ -1566,22 +1794,10 @@ var CameraExposureGate = class {
|
|
|
1566
1794
|
if (accumulated >= size / 2) break;
|
|
1567
1795
|
}
|
|
1568
1796
|
const mean = total / size;
|
|
1569
|
-
|
|
1570
|
-
this.reset();
|
|
1571
|
-
return false;
|
|
1572
|
-
}
|
|
1573
|
-
const baseline = this.baseline;
|
|
1574
|
-
const stable = baseline && this.lastTime !== void 0 && time > this.lastTime && time - this.lastTime <= 250 && Math.abs(mean - baseline.mean) <= Math.max(5, baseline.mean * 0.1) && Math.abs(median - baseline.median) <= Math.max(5, baseline.median * 0.1);
|
|
1575
|
-
if (!stable) {
|
|
1576
|
-
this.baseline = { mean, median, time };
|
|
1577
|
-
this.count = 0;
|
|
1578
|
-
}
|
|
1579
|
-
this.lastTime = time;
|
|
1580
|
-
this.count++;
|
|
1581
|
-
return this.count >= 5 && time - this.baseline.time >= STABLE_DURATION_MS;
|
|
1797
|
+
return mean >= 24 && median >= 18 && dark / size <= 0.75;
|
|
1582
1798
|
}
|
|
1583
1799
|
};
|
|
1584
|
-
function
|
|
1800
|
+
function waitForValidCameraFrame(track, signal) {
|
|
1585
1801
|
return new Promise((resolve, reject) => {
|
|
1586
1802
|
let video;
|
|
1587
1803
|
let timeout;
|
|
@@ -1589,8 +1805,8 @@ function waitForCameraExposure(track, signal) {
|
|
|
1589
1805
|
let frameCallback;
|
|
1590
1806
|
let finished = false;
|
|
1591
1807
|
const started = performance.now();
|
|
1592
|
-
const gate = new
|
|
1593
|
-
const finish = (
|
|
1808
|
+
const gate = new CameraFrameValidator();
|
|
1809
|
+
const finish = (result = "valid") => {
|
|
1594
1810
|
if (finished) return;
|
|
1595
1811
|
finished = true;
|
|
1596
1812
|
clearTimeout(timeout);
|
|
@@ -1603,12 +1819,19 @@ function waitForCameraExposure(track, signal) {
|
|
|
1603
1819
|
}
|
|
1604
1820
|
signal.removeEventListener("abort", abort);
|
|
1605
1821
|
track.removeEventListener("ended", ended);
|
|
1606
|
-
if (
|
|
1607
|
-
if (
|
|
1608
|
-
reject(
|
|
1822
|
+
if (result instanceof XmaxError) {
|
|
1823
|
+
if (result.code !== "CANCELLED" /* cancelled */) XmaxLogger.media.warning(() => result.message);
|
|
1824
|
+
reject(result);
|
|
1609
1825
|
} else {
|
|
1610
|
-
|
|
1826
|
+
if (result === "timeout") {
|
|
1827
|
+
XmaxLogger.media.warning(() => XmaxLogger.localized(
|
|
1828
|
+
"\u76F8\u673A\u4EAE\u5EA6\u68C0\u67E5\u8D85\u65F6\uFF0C\u6309\u5F53\u524D\u753B\u9762\u7EE7\u7EED\u751F\u6210 (Camera Frame Validation Timed Out; Continuing)\n\u2514\u2500 \u672A\u83B7\u5F97\u4EAE\u5EA6\u5408\u683C\u5E27\uFF0C\u4E0D\u518D\u7B49\u5F85\u3002",
|
|
1829
|
+
"Camera Frame Validation Timed Out; Continuing\n\u2514\u2500 No sufficiently bright frame received; proceeding without further waiting."
|
|
1830
|
+
));
|
|
1831
|
+
} else {
|
|
1832
|
+
XmaxLogger.media.info(() => `\u76F8\u673A\u66DD\u5149\u68C0\u67E5\u901A\u8FC7 (Camera Exposure Ready)
|
|
1611
1833
|
\u2514\u2500 ${Math.round(performance.now() - started)} ms`);
|
|
1834
|
+
}
|
|
1612
1835
|
resolve();
|
|
1613
1836
|
}
|
|
1614
1837
|
};
|
|
@@ -1639,21 +1862,17 @@ function waitForCameraExposure(track, signal) {
|
|
|
1639
1862
|
video.srcObject = new MediaStream([track]);
|
|
1640
1863
|
document.body.appendChild(video);
|
|
1641
1864
|
const source = video;
|
|
1642
|
-
let lastSample = -Infinity;
|
|
1643
1865
|
let lastFrameTime = -Infinity;
|
|
1644
|
-
const sample = (
|
|
1645
|
-
if (now - lastSample < SAMPLE_INTERVAL_MS) return;
|
|
1866
|
+
const sample = (mediaTime) => {
|
|
1646
1867
|
if (source.readyState < 2 || !source.videoWidth || !source.videoHeight || track.muted || !track.enabled) {
|
|
1647
|
-
gate.reset();
|
|
1648
1868
|
return;
|
|
1649
1869
|
}
|
|
1650
1870
|
if (mediaTime === lastFrameTime) return;
|
|
1651
|
-
lastSample = now;
|
|
1652
1871
|
lastFrameTime = mediaTime;
|
|
1653
1872
|
const width = source.videoWidth;
|
|
1654
1873
|
const height = source.videoHeight;
|
|
1655
1874
|
context.drawImage(source, width * 0.2, height * 0.2, width * 0.6, height * 0.6, 0, 0, 64, 48);
|
|
1656
|
-
if (gate.accept(context.getImageData(0, 0, 64, 48).data
|
|
1875
|
+
if (gate.accept(context.getImageData(0, 0, 64, 48).data)) finish();
|
|
1657
1876
|
};
|
|
1658
1877
|
const fail = (error) => finish(new XmaxError(
|
|
1659
1878
|
"MEDIA_ERROR" /* mediaError */,
|
|
@@ -1662,10 +1881,10 @@ function waitForCameraExposure(track, signal) {
|
|
|
1662
1881
|
const schedule = () => {
|
|
1663
1882
|
if (finished) return;
|
|
1664
1883
|
if (typeof source.requestVideoFrameCallback === "function") {
|
|
1665
|
-
frameCallback = source.requestVideoFrameCallback((
|
|
1884
|
+
frameCallback = source.requestVideoFrameCallback((_now, metadata) => {
|
|
1666
1885
|
frameCallback = void 0;
|
|
1667
1886
|
try {
|
|
1668
|
-
sample(
|
|
1887
|
+
sample(metadata.mediaTime);
|
|
1669
1888
|
schedule();
|
|
1670
1889
|
} catch (error) {
|
|
1671
1890
|
fail(error);
|
|
@@ -1675,22 +1894,16 @@ function waitForCameraExposure(track, signal) {
|
|
|
1675
1894
|
poll = setTimeout(() => {
|
|
1676
1895
|
try {
|
|
1677
1896
|
const frameTime = typeof source.getVideoPlaybackQuality === "function" ? source.getVideoPlaybackQuality().totalVideoFrames : source.currentTime;
|
|
1678
|
-
sample(
|
|
1897
|
+
sample(frameTime);
|
|
1679
1898
|
schedule();
|
|
1680
1899
|
} catch (error) {
|
|
1681
1900
|
fail(error);
|
|
1682
1901
|
}
|
|
1683
|
-
},
|
|
1902
|
+
}, FALLBACK_POLL_INTERVAL_MS);
|
|
1684
1903
|
}
|
|
1685
1904
|
};
|
|
1686
|
-
timeout = setTimeout(() => finish(
|
|
1687
|
-
|
|
1688
|
-
XmaxLogger.localized(
|
|
1689
|
-
"\u76F8\u673A\u753B\u9762\u8FC7\u6697\u6216\u5C1A\u672A\u7A33\u5B9A\uFF0C\u672A\u53D1\u5E03\u89C6\u9891\u3002\u8BF7\u6539\u5584\u5149\u7167\u540E\u91CD\u8BD5\u3002",
|
|
1690
|
-
"Camera frames are too dark or not stable. Video was not published. Improve lighting and retry."
|
|
1691
|
-
)
|
|
1692
|
-
)), TIMEOUT_MS);
|
|
1693
|
-
XmaxLogger.media.info(() => "\u7B49\u5F85\u76F8\u673A\u66DD\u5149\u7A33\u5B9A (Waiting for Camera Exposure)");
|
|
1905
|
+
timeout = setTimeout(() => finish("timeout"), TIMEOUT_MS);
|
|
1906
|
+
XmaxLogger.media.info(() => "\u7B49\u5F85\u9996\u5F20\u4EAE\u5EA6\u5408\u683C\u5E27 (Waiting for the First Sufficiently Bright Frame)");
|
|
1694
1907
|
void source.play().then(schedule, fail);
|
|
1695
1908
|
} catch (error) {
|
|
1696
1909
|
finish(new XmaxError(
|
|
@@ -1760,19 +1973,31 @@ var PermissionManager = class {
|
|
|
1760
1973
|
|
|
1761
1974
|
// src/Service/Realtime/VideoRenderBinding.ts
|
|
1762
1975
|
var _VideoRenderRegistry = class _VideoRenderRegistry {
|
|
1763
|
-
/**
|
|
1976
|
+
/**
|
|
1977
|
+
* 注册轨道的自定义渲染绑定,覆盖该轨道已有的绑定。
|
|
1978
|
+
* @internal
|
|
1979
|
+
*/
|
|
1764
1980
|
static register(track, binding) {
|
|
1765
1981
|
_VideoRenderRegistry.bindings.set(track, binding);
|
|
1766
1982
|
}
|
|
1767
|
-
/**
|
|
1983
|
+
/**
|
|
1984
|
+
* 移除轨道的渲染绑定,不执行视图解绑或资源释放。
|
|
1985
|
+
* @internal
|
|
1986
|
+
*/
|
|
1768
1987
|
static unregister(track) {
|
|
1769
1988
|
_VideoRenderRegistry.bindings.delete(track);
|
|
1770
1989
|
}
|
|
1771
|
-
/**
|
|
1990
|
+
/**
|
|
1991
|
+
* 查询轨道的自定义渲染绑定;未注册时返回 undefined。
|
|
1992
|
+
* @internal
|
|
1993
|
+
*/
|
|
1772
1994
|
static binding(forTrack) {
|
|
1773
1995
|
return _VideoRenderRegistry.bindings.get(forTrack);
|
|
1774
1996
|
}
|
|
1775
1997
|
};
|
|
1998
|
+
/**
|
|
1999
|
+
* 轨道渲染绑定索引
|
|
2000
|
+
*/
|
|
1776
2001
|
__publicField(_VideoRenderRegistry, "bindings", /* @__PURE__ */ new WeakMap());
|
|
1777
2002
|
var VideoRenderRegistry = _VideoRenderRegistry;
|
|
1778
2003
|
|
|
@@ -1786,12 +2011,20 @@ var RealtimeVideoTrack = class {
|
|
|
1786
2011
|
* @param init.position 本地相机轨道的初始摄像头位置。
|
|
1787
2012
|
*/
|
|
1788
2013
|
constructor(init) {
|
|
1789
|
-
|
|
1790
|
-
|
|
2014
|
+
/**
|
|
2015
|
+
* 轨道信息
|
|
2016
|
+
*/
|
|
2017
|
+
/**
|
|
2018
|
+
* 视频轨道标识。
|
|
2019
|
+
*/
|
|
1791
2020
|
__publicField(this, "id");
|
|
1792
|
-
|
|
2021
|
+
/**
|
|
2022
|
+
* 动态元数据
|
|
2023
|
+
*/
|
|
1793
2024
|
__publicField(this, "metadata");
|
|
1794
|
-
|
|
2025
|
+
/**
|
|
2026
|
+
* 平台资源
|
|
2027
|
+
*/
|
|
1795
2028
|
/**
|
|
1796
2029
|
* SDK 内部:轨道对应的 Web 媒体轨。
|
|
1797
2030
|
* 本地轨道在采集启动后可用;远端轨道在订阅成功后可用。
|
|
@@ -1803,19 +2036,27 @@ var RealtimeVideoTrack = class {
|
|
|
1803
2036
|
position: init.position
|
|
1804
2037
|
};
|
|
1805
2038
|
}
|
|
1806
|
-
/**
|
|
2039
|
+
/**
|
|
2040
|
+
* 当前视频轨道使用的视频格式。
|
|
2041
|
+
*/
|
|
1807
2042
|
get videoFormat() {
|
|
1808
2043
|
return this.metadata.videoFormat;
|
|
1809
2044
|
}
|
|
1810
|
-
/**
|
|
2045
|
+
/**
|
|
2046
|
+
* 本地相机轨道当前使用的摄像头位置。
|
|
2047
|
+
*/
|
|
1811
2048
|
get position() {
|
|
1812
2049
|
return this.metadata.position;
|
|
1813
2050
|
}
|
|
1814
|
-
/**
|
|
2051
|
+
/**
|
|
2052
|
+
* 更新轨道视频格式。 @internal
|
|
2053
|
+
*/
|
|
1815
2054
|
updateVideoFormat(videoFormat) {
|
|
1816
2055
|
this.metadata.videoFormat = videoFormat;
|
|
1817
2056
|
}
|
|
1818
|
-
/**
|
|
2057
|
+
/**
|
|
2058
|
+
* 更新轨道摄像头位置。 @internal
|
|
2059
|
+
*/
|
|
1819
2060
|
updatePosition(position) {
|
|
1820
2061
|
this.metadata.position = position;
|
|
1821
2062
|
}
|
|
@@ -1830,9 +2071,16 @@ var RealtimeMediaStream = class {
|
|
|
1830
2071
|
* @param init.videoTrack 媒体流包含的视频轨道。
|
|
1831
2072
|
*/
|
|
1832
2073
|
constructor(init) {
|
|
1833
|
-
/**
|
|
2074
|
+
/**
|
|
2075
|
+
* 流标识与轨道
|
|
2076
|
+
*/
|
|
2077
|
+
/**
|
|
2078
|
+
* 媒体流标识。
|
|
2079
|
+
*/
|
|
1834
2080
|
__publicField(this, "id");
|
|
1835
|
-
/**
|
|
2081
|
+
/**
|
|
2082
|
+
* 媒体流包含的视频轨道。
|
|
2083
|
+
*/
|
|
1836
2084
|
__publicField(this, "videoTrack");
|
|
1837
2085
|
this.id = init.id;
|
|
1838
2086
|
this.videoTrack = init.videoTrack;
|
|
@@ -1850,47 +2098,77 @@ var _CameraController = class _CameraController {
|
|
|
1850
2098
|
* @param options.errorListener 运行期错误回调。
|
|
1851
2099
|
*/
|
|
1852
2100
|
constructor(options) {
|
|
1853
|
-
|
|
2101
|
+
/**
|
|
2102
|
+
* 基础层组件
|
|
2103
|
+
*/
|
|
1854
2104
|
__publicField(this, "permissionManager");
|
|
1855
2105
|
__publicField(this, "rtcManager");
|
|
1856
|
-
|
|
2106
|
+
/**
|
|
2107
|
+
* 服务层组件
|
|
2108
|
+
*/
|
|
1857
2109
|
__publicField(this, "mediaService");
|
|
1858
|
-
|
|
2110
|
+
/**
|
|
2111
|
+
* 事件监听
|
|
2112
|
+
*/
|
|
1859
2113
|
__publicField(this, "errorListener");
|
|
1860
2114
|
__publicField(this, "previewReadyHandler");
|
|
1861
|
-
|
|
2115
|
+
/**
|
|
2116
|
+
* 本地资源
|
|
2117
|
+
*/
|
|
1862
2118
|
__publicField(this, "activeTrack");
|
|
1863
2119
|
__publicField(this, "previewStream");
|
|
1864
2120
|
__publicField(this, "previewMirrorApplier");
|
|
1865
|
-
|
|
2121
|
+
/**
|
|
2122
|
+
* 预览状态
|
|
2123
|
+
*/
|
|
1866
2124
|
__publicField(this, "hasCapturedFrame", false);
|
|
1867
2125
|
__publicField(this, "isPreviewAttached", false);
|
|
1868
|
-
|
|
2126
|
+
/**
|
|
2127
|
+
* 当前相机流配置
|
|
2128
|
+
*/
|
|
1869
2129
|
__publicField(this, "storedUseMicrophone", false);
|
|
2130
|
+
__publicField(this, "storedFrameValidationEnabled", true);
|
|
1870
2131
|
this.permissionManager = options?.permissionManager ?? new PermissionManager();
|
|
1871
2132
|
this.rtcManager = options?.rtcManager ?? new RtcManager();
|
|
1872
2133
|
this.mediaService = options?.mediaService ?? new MediaService();
|
|
1873
2134
|
this.errorListener = options?.errorListener ?? (() => {
|
|
1874
2135
|
});
|
|
1875
2136
|
}
|
|
1876
|
-
/**
|
|
2137
|
+
/**
|
|
2138
|
+
* 当前活动的本地相机视频轨道;尚未创建时为空。
|
|
2139
|
+
*/
|
|
1877
2140
|
get currentTrack() {
|
|
1878
2141
|
return this.activeTrack;
|
|
1879
2142
|
}
|
|
1880
|
-
/**
|
|
2143
|
+
/**
|
|
2144
|
+
* 当前相机流是否配置为使用麦克风。
|
|
2145
|
+
*/
|
|
1881
2146
|
get useMicrophone() {
|
|
1882
2147
|
return this.activeTrack !== void 0 && this.storedUseMicrophone;
|
|
1883
2148
|
}
|
|
1884
|
-
|
|
2149
|
+
/**
|
|
2150
|
+
* 当前活动相机流是否启用发布前亮度检测;没有活动流时为 false。
|
|
2151
|
+
*/
|
|
2152
|
+
get isFrameValidationEnabled() {
|
|
2153
|
+
return this.activeTrack !== void 0 && this.storedFrameValidationEnabled;
|
|
2154
|
+
}
|
|
2155
|
+
/**
|
|
2156
|
+
* 等待当前轨道首张合格帧或超时放行;禁用时跳过,取消或轨道被替换时拒绝。
|
|
2157
|
+
*/
|
|
2158
|
+
async waitForValidCameraFrame(signal) {
|
|
1885
2159
|
const track = this.activeTrack;
|
|
1886
2160
|
const mediaTrack = track?.mediaStreamTrack;
|
|
1887
2161
|
if (!mediaTrack) throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Camera capture is not running");
|
|
1888
|
-
|
|
2162
|
+
if (signal.aborted) throw new XmaxError("CANCELLED" /* cancelled */, "Camera frame validation cancelled");
|
|
2163
|
+
if (!this.isFrameValidationEnabled) return;
|
|
2164
|
+
await waitForValidCameraFrame(mediaTrack, signal);
|
|
1889
2165
|
if (this.activeTrack !== track || track.mediaStreamTrack !== mediaTrack) {
|
|
1890
2166
|
throw new XmaxError("CANCELLED" /* cancelled */, "Camera track changed during exposure check");
|
|
1891
2167
|
}
|
|
1892
2168
|
}
|
|
1893
|
-
/**
|
|
2169
|
+
/**
|
|
2170
|
+
* 设置当前相机流的一次性内部就绪处理;条件为已收到有效帧且预览已绑定。
|
|
2171
|
+
*/
|
|
1894
2172
|
setPreviewReadyHandler(handler) {
|
|
1895
2173
|
this.previewReadyHandler = handler;
|
|
1896
2174
|
const track = this.activeTrack;
|
|
@@ -1904,6 +2182,7 @@ var _CameraController = class _CameraController {
|
|
|
1904
2182
|
* @param options.videoFormat 期望的输出尺寸、帧率和编码配置;尺寸按模型规则调整。
|
|
1905
2183
|
* @param options.position 首次启动时使用的摄像头位置。
|
|
1906
2184
|
* @param options.useMicrophone 是否申请麦克风权限并允许实时连接时启动音频采集。
|
|
2185
|
+
* @param options.enableFrameValidation 是否在发布前检测帧亮度,默认 true;false 跳过检测及其等待。
|
|
1907
2186
|
* @returns 包含本地相机视频轨道的媒体流。
|
|
1908
2187
|
* @throws 已有活动相机流、格式无效、权限不足或采集启动失败时抛出错误。
|
|
1909
2188
|
*/
|
|
@@ -1928,6 +2207,7 @@ var _CameraController = class _CameraController {
|
|
|
1928
2207
|
await this.rtcManager.initialize();
|
|
1929
2208
|
this.activeTrack = track;
|
|
1930
2209
|
this.storedUseMicrophone = options.useMicrophone;
|
|
2210
|
+
this.storedFrameValidationEnabled = options.enableFrameValidation ?? true;
|
|
1931
2211
|
this.hasCapturedFrame = false;
|
|
1932
2212
|
this.isPreviewAttached = false;
|
|
1933
2213
|
this.registerPreviewBinding(track);
|
|
@@ -1946,7 +2226,9 @@ var _CameraController = class _CameraController {
|
|
|
1946
2226
|
throw XmaxError.from(error);
|
|
1947
2227
|
}
|
|
1948
2228
|
}
|
|
1949
|
-
/**
|
|
2229
|
+
/**
|
|
2230
|
+
* 停止相机采集并销毁 RTC 引擎,释放当前轨道及本地预览资源。
|
|
2231
|
+
*/
|
|
1950
2232
|
async stopLocalCameraStream() {
|
|
1951
2233
|
const track = this.activeTrack;
|
|
1952
2234
|
this.activeTrack = void 0;
|
|
@@ -1954,6 +2236,7 @@ var _CameraController = class _CameraController {
|
|
|
1954
2236
|
this.previewStream = void 0;
|
|
1955
2237
|
this.previewMirrorApplier = void 0;
|
|
1956
2238
|
this.storedUseMicrophone = false;
|
|
2239
|
+
this.storedFrameValidationEnabled = true;
|
|
1957
2240
|
this.hasCapturedFrame = false;
|
|
1958
2241
|
this.isPreviewAttached = false;
|
|
1959
2242
|
await this.rtcManager.stopCameraCapture();
|
|
@@ -1996,7 +2279,9 @@ var _CameraController = class _CameraController {
|
|
|
1996
2279
|
this.observeMediaTrack(track, mediaTrack);
|
|
1997
2280
|
return new RealtimeMediaStream({ id: "stream0" /* local */, videoTrack: track });
|
|
1998
2281
|
}
|
|
1999
|
-
/**
|
|
2282
|
+
/**
|
|
2283
|
+
* 为相机轨道注册预览渲染绑定:attach 时挂流、镜像并标记预览已绑定。
|
|
2284
|
+
*/
|
|
2000
2285
|
registerPreviewBinding(track) {
|
|
2001
2286
|
VideoRenderRegistry.register(track, {
|
|
2002
2287
|
attachHandler: (view) => {
|
|
@@ -2022,7 +2307,9 @@ var _CameraController = class _CameraController {
|
|
|
2022
2307
|
}
|
|
2023
2308
|
});
|
|
2024
2309
|
}
|
|
2025
|
-
/**
|
|
2310
|
+
/**
|
|
2311
|
+
* 监听媒体轨首帧与意外结束:首帧推进预览就绪,意外结束上报错误。
|
|
2312
|
+
*/
|
|
2026
2313
|
observeMediaTrack(track, mediaTrack) {
|
|
2027
2314
|
const isCurrent = () => this.activeTrack === track && track.mediaStreamTrack === mediaTrack;
|
|
2028
2315
|
if (mediaTrack.muted) {
|
|
@@ -2053,7 +2340,9 @@ var _CameraController = class _CameraController {
|
|
|
2053
2340
|
);
|
|
2054
2341
|
});
|
|
2055
2342
|
}
|
|
2056
|
-
/**
|
|
2343
|
+
/**
|
|
2344
|
+
* 满足"已收到有效帧且预览已绑定"时触发一次性就绪回调。
|
|
2345
|
+
*/
|
|
2057
2346
|
notifyPreviewReady(track) {
|
|
2058
2347
|
if (this.activeTrack !== track || !this.hasCapturedFrame || !this.isPreviewAttached || !this.previewReadyHandler) {
|
|
2059
2348
|
return;
|
|
@@ -2062,7 +2351,9 @@ var _CameraController = class _CameraController {
|
|
|
2062
2351
|
this.previewReadyHandler = void 0;
|
|
2063
2352
|
handler(() => this.activeTrack === track);
|
|
2064
2353
|
}
|
|
2065
|
-
/**
|
|
2354
|
+
/**
|
|
2355
|
+
* 按模型输入规则校验并解析目标视频规格。
|
|
2356
|
+
*/
|
|
2066
2357
|
resolveVideoFormat(videoFormat) {
|
|
2067
2358
|
videoFormat.validate();
|
|
2068
2359
|
const targetSize = this.mediaService.resolveModelInputSize({
|
|
@@ -2074,7 +2365,9 @@ var _CameraController = class _CameraController {
|
|
|
2074
2365
|
return resolvedFormat;
|
|
2075
2366
|
}
|
|
2076
2367
|
};
|
|
2077
|
-
|
|
2368
|
+
/**
|
|
2369
|
+
* 轨道标识
|
|
2370
|
+
*/
|
|
2078
2371
|
__publicField(_CameraController, "localVideoTrackID", "video0");
|
|
2079
2372
|
var CameraController = _CameraController;
|
|
2080
2373
|
|
|
@@ -2090,18 +2383,34 @@ var RealtimeSession = class {
|
|
|
2090
2383
|
* @param init.closeReason 会话关闭原因。
|
|
2091
2384
|
*/
|
|
2092
2385
|
constructor(init) {
|
|
2093
|
-
|
|
2094
|
-
|
|
2386
|
+
/**
|
|
2387
|
+
* 会话信息
|
|
2388
|
+
*/
|
|
2389
|
+
/**
|
|
2390
|
+
* 会话标识(sessionUid)。
|
|
2391
|
+
*/
|
|
2095
2392
|
__publicField(this, "id");
|
|
2096
|
-
/**
|
|
2393
|
+
/**
|
|
2394
|
+
* 业务用户标识。
|
|
2395
|
+
*/
|
|
2097
2396
|
__publicField(this, "userID");
|
|
2098
|
-
|
|
2099
|
-
|
|
2397
|
+
/**
|
|
2398
|
+
* 运行状态
|
|
2399
|
+
*/
|
|
2400
|
+
/**
|
|
2401
|
+
* 会话状态(如 ACTIVE);响应未携带时为空。
|
|
2402
|
+
*/
|
|
2100
2403
|
__publicField(this, "status");
|
|
2101
|
-
/**
|
|
2404
|
+
/**
|
|
2405
|
+
* 会话关闭原因。
|
|
2406
|
+
*/
|
|
2102
2407
|
__publicField(this, "closeReason");
|
|
2103
|
-
|
|
2104
|
-
|
|
2408
|
+
/**
|
|
2409
|
+
* RTC 连接
|
|
2410
|
+
*/
|
|
2411
|
+
/**
|
|
2412
|
+
* RTC 连接参数;心跳等响应未携带完整参数时为空。
|
|
2413
|
+
*/
|
|
2105
2414
|
__publicField(this, "connection");
|
|
2106
2415
|
this.id = init.id;
|
|
2107
2416
|
this.userID = init.userID;
|
|
@@ -2125,22 +2434,42 @@ var RealtimeSessionConnection = class {
|
|
|
2125
2434
|
* @param init.botID 房间内生成 Bot 的用户标识。
|
|
2126
2435
|
*/
|
|
2127
2436
|
constructor(init) {
|
|
2128
|
-
|
|
2129
|
-
|
|
2437
|
+
/**
|
|
2438
|
+
* RTC 提供方
|
|
2439
|
+
*/
|
|
2440
|
+
/**
|
|
2441
|
+
* RTC 提供方标识(当前仅支持 `trtc`)。
|
|
2442
|
+
*/
|
|
2130
2443
|
__publicField(this, "provider");
|
|
2131
|
-
|
|
2132
|
-
|
|
2444
|
+
/**
|
|
2445
|
+
* 进房参数
|
|
2446
|
+
*/
|
|
2447
|
+
/**
|
|
2448
|
+
* TRTC 房间号(字符串房间号)。
|
|
2449
|
+
*/
|
|
2133
2450
|
__publicField(this, "roomID");
|
|
2134
|
-
/**
|
|
2451
|
+
/**
|
|
2452
|
+
* TRTC SDKAppID(按字符串处理)。
|
|
2453
|
+
*/
|
|
2135
2454
|
__publicField(this, "sdkAppID");
|
|
2136
|
-
/**
|
|
2455
|
+
/**
|
|
2456
|
+
* TRTC 登录身份(`rtc_user_id`,不是业务 `user_id`)。
|
|
2457
|
+
*/
|
|
2137
2458
|
__publicField(this, "userID");
|
|
2138
|
-
/**
|
|
2459
|
+
/**
|
|
2460
|
+
* TRTC 登录签名。
|
|
2461
|
+
*/
|
|
2139
2462
|
__publicField(this, "userSig");
|
|
2140
|
-
/**
|
|
2463
|
+
/**
|
|
2464
|
+
* TRTC 进房鉴权字段(字符串房间号对应的 privateMapKey)。
|
|
2465
|
+
*/
|
|
2141
2466
|
__publicField(this, "privateMapKey");
|
|
2142
|
-
|
|
2143
|
-
|
|
2467
|
+
/**
|
|
2468
|
+
* 房间成员
|
|
2469
|
+
*/
|
|
2470
|
+
/**
|
|
2471
|
+
* 房间内生成 Bot 的用户标识,用于匹配远端结果流。
|
|
2472
|
+
*/
|
|
2144
2473
|
__publicField(this, "botID");
|
|
2145
2474
|
this.provider = init.provider;
|
|
2146
2475
|
this.roomID = init.roomID;
|
|
@@ -2162,34 +2491,48 @@ var RealtimeSessionService = class _RealtimeSessionService {
|
|
|
2162
2491
|
* @param options.sleep 心跳等待实现(可替换,测试用)。
|
|
2163
2492
|
*/
|
|
2164
2493
|
constructor(options) {
|
|
2165
|
-
|
|
2494
|
+
/**
|
|
2495
|
+
* 服务层组件
|
|
2496
|
+
*/
|
|
2166
2497
|
__publicField(this, "apiService");
|
|
2167
|
-
|
|
2498
|
+
/**
|
|
2499
|
+
* 心跳配置
|
|
2500
|
+
*/
|
|
2168
2501
|
__publicField(this, "heartbeatIntervalMs");
|
|
2169
2502
|
__publicField(this, "sleep");
|
|
2170
|
-
|
|
2503
|
+
/**
|
|
2504
|
+
* 运行状态
|
|
2505
|
+
*/
|
|
2171
2506
|
__publicField(this, "heartbeatVersion", 0);
|
|
2172
2507
|
this.apiService = options.apiService;
|
|
2173
2508
|
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? 1e4;
|
|
2174
2509
|
this.sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2175
2510
|
}
|
|
2176
|
-
/**
|
|
2511
|
+
/**
|
|
2512
|
+
* 创建实时会话并返回 RTC 连接信息。
|
|
2513
|
+
*/
|
|
2177
2514
|
async createSession(model) {
|
|
2178
2515
|
const payload = await this.apiService.post("/session", {
|
|
2179
2516
|
model
|
|
2180
2517
|
});
|
|
2181
2518
|
return _RealtimeSessionService.makeSession(payload, true);
|
|
2182
2519
|
}
|
|
2183
|
-
/**
|
|
2520
|
+
/**
|
|
2521
|
+
* 启动指定会话的周期心跳;重复启动会替换当前心跳。
|
|
2522
|
+
*/
|
|
2184
2523
|
startHeartbeat(sessionID, handlers) {
|
|
2185
2524
|
const version = ++this.heartbeatVersion;
|
|
2186
2525
|
void this.runHeartbeat(version, sessionID, handlers);
|
|
2187
2526
|
}
|
|
2188
|
-
/**
|
|
2527
|
+
/**
|
|
2528
|
+
* 停止当前心跳;已经失效的迟到结果不会再触发回调。
|
|
2529
|
+
*/
|
|
2189
2530
|
stopHeartbeat() {
|
|
2190
2531
|
this.heartbeatVersion += 1;
|
|
2191
2532
|
}
|
|
2192
|
-
/**
|
|
2533
|
+
/**
|
|
2534
|
+
* 关闭指定实时会话。
|
|
2535
|
+
*/
|
|
2193
2536
|
async closeSession(sessionID) {
|
|
2194
2537
|
try {
|
|
2195
2538
|
await this.apiService.delete(
|
|
@@ -2199,7 +2542,9 @@ var RealtimeSessionService = class _RealtimeSessionService {
|
|
|
2199
2542
|
throw XmaxError.from(error);
|
|
2200
2543
|
}
|
|
2201
2544
|
}
|
|
2202
|
-
/**
|
|
2545
|
+
/**
|
|
2546
|
+
* 周期心跳:等待 → 请求 → 校验会话活跃并回调刷新;失败时失效版本并回调。
|
|
2547
|
+
*/
|
|
2203
2548
|
async runHeartbeat(version, sessionID, handlers) {
|
|
2204
2549
|
while (this.heartbeatVersion === version) {
|
|
2205
2550
|
await this.sleep(this.heartbeatIntervalMs);
|
|
@@ -2223,14 +2568,18 @@ var RealtimeSessionService = class _RealtimeSessionService {
|
|
|
2223
2568
|
}
|
|
2224
2569
|
}
|
|
2225
2570
|
}
|
|
2226
|
-
/**
|
|
2571
|
+
/**
|
|
2572
|
+
* 发送一次心跳请求并解析会话数据(不要求携带完整连接参数)。
|
|
2573
|
+
*/
|
|
2227
2574
|
async heartbeatSession(sessionID) {
|
|
2228
2575
|
const payload = await this.apiService.put(
|
|
2229
2576
|
`/session/${sessionID}/heartbeat`
|
|
2230
2577
|
);
|
|
2231
2578
|
return _RealtimeSessionService.makeSession(payload, false);
|
|
2232
2579
|
}
|
|
2233
|
-
/**
|
|
2580
|
+
/**
|
|
2581
|
+
* 校验会话仍处于活跃状态;状态明确非 ACTIVE 时抛出会话错误。
|
|
2582
|
+
*/
|
|
2234
2583
|
static ensureSessionActive(session) {
|
|
2235
2584
|
if (!session.status || session.status === "ACTIVE") {
|
|
2236
2585
|
return;
|
|
@@ -2240,7 +2589,9 @@ var RealtimeSessionService = class _RealtimeSessionService {
|
|
|
2240
2589
|
session.closeReason ?? `Session is no longer active: ${session.status}`
|
|
2241
2590
|
);
|
|
2242
2591
|
}
|
|
2243
|
-
/**
|
|
2592
|
+
/**
|
|
2593
|
+
* 由会话接口数据构造会话模型;`requiresConnection` 要求携带完整 RTC 连接参数。
|
|
2594
|
+
*/
|
|
2244
2595
|
static makeSession(payload, requiresConnection) {
|
|
2245
2596
|
const sessionID = _RealtimeSessionService.nonEmpty(payload.sessionUid);
|
|
2246
2597
|
if (!sessionID) {
|
|
@@ -2261,7 +2612,9 @@ var RealtimeSessionService = class _RealtimeSessionService {
|
|
|
2261
2612
|
closeReason: _RealtimeSessionService.nonEmpty(payload.closeReason)
|
|
2262
2613
|
});
|
|
2263
2614
|
}
|
|
2264
|
-
/**
|
|
2615
|
+
/**
|
|
2616
|
+
* 解析 `modelExtra` 中的 TRTC 连接参数;字段不完整或提供方不支持时返回空。
|
|
2617
|
+
*/
|
|
2265
2618
|
static makeConnection(modelExtra) {
|
|
2266
2619
|
const payload = _RealtimeSessionService.decodeConnectionPayload(modelExtra);
|
|
2267
2620
|
if (!payload) {
|
|
@@ -2290,7 +2643,9 @@ var RealtimeSessionService = class _RealtimeSessionService {
|
|
|
2290
2643
|
botID: _RealtimeSessionService.nonEmpty(payload.rtc_bot_id)
|
|
2291
2644
|
});
|
|
2292
2645
|
}
|
|
2293
|
-
/**
|
|
2646
|
+
/**
|
|
2647
|
+
* `modelExtra` 可能为对象或 JSON 字符串,统一解析为连接参数字段。
|
|
2648
|
+
*/
|
|
2294
2649
|
static decodeConnectionPayload(modelExtra) {
|
|
2295
2650
|
if (typeof modelExtra === "string") {
|
|
2296
2651
|
try {
|
|
@@ -2304,10 +2659,12 @@ var RealtimeSessionService = class _RealtimeSessionService {
|
|
|
2304
2659
|
}
|
|
2305
2660
|
return void 0;
|
|
2306
2661
|
}
|
|
2307
|
-
/**
|
|
2662
|
+
/**
|
|
2663
|
+
* 归一化可选字符串:去除首尾空白后为空时返回 `undefined`。
|
|
2664
|
+
*/
|
|
2308
2665
|
static nonEmpty(value) {
|
|
2309
2666
|
const normalized = value?.trim();
|
|
2310
|
-
return normalized
|
|
2667
|
+
return normalized || void 0;
|
|
2311
2668
|
}
|
|
2312
2669
|
};
|
|
2313
2670
|
|
|
@@ -2325,6 +2682,9 @@ var RealtimeConnectionState = /* @__PURE__ */ ((RealtimeConnectionState2) => {
|
|
|
2325
2682
|
var RealtimeReason = {
|
|
2326
2683
|
normal: { kind: "normal" },
|
|
2327
2684
|
orientationChanged: { kind: "orientationChanged" },
|
|
2685
|
+
/**
|
|
2686
|
+
* 将 SDK 错误包装为状态切换原因,保留错误码和原始说明。
|
|
2687
|
+
*/
|
|
2328
2688
|
failure(error) {
|
|
2329
2689
|
return { kind: "failure", error };
|
|
2330
2690
|
}
|
|
@@ -2339,13 +2699,30 @@ var RealtimeState = class {
|
|
|
2339
2699
|
* @param init.reason 状态变化原因,默认无。
|
|
2340
2700
|
*/
|
|
2341
2701
|
constructor(init) {
|
|
2342
|
-
/**
|
|
2702
|
+
/**
|
|
2703
|
+
* 连接生命周期
|
|
2704
|
+
*/
|
|
2705
|
+
/**
|
|
2706
|
+
* 当前连接生命周期状态。
|
|
2707
|
+
*/
|
|
2343
2708
|
__publicField(this, "connectionState");
|
|
2344
|
-
/**
|
|
2709
|
+
/**
|
|
2710
|
+
* 业务标识
|
|
2711
|
+
*/
|
|
2712
|
+
/**
|
|
2713
|
+
* 当前或最近一次实时 Session 标识。
|
|
2714
|
+
*/
|
|
2345
2715
|
__publicField(this, "sessionID");
|
|
2346
|
-
/**
|
|
2716
|
+
/**
|
|
2717
|
+
* 当前生成任务标识。
|
|
2718
|
+
*/
|
|
2347
2719
|
__publicField(this, "taskID");
|
|
2348
|
-
/**
|
|
2720
|
+
/**
|
|
2721
|
+
* 状态变化原因
|
|
2722
|
+
*/
|
|
2723
|
+
/**
|
|
2724
|
+
* 进入当前状态的原因;正常开始新的操作时清空。
|
|
2725
|
+
*/
|
|
2349
2726
|
__publicField(this, "reason");
|
|
2350
2727
|
this.connectionState = init.connectionState;
|
|
2351
2728
|
this.sessionID = init.sessionID;
|
|
@@ -2371,15 +2748,23 @@ var RemoteStream = class {
|
|
|
2371
2748
|
* @param init.userID 远端流所属用户标识。
|
|
2372
2749
|
*/
|
|
2373
2750
|
constructor(init) {
|
|
2374
|
-
|
|
2375
|
-
|
|
2751
|
+
/**
|
|
2752
|
+
* 流标识
|
|
2753
|
+
*/
|
|
2754
|
+
/**
|
|
2755
|
+
* 远端流所在房间号。
|
|
2756
|
+
*/
|
|
2376
2757
|
__publicField(this, "roomID");
|
|
2377
|
-
/**
|
|
2758
|
+
/**
|
|
2759
|
+
* 远端流所属用户标识。
|
|
2760
|
+
*/
|
|
2378
2761
|
__publicField(this, "userID");
|
|
2379
2762
|
this.roomID = init.roomID;
|
|
2380
2763
|
this.userID = init.userID;
|
|
2381
2764
|
}
|
|
2382
|
-
/**
|
|
2765
|
+
/**
|
|
2766
|
+
* 跨房间唯一的远端流键。
|
|
2767
|
+
*/
|
|
2383
2768
|
get key() {
|
|
2384
2769
|
return `${this.roomID}:${this.userID}`;
|
|
2385
2770
|
}
|
|
@@ -2397,16 +2782,28 @@ var RoomJoinConfiguration = class {
|
|
|
2397
2782
|
* @param init.privateMapKey TRTC 进房鉴权字段。
|
|
2398
2783
|
*/
|
|
2399
2784
|
constructor(init) {
|
|
2400
|
-
|
|
2401
|
-
|
|
2785
|
+
/**
|
|
2786
|
+
* 进房参数
|
|
2787
|
+
*/
|
|
2788
|
+
/**
|
|
2789
|
+
* TRTC 房间号(字符串房间号)。
|
|
2790
|
+
*/
|
|
2402
2791
|
__publicField(this, "roomID");
|
|
2403
|
-
/**
|
|
2792
|
+
/**
|
|
2793
|
+
* TRTC 登录身份(`rtc_user_id`)。
|
|
2794
|
+
*/
|
|
2404
2795
|
__publicField(this, "userID");
|
|
2405
|
-
/**
|
|
2796
|
+
/**
|
|
2797
|
+
* TRTC SDKAppID(字符串形式,进房时转换为数值)。
|
|
2798
|
+
*/
|
|
2406
2799
|
__publicField(this, "sdkAppID");
|
|
2407
|
-
/**
|
|
2800
|
+
/**
|
|
2801
|
+
* TRTC 登录签名。
|
|
2802
|
+
*/
|
|
2408
2803
|
__publicField(this, "userSig");
|
|
2409
|
-
/**
|
|
2804
|
+
/**
|
|
2805
|
+
* TRTC 进房鉴权字段(字符串房间号对应的 privateMapKey)。
|
|
2806
|
+
*/
|
|
2410
2807
|
__publicField(this, "privateMapKey");
|
|
2411
2808
|
this.roomID = init.roomID;
|
|
2412
2809
|
this.userID = init.userID;
|
|
@@ -2418,7 +2815,9 @@ var RoomJoinConfiguration = class {
|
|
|
2418
2815
|
|
|
2419
2816
|
// src/Stream/Room/RoomEvent.ts
|
|
2420
2817
|
var RoomEvent = class _RoomEvent {
|
|
2421
|
-
/**
|
|
2818
|
+
/**
|
|
2819
|
+
* 生成开始信令。
|
|
2820
|
+
*/
|
|
2422
2821
|
static start(options) {
|
|
2423
2822
|
return _RoomEvent.encode({
|
|
2424
2823
|
event: "start",
|
|
@@ -2427,7 +2826,9 @@ var RoomEvent = class _RoomEvent {
|
|
|
2427
2826
|
uid: options.taskID
|
|
2428
2827
|
});
|
|
2429
2828
|
}
|
|
2430
|
-
/**
|
|
2829
|
+
/**
|
|
2830
|
+
* 生成条件变更信令。
|
|
2831
|
+
*/
|
|
2431
2832
|
static changeCondition(options) {
|
|
2432
2833
|
return _RoomEvent.encode({
|
|
2433
2834
|
event: "change_condition",
|
|
@@ -2436,7 +2837,9 @@ var RoomEvent = class _RoomEvent {
|
|
|
2436
2837
|
uid: options.taskID
|
|
2437
2838
|
});
|
|
2438
2839
|
}
|
|
2439
|
-
/**
|
|
2840
|
+
/**
|
|
2841
|
+
* 回传尺寸调整信令。
|
|
2842
|
+
*/
|
|
2440
2843
|
static changeTargetSize(options) {
|
|
2441
2844
|
return _RoomEvent.encode({
|
|
2442
2845
|
event: "change_target_size",
|
|
@@ -2450,7 +2853,9 @@ var RoomEvent = class _RoomEvent {
|
|
|
2450
2853
|
uid: options.taskID
|
|
2451
2854
|
});
|
|
2452
2855
|
}
|
|
2453
|
-
/**
|
|
2856
|
+
/**
|
|
2857
|
+
* 生成停止信令。
|
|
2858
|
+
*/
|
|
2454
2859
|
static stop(options) {
|
|
2455
2860
|
return _RoomEvent.encode({
|
|
2456
2861
|
event: "stop",
|
|
@@ -2458,7 +2863,9 @@ var RoomEvent = class _RoomEvent {
|
|
|
2458
2863
|
uid: options.taskID
|
|
2459
2864
|
});
|
|
2460
2865
|
}
|
|
2461
|
-
/**
|
|
2866
|
+
/**
|
|
2867
|
+
* 交互轨迹信令。
|
|
2868
|
+
*/
|
|
2462
2869
|
static tracks(options) {
|
|
2463
2870
|
return _RoomEvent.encode({
|
|
2464
2871
|
event: "tracks",
|
|
@@ -2467,14 +2874,18 @@ var RoomEvent = class _RoomEvent {
|
|
|
2467
2874
|
uid: options.taskID
|
|
2468
2875
|
});
|
|
2469
2876
|
}
|
|
2470
|
-
/**
|
|
2877
|
+
/**
|
|
2878
|
+
* 房间心跳信令。
|
|
2879
|
+
*/
|
|
2471
2880
|
static heartbeat(options) {
|
|
2472
2881
|
return _RoomEvent.encode({
|
|
2473
2882
|
event: "heartbeat",
|
|
2474
2883
|
user_id: options.userID
|
|
2475
2884
|
});
|
|
2476
2885
|
}
|
|
2477
|
-
/**
|
|
2886
|
+
/**
|
|
2887
|
+
* 生成条件参数:模型、生成尺寸、回传尺寸、文本与参考图路径。
|
|
2888
|
+
*/
|
|
2478
2889
|
static generationParameters(options) {
|
|
2479
2890
|
return {
|
|
2480
2891
|
model: "default",
|
|
@@ -2487,7 +2898,9 @@ var RoomEvent = class _RoomEvent {
|
|
|
2487
2898
|
ref_image_path: options.context.referencePath
|
|
2488
2899
|
};
|
|
2489
2900
|
}
|
|
2490
|
-
/**
|
|
2901
|
+
/**
|
|
2902
|
+
* 业务字段外加 `runtime` 运行环境信息并序列化为 JSON 文本。
|
|
2903
|
+
*/
|
|
2491
2904
|
static encode(payload) {
|
|
2492
2905
|
try {
|
|
2493
2906
|
return JSON.stringify({ ...payload, runtime: RuntimeInfo.toJSON() });
|
|
@@ -2510,27 +2923,39 @@ var RoomHeartbeat = class {
|
|
|
2510
2923
|
* @param options.sleep 心跳等待实现(可替换,测试用)。
|
|
2511
2924
|
*/
|
|
2512
2925
|
constructor(options) {
|
|
2513
|
-
|
|
2926
|
+
/**
|
|
2927
|
+
* 基础层组件
|
|
2928
|
+
*/
|
|
2514
2929
|
__publicField(this, "rtcManager");
|
|
2515
|
-
|
|
2930
|
+
/**
|
|
2931
|
+
* 心跳配置
|
|
2932
|
+
*/
|
|
2516
2933
|
__publicField(this, "intervalMs");
|
|
2517
2934
|
__publicField(this, "sleep");
|
|
2518
|
-
|
|
2935
|
+
/**
|
|
2936
|
+
* 运行状态
|
|
2937
|
+
*/
|
|
2519
2938
|
__publicField(this, "cycleVersion", 0);
|
|
2520
2939
|
this.rtcManager = options.rtcManager;
|
|
2521
2940
|
this.intervalMs = options.intervalMs ?? 1e4;
|
|
2522
2941
|
this.sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2523
2942
|
}
|
|
2524
|
-
/**
|
|
2943
|
+
/**
|
|
2944
|
+
* 启动周期心跳;重复启动会替换当前周期。
|
|
2945
|
+
*/
|
|
2525
2946
|
start(userID) {
|
|
2526
2947
|
const version = ++this.cycleVersion;
|
|
2527
2948
|
void this.run(version, userID);
|
|
2528
2949
|
}
|
|
2529
|
-
/**
|
|
2950
|
+
/**
|
|
2951
|
+
* 停止当前心跳周期;迟到结果不会再发送。
|
|
2952
|
+
*/
|
|
2530
2953
|
stop() {
|
|
2531
2954
|
this.cycleVersion += 1;
|
|
2532
2955
|
}
|
|
2533
|
-
/**
|
|
2956
|
+
/**
|
|
2957
|
+
* 周期循环:等待后发送心跳;发送失败仅记录日志,周期继续。
|
|
2958
|
+
*/
|
|
2534
2959
|
async run(version, userID) {
|
|
2535
2960
|
while (this.cycleVersion === version) {
|
|
2536
2961
|
await this.sleep(this.intervalMs);
|
|
@@ -2556,7 +2981,9 @@ var RoomHeartbeat = class {
|
|
|
2556
2981
|
var CHUNK_EVENT = "__trtc_chunk__";
|
|
2557
2982
|
var _RoomMessageCodec = class _RoomMessageCodec {
|
|
2558
2983
|
constructor() {
|
|
2559
|
-
|
|
2984
|
+
/**
|
|
2985
|
+
* 接收缓存
|
|
2986
|
+
*/
|
|
2560
2987
|
__publicField(this, "records", /* @__PURE__ */ new Map());
|
|
2561
2988
|
}
|
|
2562
2989
|
/**
|
|
@@ -2592,13 +3019,20 @@ var _RoomMessageCodec = class _RoomMessageCodec {
|
|
|
2592
3019
|
/**
|
|
2593
3020
|
* 处理入站消息。
|
|
2594
3021
|
*
|
|
2595
|
-
* @returns
|
|
2596
|
-
*
|
|
3022
|
+
* @returns 返回解析后的业务消息;分片未收齐或 JSON 非法时返回
|
|
3023
|
+
* `undefined`;分片收齐后从缓存移除并解析完整消息。
|
|
2597
3024
|
*/
|
|
2598
3025
|
processIncoming(senderUserID, message) {
|
|
2599
|
-
|
|
3026
|
+
let parsed;
|
|
3027
|
+
try {
|
|
3028
|
+
parsed = JSON.parse(message);
|
|
3029
|
+
} catch {
|
|
3030
|
+
XmaxLogger.room.warning(() => "\u623F\u95F4\u6D88\u606F\u4E0D\u662F\u5408\u6CD5 JSON (Room Message Is Not Valid JSON)");
|
|
3031
|
+
return void 0;
|
|
3032
|
+
}
|
|
3033
|
+
const chunk = _RoomMessageCodec.parseChunk(parsed);
|
|
2600
3034
|
if (!chunk) {
|
|
2601
|
-
return
|
|
3035
|
+
return parsed;
|
|
2602
3036
|
}
|
|
2603
3037
|
const key = `${senderUserID}:${chunk.eventId}`;
|
|
2604
3038
|
let record = this.records.get(key);
|
|
@@ -2622,7 +3056,7 @@ var _RoomMessageCodec = class _RoomMessageCodec {
|
|
|
2622
3056
|
}
|
|
2623
3057
|
const joined = parts.join("");
|
|
2624
3058
|
try {
|
|
2625
|
-
JSON.parse(joined);
|
|
3059
|
+
return JSON.parse(joined);
|
|
2626
3060
|
} catch {
|
|
2627
3061
|
XmaxLogger.room.warning(
|
|
2628
3062
|
() => `\u623F\u95F4\u6D88\u606F\u7EC4\u5305\u540E\u4E0D\u662F\u5408\u6CD5 JSON (Reassembled Message Is Not Valid JSON)
|
|
@@ -2630,20 +3064,17 @@ var _RoomMessageCodec = class _RoomMessageCodec {
|
|
|
2630
3064
|
);
|
|
2631
3065
|
return void 0;
|
|
2632
3066
|
}
|
|
2633
|
-
return joined;
|
|
2634
3067
|
}
|
|
2635
|
-
/**
|
|
3068
|
+
/**
|
|
3069
|
+
* 清空未完成的组包缓存。
|
|
3070
|
+
*/
|
|
2636
3071
|
reset() {
|
|
2637
3072
|
this.records.clear();
|
|
2638
3073
|
}
|
|
2639
|
-
/**
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
parsed = JSON.parse(message);
|
|
2644
|
-
} catch {
|
|
2645
|
-
return void 0;
|
|
2646
|
-
}
|
|
3074
|
+
/**
|
|
3075
|
+
* 从已解析的值识别分片消息;字段不完整时按普通业务消息处理。
|
|
3076
|
+
*/
|
|
3077
|
+
static parseChunk(parsed) {
|
|
2647
3078
|
if (typeof parsed !== "object" || parsed === null) {
|
|
2648
3079
|
return void 0;
|
|
2649
3080
|
}
|
|
@@ -2658,7 +3089,9 @@ var _RoomMessageCodec = class _RoomMessageCodec {
|
|
|
2658
3089
|
}
|
|
2659
3090
|
return { eventId: record.eventId, index, count, data: record.data };
|
|
2660
3091
|
}
|
|
2661
|
-
/**
|
|
3092
|
+
/**
|
|
3093
|
+
* 淘汰最旧的未完成记录,保持缓存不超过上限。
|
|
3094
|
+
*/
|
|
2662
3095
|
evictOverflow() {
|
|
2663
3096
|
while (this.records.size > _RoomMessageCodec.cacheLimit) {
|
|
2664
3097
|
const oldest = this.records.keys().next().value;
|
|
@@ -2668,7 +3101,9 @@ var _RoomMessageCodec = class _RoomMessageCodec {
|
|
|
2668
3101
|
this.records.delete(oldest);
|
|
2669
3102
|
}
|
|
2670
3103
|
}
|
|
2671
|
-
/**
|
|
3104
|
+
/**
|
|
3105
|
+
* 按 UTF-8 字节预算切分字符串,不拆分多字节字符。
|
|
3106
|
+
*/
|
|
2672
3107
|
static sliceByByteBudget(message, budget) {
|
|
2673
3108
|
const slices = [];
|
|
2674
3109
|
let current = "";
|
|
@@ -2688,11 +3123,15 @@ var _RoomMessageCodec = class _RoomMessageCodec {
|
|
|
2688
3123
|
}
|
|
2689
3124
|
return slices;
|
|
2690
3125
|
}
|
|
2691
|
-
/**
|
|
3126
|
+
/**
|
|
3127
|
+
* 计算字符串的 UTF-8 字节长度。
|
|
3128
|
+
*/
|
|
2692
3129
|
static byteLength(value) {
|
|
2693
3130
|
return new TextEncoder().encode(value).byteLength;
|
|
2694
3131
|
}
|
|
2695
|
-
/**
|
|
3132
|
+
/**
|
|
3133
|
+
* 生成拆包事件标识(32 位十六进制)。
|
|
3134
|
+
*/
|
|
2696
3135
|
static makeEventId() {
|
|
2697
3136
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
2698
3137
|
return crypto.randomUUID().replace(/-/g, "");
|
|
@@ -2703,12 +3142,20 @@ var _RoomMessageCodec = class _RoomMessageCodec {
|
|
|
2703
3142
|
).join("");
|
|
2704
3143
|
}
|
|
2705
3144
|
};
|
|
2706
|
-
|
|
2707
|
-
|
|
3145
|
+
/**
|
|
3146
|
+
* 协议常量
|
|
3147
|
+
*/
|
|
3148
|
+
/**
|
|
3149
|
+
* 触发拆包的消息字节阈值。
|
|
3150
|
+
*/
|
|
2708
3151
|
__publicField(_RoomMessageCodec, "chunkThresholdBytes", 800);
|
|
2709
|
-
/**
|
|
3152
|
+
/**
|
|
3153
|
+
* 单个分片正文的字节预算,保证分片 JSON 自身不超过阈值。
|
|
3154
|
+
*/
|
|
2710
3155
|
__publicField(_RoomMessageCodec, "chunkDataBudgetBytes", 600);
|
|
2711
|
-
/**
|
|
3156
|
+
/**
|
|
3157
|
+
* 未完成组包记录的缓存上限(LRU)。
|
|
3158
|
+
*/
|
|
2712
3159
|
__publicField(_RoomMessageCodec, "cacheLimit", 1e3);
|
|
2713
3160
|
var RoomMessageCodec = _RoomMessageCodec;
|
|
2714
3161
|
|
|
@@ -2722,14 +3169,22 @@ var RoomController = class {
|
|
|
2722
3169
|
* @param options.codec 房间消息编解码组件(可替换,测试用)。
|
|
2723
3170
|
*/
|
|
2724
3171
|
constructor(options) {
|
|
2725
|
-
|
|
3172
|
+
/**
|
|
3173
|
+
* 基础层组件
|
|
3174
|
+
*/
|
|
2726
3175
|
__publicField(this, "rtcManager");
|
|
2727
|
-
|
|
3176
|
+
/**
|
|
3177
|
+
* 传输层组件
|
|
3178
|
+
*/
|
|
2728
3179
|
__publicField(this, "heartbeat");
|
|
2729
3180
|
__publicField(this, "codec");
|
|
2730
|
-
|
|
3181
|
+
/**
|
|
3182
|
+
* 事件监听
|
|
3183
|
+
*/
|
|
2731
3184
|
__publicField(this, "listener");
|
|
2732
|
-
|
|
3185
|
+
/**
|
|
3186
|
+
* 房间资源
|
|
3187
|
+
*/
|
|
2733
3188
|
__publicField(this, "state", { kind: "idle" });
|
|
2734
3189
|
__publicField(this, "leaveOperation");
|
|
2735
3190
|
this.rtcManager = options.rtcManager;
|
|
@@ -2784,7 +3239,9 @@ var RoomController = class {
|
|
|
2784
3239
|
throw XmaxError.from(error);
|
|
2785
3240
|
}
|
|
2786
3241
|
}
|
|
2787
|
-
/**
|
|
3242
|
+
/**
|
|
3243
|
+
* 停止房间心跳并离开当前 RTC 房间;并发的离开操作共享同一个任务。
|
|
3244
|
+
*/
|
|
2788
3245
|
async leave() {
|
|
2789
3246
|
if (this.leaveOperation) {
|
|
2790
3247
|
await this.leaveOperation;
|
|
@@ -2844,14 +3301,18 @@ var RoomController = class {
|
|
|
2844
3301
|
})
|
|
2845
3302
|
);
|
|
2846
3303
|
}
|
|
2847
|
-
/**
|
|
3304
|
+
/**
|
|
3305
|
+
* 尝试发送生成停止信令;未进房或任务标识为空时忽略。
|
|
3306
|
+
*/
|
|
2848
3307
|
stopGeneration(taskID) {
|
|
2849
3308
|
if (!taskID || this.state.kind !== "joined") {
|
|
2850
3309
|
return;
|
|
2851
3310
|
}
|
|
2852
3311
|
this.send(RoomEvent.stop({ userID: this.state.userID, taskID }));
|
|
2853
3312
|
}
|
|
2854
|
-
/**
|
|
3313
|
+
/**
|
|
3314
|
+
* 发送生成任务的交互轨迹;任务标识或轨迹为空时忽略。
|
|
3315
|
+
*/
|
|
2855
3316
|
sendTracks(taskID, points) {
|
|
2856
3317
|
if (!taskID || points.length === 0) {
|
|
2857
3318
|
return;
|
|
@@ -2864,11 +3325,15 @@ var RoomController = class {
|
|
|
2864
3325
|
})
|
|
2865
3326
|
);
|
|
2866
3327
|
}
|
|
2867
|
-
/**
|
|
3328
|
+
/**
|
|
3329
|
+
* 设置房间事件监听器,传入空值时清除监听器。
|
|
3330
|
+
*/
|
|
2868
3331
|
setListener(listener) {
|
|
2869
3332
|
this.listener = listener;
|
|
2870
3333
|
}
|
|
2871
|
-
/**
|
|
3334
|
+
/**
|
|
3335
|
+
* 执行离开:复位状态、停止心跳、清空组包缓存并退房。
|
|
3336
|
+
*/
|
|
2872
3337
|
async performLeave() {
|
|
2873
3338
|
this.state = { kind: "idle" };
|
|
2874
3339
|
this.heartbeat.stop();
|
|
@@ -2876,19 +3341,11 @@ var RoomController = class {
|
|
|
2876
3341
|
await this.rtcManager.leaveRoom();
|
|
2877
3342
|
this.leaveOperation = void 0;
|
|
2878
3343
|
}
|
|
2879
|
-
/**
|
|
3344
|
+
/**
|
|
3345
|
+
* 组包入站消息并按目标用户过滤后分发完整业务消息。
|
|
3346
|
+
*/
|
|
2880
3347
|
handleIncomingMessage(senderUserID, raw) {
|
|
2881
|
-
const
|
|
2882
|
-
if (complete === void 0) {
|
|
2883
|
-
return;
|
|
2884
|
-
}
|
|
2885
|
-
let parsed;
|
|
2886
|
-
try {
|
|
2887
|
-
parsed = JSON.parse(complete);
|
|
2888
|
-
} catch {
|
|
2889
|
-
XmaxLogger.room.warning(() => "\u623F\u95F4\u6D88\u606F\u4E0D\u662F\u5408\u6CD5 JSON (Room Message Is Not Valid JSON)");
|
|
2890
|
-
return;
|
|
2891
|
-
}
|
|
3348
|
+
const parsed = this.codec.processIncoming(senderUserID, raw);
|
|
2892
3349
|
if (typeof parsed !== "object" || parsed === null) {
|
|
2893
3350
|
return;
|
|
2894
3351
|
}
|
|
@@ -2900,7 +3357,9 @@ var RoomController = class {
|
|
|
2900
3357
|
}
|
|
2901
3358
|
this.listener?.onRoomMessage(senderUserID, message);
|
|
2902
3359
|
}
|
|
2903
|
-
/**
|
|
3360
|
+
/**
|
|
3361
|
+
* 发送房间信令并输出日志。
|
|
3362
|
+
*/
|
|
2904
3363
|
send(message) {
|
|
2905
3364
|
for (const packet of this.codec.encodeOutgoing(message)) {
|
|
2906
3365
|
try {
|
|
@@ -2915,7 +3374,9 @@ var RoomController = class {
|
|
|
2915
3374
|
${JSON.stringify(JSON.parse(message), null, 2)}`
|
|
2916
3375
|
);
|
|
2917
3376
|
}
|
|
2918
|
-
/**
|
|
3377
|
+
/**
|
|
3378
|
+
* 返回当前房间中的用户标识;未进房时抛出错误。
|
|
3379
|
+
*/
|
|
2919
3380
|
requireUserID() {
|
|
2920
3381
|
if (this.state.kind !== "joined") {
|
|
2921
3382
|
throw new XmaxError("RTC_ERROR" /* rtcError */, "RTC room is not joined");
|
|
@@ -2936,10 +3397,15 @@ var _EncodingController = class _EncodingController {
|
|
|
2936
3397
|
* @param options.rtcManager RTC 引擎与媒体传输组件。
|
|
2937
3398
|
*/
|
|
2938
3399
|
constructor(options) {
|
|
2939
|
-
|
|
3400
|
+
/**
|
|
3401
|
+
* 基础层组件
|
|
3402
|
+
*/
|
|
2940
3403
|
__publicField(this, "rtcManager");
|
|
2941
3404
|
this.rtcManager = options.rtcManager;
|
|
2942
3405
|
}
|
|
3406
|
+
/**
|
|
3407
|
+
* 校验视频格式,解析显式或推荐码率范围后配置 RTC 编码器;参数或配置失败时抛错。
|
|
3408
|
+
*/
|
|
2943
3409
|
async configure(videoFormat) {
|
|
2944
3410
|
videoFormat.validate();
|
|
2945
3411
|
let minimum;
|
|
@@ -2959,8 +3425,8 @@ var _EncodingController = class _EncodingController {
|
|
|
2959
3425
|
"Realtime video format exceeds the supported bitrate range"
|
|
2960
3426
|
);
|
|
2961
3427
|
}
|
|
2962
|
-
const defaultMinimum = Math.max(1, Math.
|
|
2963
|
-
const defaultMaximum = Math.max(defaultMinimum + 1,
|
|
3428
|
+
const defaultMinimum = Math.max(1, Math.round(bitrates.minimum));
|
|
3429
|
+
const defaultMaximum = Math.max(defaultMinimum + 1, maximumBitrate);
|
|
2964
3430
|
minimum = videoFormat.minimumBitrate ?? defaultMinimum;
|
|
2965
3431
|
maximum = videoFormat.maximumBitrate ?? defaultMaximum;
|
|
2966
3432
|
}
|
|
@@ -2979,7 +3445,9 @@ var _EncodingController = class _EncodingController {
|
|
|
2979
3445
|
encoderPreference: ENCODER_PREFERENCE_MAP[videoFormat.encoderPreference]
|
|
2980
3446
|
});
|
|
2981
3447
|
}
|
|
2982
|
-
/**
|
|
3448
|
+
/**
|
|
3449
|
+
* 按像素面积与帧率计算码率区间(kbps):流畅优先为最低,画质优先为最高。
|
|
3450
|
+
*/
|
|
2983
3451
|
static resolveBitrates(pixels, fps) {
|
|
2984
3452
|
const bitrate15 = _EncodingController.interpolate(
|
|
2985
3453
|
pixels,
|
|
@@ -3017,7 +3485,9 @@ var _EncodingController = class _EncodingController {
|
|
|
3017
3485
|
])
|
|
3018
3486
|
};
|
|
3019
3487
|
}
|
|
3020
|
-
/**
|
|
3488
|
+
/**
|
|
3489
|
+
* 在相邻参考点间线性插值,表外按最近端点的比例外推。
|
|
3490
|
+
*/
|
|
3021
3491
|
static interpolate(value, points) {
|
|
3022
3492
|
const first = points[0];
|
|
3023
3493
|
if (value <= first.value) {
|
|
@@ -3035,7 +3505,9 @@ var _EncodingController = class _EncodingController {
|
|
|
3035
3505
|
return last.bitrate * (value / last.value);
|
|
3036
3506
|
}
|
|
3037
3507
|
};
|
|
3038
|
-
|
|
3508
|
+
/**
|
|
3509
|
+
* 流畅优先码率参考,按像素面积升序排列,单位为 kbps。
|
|
3510
|
+
*/
|
|
3039
3511
|
__publicField(_EncodingController, "referenceBitratesAt15FPS", [
|
|
3040
3512
|
{ value: 120 * 120, bitrate: 50 },
|
|
3041
3513
|
{ value: 160 * 120, bitrate: 65 },
|
|
@@ -3088,22 +3560,34 @@ var StreamController = class {
|
|
|
3088
3560
|
* @param options.generationTimeoutMs 生成开始确认超时时间(毫秒);默认 30 秒。
|
|
3089
3561
|
*/
|
|
3090
3562
|
constructor(options) {
|
|
3091
|
-
|
|
3563
|
+
/**
|
|
3564
|
+
* 基础层组件
|
|
3565
|
+
*/
|
|
3092
3566
|
__publicField(this, "rtcManager");
|
|
3093
|
-
|
|
3567
|
+
/**
|
|
3568
|
+
* 传输层组件
|
|
3569
|
+
*/
|
|
3094
3570
|
__publicField(this, "roomController");
|
|
3095
3571
|
__publicField(this, "encodingController");
|
|
3096
|
-
|
|
3572
|
+
/**
|
|
3573
|
+
* 事件监听
|
|
3574
|
+
*/
|
|
3097
3575
|
__publicField(this, "errorListener");
|
|
3098
3576
|
__publicField(this, "remoteStreamListener");
|
|
3099
3577
|
__publicField(this, "localVideoStatisticsListener");
|
|
3100
3578
|
__publicField(this, "networkStatisticsListener");
|
|
3101
3579
|
__publicField(this, "remoteVideoStatisticsListener");
|
|
3102
|
-
|
|
3580
|
+
/**
|
|
3581
|
+
* 生成配置
|
|
3582
|
+
*/
|
|
3103
3583
|
__publicField(this, "generationTimeoutMs");
|
|
3104
|
-
|
|
3584
|
+
/**
|
|
3585
|
+
* 音频配置
|
|
3586
|
+
*/
|
|
3105
3587
|
__publicField(this, "remoteAudioVolumePercentage", 100);
|
|
3106
|
-
|
|
3588
|
+
/**
|
|
3589
|
+
* 运行状态
|
|
3590
|
+
*/
|
|
3107
3591
|
__publicField(this, "state", makeInitialState());
|
|
3108
3592
|
this.rtcManager = options.rtcManager;
|
|
3109
3593
|
this.roomController = options.roomController ?? new RoomController({ rtcManager: options.rtcManager });
|
|
@@ -3138,27 +3622,39 @@ var StreamController = class {
|
|
|
3138
3622
|
}
|
|
3139
3623
|
});
|
|
3140
3624
|
}
|
|
3141
|
-
/**
|
|
3625
|
+
/**
|
|
3626
|
+
* 当前是否存在正在启动或已经运行的生成任务。
|
|
3627
|
+
*/
|
|
3142
3628
|
get hasGenerationTask() {
|
|
3143
3629
|
return this.state.generationTaskID !== void 0;
|
|
3144
3630
|
}
|
|
3145
|
-
/**
|
|
3631
|
+
/**
|
|
3632
|
+
* 设置本地主视频流统计监听器,不改变 RTC 事件监听权。
|
|
3633
|
+
*/
|
|
3146
3634
|
setLocalVideoStatisticsListener(listener) {
|
|
3147
3635
|
this.localVideoStatisticsListener = listener;
|
|
3148
3636
|
}
|
|
3149
|
-
/**
|
|
3637
|
+
/**
|
|
3638
|
+
* 网络统计不按远端用户过滤,连接发布完成后即可回调。
|
|
3639
|
+
*/
|
|
3150
3640
|
setNetworkStatisticsListener(listener) {
|
|
3151
3641
|
this.networkStatisticsListener = listener;
|
|
3152
3642
|
}
|
|
3153
|
-
/**
|
|
3643
|
+
/**
|
|
3644
|
+
* 监听当前实际生成结果流的统计,不按会话下发的 botID 猜测结果流身份。
|
|
3645
|
+
*/
|
|
3154
3646
|
setRemoteVideoStatisticsListener(listener) {
|
|
3155
3647
|
this.remoteVideoStatisticsListener = listener;
|
|
3156
3648
|
}
|
|
3157
|
-
/**
|
|
3649
|
+
/**
|
|
3650
|
+
* 当前远端生成音频播放音量,取值范围为 `0...1`。
|
|
3651
|
+
*/
|
|
3158
3652
|
get remoteAudioVolume() {
|
|
3159
3653
|
return this.remoteAudioVolumePercentage / 100;
|
|
3160
3654
|
}
|
|
3161
|
-
/**
|
|
3655
|
+
/**
|
|
3656
|
+
* 设置远端生成音频播放音量,并应用到所有已订阅的远端音频。
|
|
3657
|
+
*/
|
|
3162
3658
|
setRemoteAudioVolume(volume) {
|
|
3163
3659
|
const rtcVolume = Math.min(Math.max(Math.round(volume * 100), 0), 100);
|
|
3164
3660
|
for (const userID of [...this.state.subscribedRemoteAudioUserIDs].sort()) {
|
|
@@ -3191,7 +3687,9 @@ var StreamController = class {
|
|
|
3191
3687
|
ensureActive();
|
|
3192
3688
|
await this.publishLocalStream(includeLocalAudio);
|
|
3193
3689
|
}
|
|
3194
|
-
/**
|
|
3690
|
+
/**
|
|
3691
|
+
* 清理生成状态、本地发布和远端订阅,并离开当前 RTC 房间。
|
|
3692
|
+
*/
|
|
3195
3693
|
async disconnect() {
|
|
3196
3694
|
await this.resetStream();
|
|
3197
3695
|
await this.roomController.leave();
|
|
@@ -3334,11 +3832,15 @@ var StreamController = class {
|
|
|
3334
3832
|
throw XmaxError.from(error);
|
|
3335
3833
|
}
|
|
3336
3834
|
}
|
|
3337
|
-
/**
|
|
3835
|
+
/**
|
|
3836
|
+
* 设置房间业务消息监听器,传入空值时清除监听器。
|
|
3837
|
+
*/
|
|
3338
3838
|
setRoomListener(listener) {
|
|
3339
3839
|
this.roomController.setListener(listener);
|
|
3340
3840
|
}
|
|
3341
|
-
/**
|
|
3841
|
+
/**
|
|
3842
|
+
* 配置当前房间;已有发布、订阅或生成任务时拒绝重新配置。
|
|
3843
|
+
*/
|
|
3342
3844
|
configureRoom(options) {
|
|
3343
3845
|
const roomID = options.roomID.trim();
|
|
3344
3846
|
const botID = options.botID?.trim() ?? "";
|
|
@@ -3357,7 +3859,9 @@ var StreamController = class {
|
|
|
3357
3859
|
this.state.roomID = roomID;
|
|
3358
3860
|
this.state.botID = botID;
|
|
3359
3861
|
}
|
|
3360
|
-
/**
|
|
3862
|
+
/**
|
|
3863
|
+
* 发布本地视频,按需发布本地音频;音频发布失败时回滚视频发布。
|
|
3864
|
+
*/
|
|
3361
3865
|
async publishLocalStream(includeAudio) {
|
|
3362
3866
|
if (!this.state.roomID) {
|
|
3363
3867
|
throw new XmaxError(
|
|
@@ -3383,7 +3887,9 @@ var StreamController = class {
|
|
|
3383
3887
|
throw XmaxError.from(error);
|
|
3384
3888
|
}
|
|
3385
3889
|
}
|
|
3386
|
-
/**
|
|
3890
|
+
/**
|
|
3891
|
+
* 处理远端视频发布状态变化:按机器人标识过滤后订阅或清理。
|
|
3892
|
+
*/
|
|
3387
3893
|
async handleRemoteVideoPublished(userID, published) {
|
|
3388
3894
|
const trimmedUserID = userID.trim();
|
|
3389
3895
|
if (!trimmedUserID) {
|
|
@@ -3420,7 +3926,9 @@ var StreamController = class {
|
|
|
3420
3926
|
this.clearRemoteStream();
|
|
3421
3927
|
}
|
|
3422
3928
|
}
|
|
3423
|
-
/**
|
|
3929
|
+
/**
|
|
3930
|
+
* 订阅远端视频并在成功后就绪或确认生成任务。
|
|
3931
|
+
*/
|
|
3424
3932
|
async subscribeRemoteVideo(userID) {
|
|
3425
3933
|
if (this.state.subscribedRemoteUserIDs.has(userID)) {
|
|
3426
3934
|
return;
|
|
@@ -3452,7 +3960,9 @@ var StreamController = class {
|
|
|
3452
3960
|
}
|
|
3453
3961
|
}
|
|
3454
3962
|
}
|
|
3455
|
-
/**
|
|
3963
|
+
/**
|
|
3964
|
+
* 订阅成功后就绪远端流:有待确认的生成任务时确认它,否则更新当前绑定。
|
|
3965
|
+
*/
|
|
3456
3966
|
confirmOrUpdateRemoteStream(userID, track) {
|
|
3457
3967
|
const waiter = this.state.generationWaiter;
|
|
3458
3968
|
const taskID = this.state.generationTaskID;
|
|
@@ -3485,7 +3995,9 @@ var StreamController = class {
|
|
|
3485
3995
|
}
|
|
3486
3996
|
}
|
|
3487
3997
|
}
|
|
3488
|
-
/**
|
|
3998
|
+
/**
|
|
3999
|
+
* 订阅远端音频:先应用当前音量再订阅。
|
|
4000
|
+
*/
|
|
3489
4001
|
async subscribeRemoteAudio(userID) {
|
|
3490
4002
|
if (this.state.subscribedRemoteAudioUserIDs.has(userID)) {
|
|
3491
4003
|
return;
|
|
@@ -3494,7 +4006,9 @@ var StreamController = class {
|
|
|
3494
4006
|
await this.rtcManager.subscribeRemoteAudio(userID, true);
|
|
3495
4007
|
this.state.subscribedRemoteAudioUserIDs.add(userID);
|
|
3496
4008
|
}
|
|
3497
|
-
/**
|
|
4009
|
+
/**
|
|
4010
|
+
* 取消订阅远端音频;失败仅记录日志。
|
|
4011
|
+
*/
|
|
3498
4012
|
async unsubscribeRemoteAudio(userID) {
|
|
3499
4013
|
if (!this.state.subscribedRemoteAudioUserIDs.delete(userID)) {
|
|
3500
4014
|
return;
|
|
@@ -3533,7 +4047,9 @@ var StreamController = class {
|
|
|
3533
4047
|
this.clearRemoteStream();
|
|
3534
4048
|
return currentTaskID;
|
|
3535
4049
|
}
|
|
3536
|
-
/**
|
|
4050
|
+
/**
|
|
4051
|
+
* 复位传输层:停止生成、取消全部远端订阅、取消本地发布。
|
|
4052
|
+
*/
|
|
3537
4053
|
async resetStream() {
|
|
3538
4054
|
await this.stopStreamGeneration("");
|
|
3539
4055
|
const previousState = this.state;
|
|
@@ -3559,7 +4075,9 @@ var StreamController = class {
|
|
|
3559
4075
|
);
|
|
3560
4076
|
}
|
|
3561
4077
|
}
|
|
3562
|
-
/**
|
|
4078
|
+
/**
|
|
4079
|
+
* 确认生成开始:清理等待器并兑现确认 Promise。
|
|
4080
|
+
*/
|
|
3563
4081
|
resolveGenerationStart(taskID) {
|
|
3564
4082
|
const waiter = this.state.generationWaiter;
|
|
3565
4083
|
if (this.state.generationTaskID !== taskID || waiter?.taskID !== taskID) {
|
|
@@ -3569,7 +4087,9 @@ var StreamController = class {
|
|
|
3569
4087
|
clearTimeout(waiter.timeoutTimer);
|
|
3570
4088
|
waiter.resolve();
|
|
3571
4089
|
}
|
|
3572
|
-
/**
|
|
4090
|
+
/**
|
|
4091
|
+
* 拒绝生成开始:清理等待器并拒绝确认 Promise。
|
|
4092
|
+
*/
|
|
3573
4093
|
rejectGenerationStart(taskID, error) {
|
|
3574
4094
|
const waiter = this.state.generationWaiter;
|
|
3575
4095
|
if (this.state.generationTaskID !== taskID || waiter?.taskID !== taskID) {
|
|
@@ -3579,7 +4099,9 @@ var StreamController = class {
|
|
|
3579
4099
|
clearTimeout(waiter.timeoutTimer);
|
|
3580
4100
|
waiter.reject(error);
|
|
3581
4101
|
}
|
|
3582
|
-
/**
|
|
4102
|
+
/**
|
|
4103
|
+
* 通知渲染层清理远端生成流;失败仅记录日志。
|
|
4104
|
+
*/
|
|
3583
4105
|
clearRemoteStream() {
|
|
3584
4106
|
this.remoteVideoStatisticsListener?.(void 0);
|
|
3585
4107
|
try {
|
|
@@ -3591,7 +4113,9 @@ var StreamController = class {
|
|
|
3591
4113
|
);
|
|
3592
4114
|
}
|
|
3593
4115
|
}
|
|
3594
|
-
/**
|
|
4116
|
+
/**
|
|
4117
|
+
* 回滚本地视频发布;失败仅记录日志。
|
|
4118
|
+
*/
|
|
3595
4119
|
async rollbackLocalVideoPublication() {
|
|
3596
4120
|
await this.performCleanup(
|
|
3597
4121
|
"\u56DE\u6EDA RTC \u672C\u5730\u89C6\u9891\u53D1\u5E03\u5931\u8D25 (Failed to Roll Back RTC Local Video Publication)",
|
|
@@ -3601,7 +4125,9 @@ var StreamController = class {
|
|
|
3601
4125
|
}
|
|
3602
4126
|
);
|
|
3603
4127
|
}
|
|
3604
|
-
/**
|
|
4128
|
+
/**
|
|
4129
|
+
* 执行清理动作;失败仅记录日志。
|
|
4130
|
+
*/
|
|
3605
4131
|
async performCleanup(title, action) {
|
|
3606
4132
|
try {
|
|
3607
4133
|
await action();
|
|
@@ -3622,31 +4148,52 @@ function scopeAffects(scope, kind) {
|
|
|
3622
4148
|
return kind !== "media" /* media */;
|
|
3623
4149
|
}
|
|
3624
4150
|
var OperationLease = class {
|
|
4151
|
+
/**
|
|
4152
|
+
* 创建操作租约;未指定失败范围时不自动扩大资源清理范围。
|
|
4153
|
+
*/
|
|
3625
4154
|
constructor(failureScope) {
|
|
3626
|
-
|
|
4155
|
+
/**
|
|
4156
|
+
* 并发状态
|
|
4157
|
+
*/
|
|
3627
4158
|
__publicField(this, "valid", true);
|
|
3628
4159
|
__publicField(this, "terminalError");
|
|
3629
4160
|
__publicField(this, "storedFailureScope");
|
|
3630
4161
|
__publicField(this, "abortController", new AbortController());
|
|
3631
4162
|
this.storedFailureScope = failureScope;
|
|
3632
4163
|
}
|
|
4164
|
+
/**
|
|
4165
|
+
* 租约是否仍然有效,可用于异步结果提交前的快速判断。
|
|
4166
|
+
*/
|
|
3633
4167
|
get isCurrent() {
|
|
3634
4168
|
return this.valid;
|
|
3635
4169
|
}
|
|
4170
|
+
/**
|
|
4171
|
+
* 首次使租约失效的错误;主动取消且未指定原因时为空。
|
|
4172
|
+
*/
|
|
3636
4173
|
get failure() {
|
|
3637
4174
|
return this.terminalError;
|
|
3638
4175
|
}
|
|
4176
|
+
/**
|
|
4177
|
+
* 当前操作失败或取消时需要清理的资源范围。
|
|
4178
|
+
*/
|
|
3639
4179
|
get failureScope() {
|
|
3640
4180
|
return this.storedFailureScope;
|
|
3641
4181
|
}
|
|
4182
|
+
/**
|
|
4183
|
+
* 租约失效时同步触发的取消信号。
|
|
4184
|
+
*/
|
|
3642
4185
|
get signal() {
|
|
3643
4186
|
return this.abortController.signal;
|
|
3644
4187
|
}
|
|
3645
|
-
/**
|
|
4188
|
+
/**
|
|
4189
|
+
* 更新当前操作失败或被调用方取消时需要释放的资源范围。
|
|
4190
|
+
*/
|
|
3646
4191
|
setFailureScope(scope) {
|
|
3647
4192
|
this.storedFailureScope = scope;
|
|
3648
4193
|
}
|
|
3649
|
-
/**
|
|
4194
|
+
/**
|
|
4195
|
+
* 作废租约并触发中止信号;首个终止原因被保留。
|
|
4196
|
+
*/
|
|
3650
4197
|
invalidate(error) {
|
|
3651
4198
|
this.valid = false;
|
|
3652
4199
|
if (!this.terminalError) {
|
|
@@ -3654,7 +4201,9 @@ var OperationLease = class {
|
|
|
3654
4201
|
}
|
|
3655
4202
|
this.abortController.abort();
|
|
3656
4203
|
}
|
|
3657
|
-
/**
|
|
4204
|
+
/**
|
|
4205
|
+
* 在异步边界后校验当前操作仍拥有实时生命周期。
|
|
4206
|
+
*/
|
|
3658
4207
|
ensureCurrent() {
|
|
3659
4208
|
if (!this.valid) {
|
|
3660
4209
|
throw this.terminalError ?? RealtimeCoordinator.cancelledError();
|
|
@@ -3663,22 +4212,33 @@ var OperationLease = class {
|
|
|
3663
4212
|
};
|
|
3664
4213
|
var operationSequence = 0;
|
|
3665
4214
|
var RealtimeCoordinator = class _RealtimeCoordinator {
|
|
4215
|
+
/**
|
|
4216
|
+
* 注入错误分发与资源清理逻辑,初始状态为 idle。
|
|
4217
|
+
*/
|
|
3666
4218
|
constructor(options) {
|
|
3667
|
-
|
|
4219
|
+
/**
|
|
4220
|
+
* 业务组件
|
|
4221
|
+
*/
|
|
3668
4222
|
__publicField(this, "errorHandler");
|
|
3669
4223
|
__publicField(this, "cleanup");
|
|
3670
|
-
|
|
4224
|
+
/**
|
|
4225
|
+
* 状态管理
|
|
4226
|
+
*/
|
|
3671
4227
|
__publicField(this, "state", new RealtimeState({
|
|
3672
4228
|
connectionState: "Idle" /* idle */
|
|
3673
4229
|
}));
|
|
3674
4230
|
__publicField(this, "stateListener");
|
|
3675
|
-
|
|
4231
|
+
/**
|
|
4232
|
+
* 操作管理
|
|
4233
|
+
*/
|
|
3676
4234
|
__publicField(this, "activeOperation");
|
|
3677
4235
|
__publicField(this, "termination");
|
|
3678
4236
|
this.errorHandler = options.errorHandler;
|
|
3679
4237
|
this.cleanup = options.cleanup;
|
|
3680
4238
|
}
|
|
3681
|
-
/**
|
|
4239
|
+
/**
|
|
4240
|
+
* 当前实时状态快照。
|
|
4241
|
+
*/
|
|
3682
4242
|
get currentState() {
|
|
3683
4243
|
return this.state;
|
|
3684
4244
|
}
|
|
@@ -3734,12 +4294,16 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
|
|
|
3734
4294
|
return this.handleFailure(error, operation);
|
|
3735
4295
|
}
|
|
3736
4296
|
}
|
|
3737
|
-
/**
|
|
4297
|
+
/**
|
|
4298
|
+
* 仅当前操作可以提交新的公开状态。
|
|
4299
|
+
*/
|
|
3738
4300
|
async commit(nextState, token) {
|
|
3739
4301
|
token.ensureCurrent();
|
|
3740
4302
|
await this.setState(nextState);
|
|
3741
4303
|
}
|
|
3742
|
-
/**
|
|
4304
|
+
/**
|
|
4305
|
+
* 当前本地预览满足就绪条件后,结束媒体准备状态。
|
|
4306
|
+
*/
|
|
3743
4307
|
async localPreviewDidBecomeReady(isCurrent) {
|
|
3744
4308
|
if (this.state.connectionState !== "Preparing" /* preparing */ || !isCurrent()) {
|
|
3745
4309
|
return;
|
|
@@ -3748,12 +4312,16 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
|
|
|
3748
4312
|
new RealtimeState({ connectionState: "Ready" /* ready */ })
|
|
3749
4313
|
);
|
|
3750
4314
|
}
|
|
3751
|
-
/**
|
|
4315
|
+
/**
|
|
4316
|
+
* 断开实时连接;尚未提交连接状态的活跃操作也会被取消。
|
|
4317
|
+
*/
|
|
3752
4318
|
async disconnect(reason = RealtimeReason.normal) {
|
|
3753
4319
|
const task = await this.beginDisconnect(reason);
|
|
3754
4320
|
await task;
|
|
3755
4321
|
}
|
|
3756
|
-
/**
|
|
4322
|
+
/**
|
|
4323
|
+
* 发起断开并返回收尾任务,使本地媒体调整不必等待网络资源释放。
|
|
4324
|
+
*/
|
|
3757
4325
|
async beginDisconnect(reason) {
|
|
3758
4326
|
const hasConnectionOperation = this.activeOperation ? scopeAffects(0 /* connection */, this.activeOperation.kind) : false;
|
|
3759
4327
|
const state2 = this.state.connectionState;
|
|
@@ -3767,12 +4335,16 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
|
|
|
3767
4335
|
reason
|
|
3768
4336
|
);
|
|
3769
4337
|
}
|
|
3770
|
-
/**
|
|
4338
|
+
/**
|
|
4339
|
+
* 终止指定范围并等待资源清理完成;并发终止请求会合并为最大范围。
|
|
4340
|
+
*/
|
|
3771
4341
|
async terminate(target, reason = RealtimeReason.normal) {
|
|
3772
4342
|
const task = await this.requestTermination(target, void 0, reason);
|
|
3773
4343
|
await task;
|
|
3774
4344
|
}
|
|
3775
|
-
/**
|
|
4345
|
+
/**
|
|
4346
|
+
* 清理仍属于当前生命周期的后台故障,并通过最终状态提供原因。
|
|
4347
|
+
*/
|
|
3776
4348
|
async terminateWithError(error, target, isCurrent = () => true) {
|
|
3777
4349
|
if (!isCurrent()) {
|
|
3778
4350
|
return;
|
|
@@ -3787,7 +4359,9 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
|
|
|
3787
4359
|
);
|
|
3788
4360
|
await task;
|
|
3789
4361
|
}
|
|
3790
|
-
/**
|
|
4362
|
+
/**
|
|
4363
|
+
* 操作失败处理:取消走终止流程,错误按失败范围清理或直接上报。
|
|
4364
|
+
*/
|
|
3791
4365
|
async handleFailure(error, operation) {
|
|
3792
4366
|
const operationWasCancelled = error instanceof XmaxError && error.code === "CANCELLED" /* cancelled */ || error instanceof DOMException && error.name === "AbortError" || !operation.lease.isCurrent;
|
|
3793
4367
|
if (operationWasCancelled) {
|
|
@@ -3832,7 +4406,9 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
|
|
|
3832
4406
|
this.finish(operation);
|
|
3833
4407
|
throw resolvedError;
|
|
3834
4408
|
}
|
|
3835
|
-
/**
|
|
4409
|
+
/**
|
|
4410
|
+
* 发起或合并终止请求,作废旧操作并进入断开中状态。
|
|
4411
|
+
*/
|
|
3836
4412
|
async requestTermination(target, error, reason, origin) {
|
|
3837
4413
|
let pending = this.termination;
|
|
3838
4414
|
if (pending) {
|
|
@@ -3875,7 +4451,9 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
|
|
|
3875
4451
|
);
|
|
3876
4452
|
return pending.task;
|
|
3877
4453
|
}
|
|
3878
|
-
/**
|
|
4454
|
+
/**
|
|
4455
|
+
* 执行终止:等待旧操作收尾,分级清理资源,提交最终状态。
|
|
4456
|
+
*/
|
|
3879
4457
|
async performTermination(id) {
|
|
3880
4458
|
const pending = this.termination;
|
|
3881
4459
|
if (!pending || pending.id !== id) {
|
|
@@ -3921,13 +4499,17 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
|
|
|
3921
4499
|
return;
|
|
3922
4500
|
}
|
|
3923
4501
|
}
|
|
3924
|
-
/**
|
|
4502
|
+
/**
|
|
4503
|
+
* 结束指定操作,释放操作准入场。
|
|
4504
|
+
*/
|
|
3925
4505
|
finish(operation) {
|
|
3926
4506
|
if (this.activeOperation?.id === operation.id) {
|
|
3927
4507
|
this.activeOperation = void 0;
|
|
3928
4508
|
}
|
|
3929
4509
|
}
|
|
3930
|
-
/**
|
|
4510
|
+
/**
|
|
4511
|
+
* 提交新状态并通知监听器;状态未变化时不通知。
|
|
4512
|
+
*/
|
|
3931
4513
|
async setState(nextState) {
|
|
3932
4514
|
if (this.state.equals(nextState)) {
|
|
3933
4515
|
return;
|
|
@@ -3935,7 +4517,9 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
|
|
|
3935
4517
|
this.state = nextState;
|
|
3936
4518
|
this.stateListener?.(nextState);
|
|
3937
4519
|
}
|
|
3938
|
-
/**
|
|
4520
|
+
/**
|
|
4521
|
+
* 构造统一的操作取消错误。
|
|
4522
|
+
*/
|
|
3939
4523
|
static cancelledError() {
|
|
3940
4524
|
return new XmaxError(
|
|
3941
4525
|
"CANCELLED" /* cancelled */,
|
|
@@ -3947,10 +4531,14 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
|
|
|
3947
4531
|
// src/Core/Realtime/RealtimeErrorHandler.ts
|
|
3948
4532
|
var RealtimeErrorHandler = class {
|
|
3949
4533
|
constructor() {
|
|
3950
|
-
|
|
4534
|
+
/**
|
|
4535
|
+
* 运行状态
|
|
4536
|
+
*/
|
|
3951
4537
|
__publicField(this, "pendingFailures", []);
|
|
3952
4538
|
}
|
|
3953
|
-
/**
|
|
4539
|
+
/**
|
|
4540
|
+
* 上报错误;当前通过日志输出,后续里程碑接入状态原因上报。
|
|
4541
|
+
*/
|
|
3954
4542
|
async report(error) {
|
|
3955
4543
|
if (error.code === "CANCELLED" /* cancelled */) {
|
|
3956
4544
|
return;
|
|
@@ -3961,7 +4549,9 @@ var RealtimeErrorHandler = class {
|
|
|
3961
4549
|
\u2514\u2500 ${XmaxLogger.localized("\u539F\u56E0\uFF1A", "Reason: ")}${error.code} ${error.message}`
|
|
3962
4550
|
);
|
|
3963
4551
|
}
|
|
3964
|
-
/**
|
|
4552
|
+
/**
|
|
4553
|
+
* 清理仍属于已终止生命周期的后台故障。
|
|
4554
|
+
*/
|
|
3965
4555
|
invalidatePendingFailures(_target) {
|
|
3966
4556
|
this.pendingFailures = [];
|
|
3967
4557
|
}
|
|
@@ -3970,16 +4560,28 @@ var RealtimeErrorHandler = class {
|
|
|
3970
4560
|
// src/Core/Realtime/RealtimeLaunchTimer.ts
|
|
3971
4561
|
var RealtimeLaunchTimer = class {
|
|
3972
4562
|
constructor() {
|
|
4563
|
+
/**
|
|
4564
|
+
* 统计快照与观察者
|
|
4565
|
+
*/
|
|
3973
4566
|
__publicField(this, "snapshot", Object.freeze({}));
|
|
3974
4567
|
__publicField(this, "listener");
|
|
4568
|
+
/**
|
|
4569
|
+
* 当前启动周期与计时起点
|
|
4570
|
+
*/
|
|
3975
4571
|
__publicField(this, "sequence", 0);
|
|
3976
4572
|
__publicField(this, "active", false);
|
|
3977
4573
|
__publicField(this, "startedAt", 0);
|
|
3978
4574
|
}
|
|
4575
|
+
/**
|
|
4576
|
+
* 设置统计监听器并立即回放当前快照;传 undefined 取消监听。
|
|
4577
|
+
*/
|
|
3979
4578
|
setListener(listener) {
|
|
3980
4579
|
this.listener = listener;
|
|
3981
4580
|
this.notify();
|
|
3982
4581
|
}
|
|
4582
|
+
/**
|
|
4583
|
+
* 开始新的启动周期并清空旧快照,返回记录相机创建完成的一次性回调。
|
|
4584
|
+
*/
|
|
3983
4585
|
startCamera() {
|
|
3984
4586
|
this.sequence += 1;
|
|
3985
4587
|
this.active = true;
|
|
@@ -3989,17 +4591,34 @@ var RealtimeLaunchTimer = class {
|
|
|
3989
4591
|
this.notify();
|
|
3990
4592
|
return complete;
|
|
3991
4593
|
}
|
|
4594
|
+
/**
|
|
4595
|
+
* 开始会话与进房计时,返回连接阶段完成时调用的记录函数。
|
|
4596
|
+
*/
|
|
3992
4597
|
startConnection() {
|
|
3993
4598
|
return this.measure("connectionMs", performance.now());
|
|
3994
4599
|
}
|
|
4600
|
+
/**
|
|
4601
|
+
* 开始亮度检测计时;合格帧到达或超时放行时调用返回的记录函数。
|
|
4602
|
+
*/
|
|
4603
|
+
startFrameValidation() {
|
|
4604
|
+
return this.measure("frameValidationMs", performance.now());
|
|
4605
|
+
}
|
|
4606
|
+
/**
|
|
4607
|
+
* 开始等待远端显示首帧;完成回调同时记录首帧耗时和实际总耗时。
|
|
4608
|
+
*/
|
|
3995
4609
|
startFirstFrame() {
|
|
3996
4610
|
return this.measure("firstFrameMs", performance.now());
|
|
3997
4611
|
}
|
|
3998
|
-
/**
|
|
4612
|
+
/**
|
|
4613
|
+
* 冻结本轮结果,令尚未完成的回调失效;下一轮相机启动才重置。
|
|
4614
|
+
*/
|
|
3999
4615
|
cancel() {
|
|
4000
4616
|
this.active = false;
|
|
4001
4617
|
this.sequence += 1;
|
|
4002
4618
|
}
|
|
4619
|
+
/**
|
|
4620
|
+
* 创建当前周期的一次性计时回调;周期失效或该阶段已记录时忽略调用。
|
|
4621
|
+
*/
|
|
4003
4622
|
measure(key, startedAt) {
|
|
4004
4623
|
const sequence = this.sequence;
|
|
4005
4624
|
return () => {
|
|
@@ -4015,6 +4634,9 @@ var RealtimeLaunchTimer = class {
|
|
|
4015
4634
|
this.notify();
|
|
4016
4635
|
};
|
|
4017
4636
|
}
|
|
4637
|
+
/**
|
|
4638
|
+
* 分发不可变快照;隔离同步和异步观察者异常,避免影响媒体生命周期。
|
|
4639
|
+
*/
|
|
4018
4640
|
notify() {
|
|
4019
4641
|
try {
|
|
4020
4642
|
void Promise.resolve(this.listener?.(this.snapshot)).catch(() => {
|
|
@@ -4026,23 +4648,38 @@ var RealtimeLaunchTimer = class {
|
|
|
4026
4648
|
|
|
4027
4649
|
// src/Core/Realtime/XmaxRealtimeConnectionManager.ts
|
|
4028
4650
|
var XmaxRealtimeConnectionManager = class {
|
|
4651
|
+
/**
|
|
4652
|
+
* 注入会话、传输、计时和渲染回调依赖;连接资源在 connect 时创建。
|
|
4653
|
+
*/
|
|
4029
4654
|
constructor(dependencies) {
|
|
4030
4655
|
__publicField(this, "dependencies", dependencies);
|
|
4656
|
+
/**
|
|
4657
|
+
* 会话资源
|
|
4658
|
+
*/
|
|
4031
4659
|
__publicField(this, "activeSession");
|
|
4660
|
+
/**
|
|
4661
|
+
* 远端轨道与渲染绑定
|
|
4662
|
+
*/
|
|
4032
4663
|
__publicField(this, "activeRemoteTrack");
|
|
4033
|
-
__publicField(this, "
|
|
4034
|
-
__publicField(this, "targetSize");
|
|
4664
|
+
__publicField(this, "remoteTarget");
|
|
4035
4665
|
}
|
|
4666
|
+
/**
|
|
4667
|
+
* 当前活动会话标识;尚未连接或清理完成后为空。
|
|
4668
|
+
*/
|
|
4036
4669
|
get currentSessionID() {
|
|
4037
4670
|
return this.activeSession?.id;
|
|
4038
4671
|
}
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
/** 未配置 API 服务时在连接操作开始前报错,不影响本地预览。 */
|
|
4672
|
+
/**
|
|
4673
|
+
* 未配置 API 服务时在连接操作开始前报错,不影响本地预览。
|
|
4674
|
+
*/
|
|
4043
4675
|
validateConfiguration() {
|
|
4044
4676
|
this.requireSessionService();
|
|
4045
4677
|
}
|
|
4678
|
+
/**
|
|
4679
|
+
* 创建会话、配置编码、进房并发布本地媒体,启动心跳后返回远端占位流。
|
|
4680
|
+
* 进房结束即记录连接耗时;可选 beforePublish 检查只阻塞发布,不计入连接耗时。
|
|
4681
|
+
* @throws 会话、进房、发布失败或操作租约已失效时抛出错误,由上层统一清理。
|
|
4682
|
+
*/
|
|
4046
4683
|
async connect(options) {
|
|
4047
4684
|
const sessionService = this.requireSessionService();
|
|
4048
4685
|
const streamController = this.dependencies.streamController;
|
|
@@ -4069,9 +4706,12 @@ var XmaxRealtimeConnectionManager = class {
|
|
|
4069
4706
|
options.ensureCurrent();
|
|
4070
4707
|
await streamController.connect(connection, options.includeLocalAudio, () => {
|
|
4071
4708
|
options.ensureCurrent();
|
|
4072
|
-
},
|
|
4073
|
-
|
|
4074
|
-
|
|
4709
|
+
}, async () => {
|
|
4710
|
+
options.ensureCurrent();
|
|
4711
|
+
completeConnection();
|
|
4712
|
+
options.ensureCurrent();
|
|
4713
|
+
await options.beforePublish?.();
|
|
4714
|
+
});
|
|
4075
4715
|
options.ensureCurrent();
|
|
4076
4716
|
options.onPublished();
|
|
4077
4717
|
sessionService.startHeartbeat(session.id, {
|
|
@@ -4091,16 +4731,27 @@ var XmaxRealtimeConnectionManager = class {
|
|
|
4091
4731
|
streamController.setRemoteAudioVolume(this.dependencies.remoteAudioVolume());
|
|
4092
4732
|
return new RealtimeMediaStream({ id: "stream1" /* remote */, videoTrack: remoteTrack });
|
|
4093
4733
|
}
|
|
4734
|
+
/**
|
|
4735
|
+
* 更新已绑定远端视图的插帧配置;传 undefined 停用插帧。
|
|
4736
|
+
*/
|
|
4094
4737
|
setFrameInterpolation(options) {
|
|
4095
|
-
this.
|
|
4738
|
+
this.remoteTarget?.setFrameInterpolation?.(options);
|
|
4096
4739
|
}
|
|
4740
|
+
/**
|
|
4741
|
+
* 清空远端视图的媒体流,保留轨道与视图绑定以供统一清理。
|
|
4742
|
+
*/
|
|
4097
4743
|
clearRemoteMedia() {
|
|
4098
|
-
this.
|
|
4744
|
+
this.remoteTarget?.setMediaStream(null);
|
|
4099
4745
|
}
|
|
4746
|
+
/**
|
|
4747
|
+
* 停止会话心跳;未配置会话服务时不执行操作。
|
|
4748
|
+
*/
|
|
4100
4749
|
stopHeartbeat() {
|
|
4101
4750
|
this.dependencies.sessionService?.stopHeartbeat();
|
|
4102
4751
|
}
|
|
4103
|
-
/**
|
|
4752
|
+
/**
|
|
4753
|
+
* 等待接收轨解除静音;超时只警告,取消必须立即退出并移除监听。
|
|
4754
|
+
*/
|
|
4104
4755
|
async waitUntilRemoteTrackReady(timeoutMs, signal) {
|
|
4105
4756
|
if (signal.aborted) throw RealtimeCoordinator.cancelledError();
|
|
4106
4757
|
const mediaTrack = this.activeRemoteTrack?.mediaStreamTrack;
|
|
@@ -4140,7 +4791,7 @@ var XmaxRealtimeConnectionManager = class {
|
|
|
4140
4791
|
if (track) {
|
|
4141
4792
|
track.mediaStreamTrack = void 0;
|
|
4142
4793
|
}
|
|
4143
|
-
this.
|
|
4794
|
+
this.remoteTarget?.setMediaStream(null);
|
|
4144
4795
|
return;
|
|
4145
4796
|
}
|
|
4146
4797
|
if (!track) {
|
|
@@ -4150,9 +4801,11 @@ var XmaxRealtimeConnectionManager = class {
|
|
|
4150
4801
|
);
|
|
4151
4802
|
}
|
|
4152
4803
|
track.mediaStreamTrack = binding.videoTrack;
|
|
4153
|
-
this.
|
|
4804
|
+
this.remoteTarget?.setMediaStream(new MediaStream([binding.videoTrack]));
|
|
4154
4805
|
}
|
|
4155
|
-
/**
|
|
4806
|
+
/**
|
|
4807
|
+
* 为远端轨道注册渲染绑定:attach 时挂流占位,媒体轨到达后送入画面。
|
|
4808
|
+
*/
|
|
4156
4809
|
registerRemoteBinding(track) {
|
|
4157
4810
|
VideoRenderRegistry.register(track, {
|
|
4158
4811
|
frameDisplayHandler: () => {
|
|
@@ -4161,39 +4814,34 @@ var XmaxRealtimeConnectionManager = class {
|
|
|
4161
4814
|
}
|
|
4162
4815
|
},
|
|
4163
4816
|
attachHandler: (view) => {
|
|
4164
|
-
this.
|
|
4817
|
+
this.remoteTarget?.setFrameInterpolation?.(void 0);
|
|
4165
4818
|
view.isMirrored = this.dependencies.isMirrored();
|
|
4166
|
-
this.
|
|
4167
|
-
target: view,
|
|
4168
|
-
setFrameInterpolation: (options) => view.setFrameInterpolation?.(options),
|
|
4169
|
-
setMediaStream: (stream) => {
|
|
4170
|
-
view.setMediaStream(stream);
|
|
4171
|
-
},
|
|
4172
|
-
setMirrored: (mirrored) => {
|
|
4173
|
-
view.isMirrored = mirrored;
|
|
4174
|
-
}
|
|
4175
|
-
};
|
|
4819
|
+
this.remoteTarget = view;
|
|
4176
4820
|
this.dependencies.onRenderAttached();
|
|
4177
4821
|
const mediaTrack = track.mediaStreamTrack;
|
|
4178
4822
|
view.setMediaStream(mediaTrack ? new MediaStream([mediaTrack]) : null);
|
|
4179
4823
|
},
|
|
4180
4824
|
detachHandler: (view) => {
|
|
4181
|
-
if (this.
|
|
4825
|
+
if (this.remoteTarget === view) {
|
|
4182
4826
|
this.dependencies.onRenderDetached();
|
|
4183
|
-
this.
|
|
4827
|
+
this.remoteTarget = void 0;
|
|
4184
4828
|
}
|
|
4185
4829
|
view.setFrameInterpolation?.(void 0);
|
|
4186
4830
|
view.setMediaStream(null);
|
|
4187
4831
|
}
|
|
4188
4832
|
});
|
|
4189
4833
|
}
|
|
4190
|
-
/**
|
|
4834
|
+
/**
|
|
4835
|
+
* 同步远端结果画面的镜像状态:与当前本地摄像头位置保持一致。
|
|
4836
|
+
*/
|
|
4191
4837
|
updateRemoteMirror() {
|
|
4192
|
-
this.
|
|
4193
|
-
this.dependencies.isMirrored()
|
|
4194
|
-
|
|
4838
|
+
if (this.remoteTarget) {
|
|
4839
|
+
this.remoteTarget.isMirrored = this.dependencies.isMirrored();
|
|
4840
|
+
}
|
|
4195
4841
|
}
|
|
4196
|
-
/**
|
|
4842
|
+
/**
|
|
4843
|
+
* 构造当前远端生成结果媒体流。
|
|
4844
|
+
*/
|
|
4197
4845
|
makeRemoteStream() {
|
|
4198
4846
|
const track = this.activeRemoteTrack;
|
|
4199
4847
|
if (!track) {
|
|
@@ -4234,7 +4882,9 @@ var XmaxRealtimeConnectionManager = class {
|
|
|
4234
4882
|
closeReason: session.closeReason
|
|
4235
4883
|
});
|
|
4236
4884
|
}
|
|
4237
|
-
/**
|
|
4885
|
+
/**
|
|
4886
|
+
* 尽力清理全部连接资源,即使退房失败也关闭服务端会话。
|
|
4887
|
+
*/
|
|
4238
4888
|
async disconnect() {
|
|
4239
4889
|
const sessionID = this.activeSession?.id;
|
|
4240
4890
|
this.stopHeartbeat();
|
|
@@ -4252,8 +4902,7 @@ var XmaxRealtimeConnectionManager = class {
|
|
|
4252
4902
|
}
|
|
4253
4903
|
this.activeSession = void 0;
|
|
4254
4904
|
this.activeRemoteTrack = void 0;
|
|
4255
|
-
this.
|
|
4256
|
-
this.targetSize = void 0;
|
|
4905
|
+
this.remoteTarget = void 0;
|
|
4257
4906
|
if (sessionID && this.dependencies.sessionService) {
|
|
4258
4907
|
try {
|
|
4259
4908
|
await this.dependencies.sessionService.closeSession(sessionID);
|
|
@@ -4264,7 +4913,9 @@ var XmaxRealtimeConnectionManager = class {
|
|
|
4264
4913
|
}
|
|
4265
4914
|
return sessionID;
|
|
4266
4915
|
}
|
|
4267
|
-
/**
|
|
4916
|
+
/**
|
|
4917
|
+
* 获取会话 Service;未配置 API 服务时抛出配置错误。
|
|
4918
|
+
*/
|
|
4268
4919
|
requireSessionService() {
|
|
4269
4920
|
if (!this.dependencies.sessionService) {
|
|
4270
4921
|
throw new XmaxError(
|
|
@@ -4278,14 +4929,25 @@ var XmaxRealtimeConnectionManager = class {
|
|
|
4278
4929
|
|
|
4279
4930
|
// src/Core/Realtime/XmaxRealtimeGenerationManager.ts
|
|
4280
4931
|
var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
|
|
4932
|
+
/**
|
|
4933
|
+
* 注入房间传输组件和任务标识生成器,不创建会话或启动生成。
|
|
4934
|
+
*/
|
|
4281
4935
|
constructor(streamController, taskIDGenerator = _XmaxRealtimeGenerationManager.createTaskID) {
|
|
4282
4936
|
__publicField(this, "streamController", streamController);
|
|
4283
4937
|
__publicField(this, "taskIDGenerator", taskIDGenerator);
|
|
4938
|
+
/**
|
|
4939
|
+
* 当前生成条件与任务标识
|
|
4940
|
+
*/
|
|
4284
4941
|
__publicField(this, "currentContext");
|
|
4285
4942
|
__publicField(this, "activeTaskID");
|
|
4943
|
+
/**
|
|
4944
|
+
* 并发停止操作的共享结果
|
|
4945
|
+
*/
|
|
4286
4946
|
__publicField(this, "pendingStop");
|
|
4287
4947
|
}
|
|
4288
|
-
/**
|
|
4948
|
+
/**
|
|
4949
|
+
* 首次请求在建立连接前校验条件;后续请求可复用缓存。
|
|
4950
|
+
*/
|
|
4289
4951
|
validateContext(context) {
|
|
4290
4952
|
const resolved = context ?? this.currentContext;
|
|
4291
4953
|
if (!resolved) {
|
|
@@ -4293,6 +4955,10 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
|
|
|
4293
4955
|
}
|
|
4294
4956
|
return resolved;
|
|
4295
4957
|
}
|
|
4958
|
+
/**
|
|
4959
|
+
* 发送生成指令并等待确认、远端就绪和音频激活,成功后返回任务标识。
|
|
4960
|
+
* 条件上下文在生成确认后缓存;启动失败时尽力停止本次任务并保留原始错误。
|
|
4961
|
+
*/
|
|
4296
4962
|
async start(options) {
|
|
4297
4963
|
const context = this.validateContext(options.context);
|
|
4298
4964
|
options.ensureCurrent();
|
|
@@ -4302,7 +4968,6 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
|
|
|
4302
4968
|
const confirmation = this.streamController.beginGeneration({
|
|
4303
4969
|
taskID,
|
|
4304
4970
|
videoFormat: options.videoFormat,
|
|
4305
|
-
targetSize: options.targetSize,
|
|
4306
4971
|
context
|
|
4307
4972
|
});
|
|
4308
4973
|
await this.awaitConfirmation(confirmation, options.signal);
|
|
@@ -4318,6 +4983,9 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
|
|
|
4318
4983
|
throw error;
|
|
4319
4984
|
}
|
|
4320
4985
|
}
|
|
4986
|
+
/**
|
|
4987
|
+
* 更新活动任务的生成条件;信令发送成功后才覆盖缓存,无任务或上下文时抛错。
|
|
4988
|
+
*/
|
|
4321
4989
|
update(taskID, options) {
|
|
4322
4990
|
const context = options.context ?? this.currentContext;
|
|
4323
4991
|
if (!taskID || !context) {
|
|
@@ -4326,11 +4994,13 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
|
|
|
4326
4994
|
this.streamController.updateGeneration({
|
|
4327
4995
|
taskID,
|
|
4328
4996
|
videoFormat: options.videoFormat,
|
|
4329
|
-
targetSize: options.targetSize,
|
|
4330
4997
|
context
|
|
4331
4998
|
});
|
|
4332
4999
|
this.currentContext = context;
|
|
4333
5000
|
}
|
|
5001
|
+
/**
|
|
5002
|
+
* 幂等停止指定活动任务;并发调用共享结果,停止失败仅记录日志。
|
|
5003
|
+
*/
|
|
4334
5004
|
async stop(taskID = this.activeTaskID) {
|
|
4335
5005
|
if (this.pendingStop && (!taskID || this.pendingStop.taskID === taskID)) {
|
|
4336
5006
|
return this.pendingStop.completion;
|
|
@@ -4352,10 +5022,16 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
|
|
|
4352
5022
|
if (this.pendingStop?.completion === completion) this.pendingStop = void 0;
|
|
4353
5023
|
}
|
|
4354
5024
|
}
|
|
5025
|
+
/**
|
|
5026
|
+
* 清空缓存的生成条件并等待活动任务停止,供断连和关闭流程使用。
|
|
5027
|
+
*/
|
|
4355
5028
|
async reset(taskID) {
|
|
4356
5029
|
this.currentContext = void 0;
|
|
4357
5030
|
await this.stop(this.activeTaskID ?? (taskID || void 0));
|
|
4358
5031
|
}
|
|
5032
|
+
/**
|
|
5033
|
+
* 等待生成确认;取消时立即拒绝,并在所有结束路径移除取消监听。
|
|
5034
|
+
*/
|
|
4359
5035
|
async awaitConfirmation(confirmation, signal) {
|
|
4360
5036
|
await new Promise((resolve, reject) => {
|
|
4361
5037
|
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
@@ -4377,7 +5053,9 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
|
|
|
4377
5053
|
if (signal.aborted) abort();
|
|
4378
5054
|
});
|
|
4379
5055
|
}
|
|
4380
|
-
/**
|
|
5056
|
+
/**
|
|
5057
|
+
* 生成当前生成任务的唯一标识。
|
|
5058
|
+
*/
|
|
4381
5059
|
static createTaskID() {
|
|
4382
5060
|
const hex = crypto.randomUUID().replace(/-/g, "");
|
|
4383
5061
|
let binary = "";
|
|
@@ -4400,19 +5078,32 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4400
5078
|
* 相机控制器与传输层共享同一个 RTC 引擎。
|
|
4401
5079
|
*/
|
|
4402
5080
|
constructor(options, dependencies) {
|
|
4403
|
-
|
|
5081
|
+
/**
|
|
5082
|
+
* 业务配置
|
|
5083
|
+
*/
|
|
4404
5084
|
__publicField(this, "options");
|
|
4405
5085
|
__publicField(this, "mediaService");
|
|
5086
|
+
/**
|
|
5087
|
+
* 插帧能力、用户配置与渲染版本
|
|
5088
|
+
*/
|
|
4406
5089
|
__publicField(this, "supportsInterpolation");
|
|
4407
5090
|
__publicField(this, "interpolationRequested");
|
|
4408
5091
|
__publicField(this, "interpolationSize");
|
|
4409
5092
|
__publicField(this, "interpolationRevision", 0);
|
|
4410
|
-
|
|
5093
|
+
/**
|
|
5094
|
+
* 业务组件
|
|
5095
|
+
*/
|
|
4411
5096
|
__publicField(this, "coordinator");
|
|
4412
5097
|
__publicField(this, "errorHandler");
|
|
4413
5098
|
__publicField(this, "cameraController");
|
|
5099
|
+
/**
|
|
5100
|
+
* 启动计时与首帧通知
|
|
5101
|
+
*/
|
|
4414
5102
|
__publicField(this, "launchTimer", new RealtimeLaunchTimer());
|
|
4415
5103
|
__publicField(this, "remoteFrameDisplayHandler");
|
|
5104
|
+
/**
|
|
5105
|
+
* 本地、远端和网络统计快照及观察者
|
|
5106
|
+
*/
|
|
4416
5107
|
__publicField(this, "localVideoStatistics");
|
|
4417
5108
|
__publicField(this, "localVideoStatisticsListener");
|
|
4418
5109
|
__publicField(this, "remoteVideoStatistics");
|
|
@@ -4420,11 +5111,15 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4420
5111
|
__publicField(this, "acceptsVideoStatistics", false);
|
|
4421
5112
|
__publicField(this, "networkStatistics");
|
|
4422
5113
|
__publicField(this, "networkStatisticsListener");
|
|
4423
|
-
|
|
5114
|
+
/**
|
|
5115
|
+
* 实时业务管理组件;状态所有权分别归连接和生成管理器。
|
|
5116
|
+
*/
|
|
4424
5117
|
__publicField(this, "connectionManager");
|
|
4425
5118
|
__publicField(this, "generationManager");
|
|
4426
5119
|
__publicField(this, "streamController");
|
|
4427
|
-
|
|
5120
|
+
/**
|
|
5121
|
+
* 音量配置
|
|
5122
|
+
*/
|
|
4428
5123
|
__publicField(this, "storedLocalAudioVolume", 1);
|
|
4429
5124
|
__publicField(this, "storedRemoteAudioVolume", 0);
|
|
4430
5125
|
this.options = options;
|
|
@@ -4435,7 +5130,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4435
5130
|
const rtcManager = dependencies?.rtcManager ?? new RtcManager();
|
|
4436
5131
|
this.cameraController = dependencies?.cameraController ?? new CameraController({
|
|
4437
5132
|
rtcManager,
|
|
4438
|
-
mediaService:
|
|
5133
|
+
mediaService: this.mediaService,
|
|
4439
5134
|
errorListener: (error) => {
|
|
4440
5135
|
void this.errorHandler.report(error);
|
|
4441
5136
|
}
|
|
@@ -4490,13 +5185,21 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4490
5185
|
}
|
|
4491
5186
|
});
|
|
4492
5187
|
}
|
|
4493
|
-
/**
|
|
5188
|
+
/**
|
|
5189
|
+
* 当前实时连接与生成状态。
|
|
5190
|
+
*/
|
|
4494
5191
|
get currentState() {
|
|
4495
5192
|
return this.coordinator.currentState;
|
|
4496
5193
|
}
|
|
5194
|
+
/**
|
|
5195
|
+
* 当前是否请求启用插帧;能力探测或运行期降级后可能为 false。
|
|
5196
|
+
*/
|
|
4497
5197
|
get isFrameInterpolationEnabled() {
|
|
4498
5198
|
return this.interpolationRequested;
|
|
4499
5199
|
}
|
|
5200
|
+
/**
|
|
5201
|
+
* 串行更新插帧开关;开启前验证能力,不支持时抛错并保留原配置,不改变回传尺寸。
|
|
5202
|
+
*/
|
|
4500
5203
|
async setFrameInterpolationEnabled(enabled) {
|
|
4501
5204
|
await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
|
|
4502
5205
|
const format = this.cameraController.currentTrack?.videoFormat;
|
|
@@ -4511,6 +5214,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4511
5214
|
this.applyRemoteInterpolation();
|
|
4512
5215
|
});
|
|
4513
5216
|
}
|
|
5217
|
+
/**
|
|
5218
|
+
* 根据当前视频尺寸准备插帧,能力不可用时降级为原视频,并在应用前校验租约。
|
|
5219
|
+
*/
|
|
4514
5220
|
async prepareFrameInterpolation(format, token) {
|
|
4515
5221
|
let size;
|
|
4516
5222
|
if (this.interpolationRequested) {
|
|
@@ -4527,6 +5233,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4527
5233
|
this.interpolationSize = size;
|
|
4528
5234
|
this.applyRemoteInterpolation();
|
|
4529
5235
|
}
|
|
5236
|
+
/**
|
|
5237
|
+
* 在 5 秒预算内探测插帧能力;超时或探测失败返回 false,主动取消则拒绝。
|
|
5238
|
+
*/
|
|
4530
5239
|
checkInterpolationSupport(size, token) {
|
|
4531
5240
|
return new Promise((resolve, reject) => {
|
|
4532
5241
|
const finish = (supported) => {
|
|
@@ -4550,6 +5259,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4550
5259
|
void this.supportsInterpolation(size).then(finish, () => finish(false));
|
|
4551
5260
|
});
|
|
4552
5261
|
}
|
|
5262
|
+
/**
|
|
5263
|
+
* 将当前插帧配置同步到远端绑定,并用版本号忽略旧管线的迟到失败。
|
|
5264
|
+
*/
|
|
4553
5265
|
applyRemoteInterpolation() {
|
|
4554
5266
|
const revision = ++this.interpolationRevision;
|
|
4555
5267
|
const size = this.interpolationSize;
|
|
@@ -4561,6 +5273,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4561
5273
|
}
|
|
4562
5274
|
} : void 0);
|
|
4563
5275
|
}
|
|
5276
|
+
/**
|
|
5277
|
+
* 关闭已失败的插帧配置并记录降级原因,保持原始视频渲染可用。
|
|
5278
|
+
*/
|
|
4564
5279
|
handleInterpolationFailure(error) {
|
|
4565
5280
|
this.interpolationRequested = false;
|
|
4566
5281
|
this.interpolationSize = void 0;
|
|
@@ -4568,11 +5283,15 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4568
5283
|
XmaxLogger.render.warning(() => `\u63D2\u5E27\u5DF2\u964D\u7EA7 (Frame Interpolation Disabled)
|
|
4569
5284
|
\u2514\u2500 ${XmaxError.from(error).message}`);
|
|
4570
5285
|
}
|
|
4571
|
-
/**
|
|
5286
|
+
/**
|
|
5287
|
+
* 当前本地媒体预览音量,取值范围为 `0...1`。
|
|
5288
|
+
*/
|
|
4572
5289
|
get localAudioVolume() {
|
|
4573
5290
|
return this.storedLocalAudioVolume;
|
|
4574
5291
|
}
|
|
4575
|
-
/**
|
|
5292
|
+
/**
|
|
5293
|
+
* 当前远端生成音频播放音量,取值范围为 `0...1`。
|
|
5294
|
+
*/
|
|
4576
5295
|
get remoteAudioVolume() {
|
|
4577
5296
|
return this.storedRemoteAudioVolume;
|
|
4578
5297
|
}
|
|
@@ -4584,20 +5303,29 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4584
5303
|
async setStateListener(listener) {
|
|
4585
5304
|
await this.coordinator.setStateListener(listener);
|
|
4586
5305
|
}
|
|
4587
|
-
/**
|
|
5306
|
+
/**
|
|
5307
|
+
* 监听启动耗时;设置后立即回放,传 undefined 取消监听。
|
|
5308
|
+
*/
|
|
4588
5309
|
async setLaunchTimingListener(listener) {
|
|
4589
5310
|
this.launchTimer.setListener(listener);
|
|
4590
5311
|
}
|
|
4591
|
-
/**
|
|
5312
|
+
/**
|
|
5313
|
+
* 监听本地主视频流的实际运行统计;立即回放最新快照。
|
|
5314
|
+
*/
|
|
4592
5315
|
async setLocalVideoStatisticsListener(listener) {
|
|
4593
5316
|
this.localVideoStatisticsListener = listener;
|
|
4594
5317
|
this.notifyLocalVideoStatistics();
|
|
4595
5318
|
}
|
|
4596
|
-
/**
|
|
5319
|
+
/**
|
|
5320
|
+
* 监听当前生成结果流的实际运行统计;立即回放最新快照。
|
|
5321
|
+
*/
|
|
4597
5322
|
async setRemoteVideoStatisticsListener(listener) {
|
|
4598
5323
|
this.remoteVideoStatisticsListener = listener;
|
|
4599
5324
|
this.notifyRemoteVideoStatistics();
|
|
4600
5325
|
}
|
|
5326
|
+
/**
|
|
5327
|
+
* 回放当前远端视频统计,隔离观察者的同步和异步异常。
|
|
5328
|
+
*/
|
|
4601
5329
|
notifyRemoteVideoStatistics() {
|
|
4602
5330
|
try {
|
|
4603
5331
|
void Promise.resolve(this.remoteVideoStatisticsListener?.(this.remoteVideoStatistics)).catch(() => {
|
|
@@ -4605,11 +5333,16 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4605
5333
|
} catch {
|
|
4606
5334
|
}
|
|
4607
5335
|
}
|
|
4608
|
-
/**
|
|
5336
|
+
/**
|
|
5337
|
+
* 网络统计立即回放最新快照,无数据或断开后为 undefined。
|
|
5338
|
+
*/
|
|
4609
5339
|
async setNetworkStatisticsListener(listener) {
|
|
4610
5340
|
this.networkStatisticsListener = listener;
|
|
4611
5341
|
this.notifyNetworkStatistics();
|
|
4612
5342
|
}
|
|
5343
|
+
/**
|
|
5344
|
+
* 回放当前网络统计,隔离观察者的同步和异步异常。
|
|
5345
|
+
*/
|
|
4613
5346
|
notifyNetworkStatistics() {
|
|
4614
5347
|
try {
|
|
4615
5348
|
void Promise.resolve(this.networkStatisticsListener?.(this.networkStatistics)).catch(() => {
|
|
@@ -4617,6 +5350,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4617
5350
|
} catch {
|
|
4618
5351
|
}
|
|
4619
5352
|
}
|
|
5353
|
+
/**
|
|
5354
|
+
* 清除远端视频统计;原先存在数据时通知观察者数据已失效。
|
|
5355
|
+
*/
|
|
4620
5356
|
clearRemoteVideoStatistics() {
|
|
4621
5357
|
const hadStatistics = this.remoteVideoStatistics !== void 0;
|
|
4622
5358
|
this.remoteVideoStatistics = void 0;
|
|
@@ -4624,6 +5360,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4624
5360
|
this.notifyRemoteVideoStatistics();
|
|
4625
5361
|
}
|
|
4626
5362
|
}
|
|
5363
|
+
/**
|
|
5364
|
+
* 回放当前本地视频统计,隔离观察者的同步和异步异常。
|
|
5365
|
+
*/
|
|
4627
5366
|
notifyLocalVideoStatistics() {
|
|
4628
5367
|
try {
|
|
4629
5368
|
void Promise.resolve(this.localVideoStatisticsListener?.(this.localVideoStatistics)).catch(() => {
|
|
@@ -4631,6 +5370,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4631
5370
|
} catch {
|
|
4632
5371
|
}
|
|
4633
5372
|
}
|
|
5373
|
+
/**
|
|
5374
|
+
* 停止接收媒体统计并清空各类快照,仅对曾有数据的统计发出清空通知。
|
|
5375
|
+
*/
|
|
4634
5376
|
clearVideoStatistics() {
|
|
4635
5377
|
this.acceptsVideoStatistics = false;
|
|
4636
5378
|
const hadNetworkStatistics = this.networkStatistics !== void 0;
|
|
@@ -4677,6 +5419,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4677
5419
|
*
|
|
4678
5420
|
* 将返回的轨道绑定到预览视图;收到有效帧且视图已绑定后进入 `ready`。
|
|
4679
5421
|
*
|
|
5422
|
+
* @param options.enableFrameValidation 是否在发布前检测帧亮度,默认 true;false 跳过检测及其等待。
|
|
4680
5423
|
* @returns 包含本地相机视频轨道的媒体流。
|
|
4681
5424
|
* @throws 模型不支持相机输入、配置无效、权限或采集启动失败时抛出错误。
|
|
4682
5425
|
*/
|
|
@@ -4711,7 +5454,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4711
5454
|
}
|
|
4712
5455
|
);
|
|
4713
5456
|
}
|
|
4714
|
-
/**
|
|
5457
|
+
/**
|
|
5458
|
+
* 停止本地相机流并释放本地预览与 RTC 资源。
|
|
5459
|
+
*/
|
|
4715
5460
|
async stopLocalCameraStream() {
|
|
4716
5461
|
await this.coordinator.run(
|
|
4717
5462
|
"media" /* media */,
|
|
@@ -4752,8 +5497,8 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4752
5497
|
* 使用当前 Manager 创建的本地流建立实时连接。
|
|
4753
5498
|
*
|
|
4754
5499
|
* 创建实时会话、加入 RTC 房间并发布本地流,成功后启动会话心跳。
|
|
4755
|
-
*
|
|
4756
|
-
*
|
|
5500
|
+
* 启用帧检测时,发布前并行检查相机亮度,首张合格帧即放行;2 秒内未通过检查则
|
|
5501
|
+
* 记录警告并按当前画面继续发布,不因环境较暗而阻断连接。
|
|
4757
5502
|
* 返回的远端媒体流在生成开始后承载远端生成画面。
|
|
4758
5503
|
*
|
|
4759
5504
|
* @param localStream 由 `createLocalCameraStream` 创建的本地媒体流。
|
|
@@ -4812,7 +5557,6 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4812
5557
|
if (currentState.connectionState === "Generating" /* generating */) {
|
|
4813
5558
|
this.generationManager.update(currentState.taskID, {
|
|
4814
5559
|
videoFormat,
|
|
4815
|
-
targetSize: this.connectionManager.currentTargetSize,
|
|
4816
5560
|
context: options.context
|
|
4817
5561
|
});
|
|
4818
5562
|
return this.connectionManager.makeRemoteStream();
|
|
@@ -4825,7 +5569,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4825
5569
|
}
|
|
4826
5570
|
);
|
|
4827
5571
|
}
|
|
4828
|
-
/**
|
|
5572
|
+
/**
|
|
5573
|
+
* 断开实时连接并保留当前本地媒体预览。
|
|
5574
|
+
*/
|
|
4829
5575
|
async disconnect() {
|
|
4830
5576
|
this.launchTimer.cancel();
|
|
4831
5577
|
this.clearVideoStatistics();
|
|
@@ -4866,9 +5612,17 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4866
5612
|
token.signal.addEventListener("abort", abortExposure, { once: true });
|
|
4867
5613
|
if (token.signal.aborted) abortExposure();
|
|
4868
5614
|
try {
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
5615
|
+
let beforePublish;
|
|
5616
|
+
if (this.cameraController.isFrameValidationEnabled) {
|
|
5617
|
+
const completeFrameValidation = this.launchTimer.startFrameValidation();
|
|
5618
|
+
const cameraReady = this.cameraController.waitForValidCameraFrame(exposureController.signal).then(() => {
|
|
5619
|
+
token.ensureCurrent();
|
|
5620
|
+
completeFrameValidation();
|
|
5621
|
+
});
|
|
5622
|
+
void cameraReady.catch(() => {
|
|
5623
|
+
});
|
|
5624
|
+
beforePublish = () => cameraReady;
|
|
5625
|
+
}
|
|
4872
5626
|
const remote = await this.connectionManager.connect({
|
|
4873
5627
|
localTrack,
|
|
4874
5628
|
model: this.options.model,
|
|
@@ -4877,7 +5631,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4877
5631
|
onPublished: () => {
|
|
4878
5632
|
this.acceptsVideoStatistics = true;
|
|
4879
5633
|
},
|
|
4880
|
-
beforePublish
|
|
5634
|
+
beforePublish
|
|
4881
5635
|
});
|
|
4882
5636
|
await this.coordinator.commit(
|
|
4883
5637
|
new RealtimeState({
|
|
@@ -4892,7 +5646,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4892
5646
|
abortExposure();
|
|
4893
5647
|
}
|
|
4894
5648
|
}
|
|
4895
|
-
/**
|
|
5649
|
+
/**
|
|
5650
|
+
* 编排插帧、首帧计时和生成状态,任务与确认由生成管理器负责。
|
|
5651
|
+
*/
|
|
4896
5652
|
async performStartGeneration(token, videoFormat, context) {
|
|
4897
5653
|
token.setFailureScope(0 /* connection */);
|
|
4898
5654
|
await this.prepareFrameInterpolation(videoFormat, token);
|
|
@@ -4902,7 +5658,6 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4902
5658
|
};
|
|
4903
5659
|
const taskID = await this.generationManager.start({
|
|
4904
5660
|
videoFormat,
|
|
4905
|
-
targetSize: this.connectionManager.currentTargetSize,
|
|
4906
5661
|
context,
|
|
4907
5662
|
signal: token.signal,
|
|
4908
5663
|
ensureCurrent: () => token.ensureCurrent(),
|
|
@@ -4921,7 +5676,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4921
5676
|
);
|
|
4922
5677
|
return this.connectionManager.makeRemoteStream();
|
|
4923
5678
|
}
|
|
4924
|
-
/**
|
|
5679
|
+
/**
|
|
5680
|
+
* 心跳失败或会话失效:结束当前连接生命周期并通过最终状态给出原因。
|
|
5681
|
+
*/
|
|
4925
5682
|
async handleHeartbeatFailure(sessionID, error) {
|
|
4926
5683
|
await this.coordinator.terminateWithError(
|
|
4927
5684
|
error,
|
|
@@ -4929,7 +5686,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4929
5686
|
() => this.connectionManager.currentSessionID === sessionID
|
|
4930
5687
|
);
|
|
4931
5688
|
}
|
|
4932
|
-
/**
|
|
5689
|
+
/**
|
|
5690
|
+
* 统一清理顺序:停止心跳与生成,再断开连接;all 额外释放本地媒体。
|
|
5691
|
+
*/
|
|
4933
5692
|
async performCleanup(scope, taskID) {
|
|
4934
5693
|
this.launchTimer.cancel();
|
|
4935
5694
|
this.remoteFrameDisplayHandler = void 0;
|
|
@@ -4955,7 +5714,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
4955
5714
|
hasLocalMedia: this.cameraController.currentTrack !== void 0
|
|
4956
5715
|
};
|
|
4957
5716
|
}
|
|
4958
|
-
/**
|
|
5717
|
+
/**
|
|
5718
|
+
* 校验音量取值范围为 `0...1`。
|
|
5719
|
+
*/
|
|
4959
5720
|
static validateVolume(volume) {
|
|
4960
5721
|
if (!Number.isFinite(volume) || volume < 0 || volume > 1) {
|
|
4961
5722
|
throw new XmaxError(
|
|
@@ -4976,11 +5737,23 @@ var XmaxConfiguration = class {
|
|
|
4976
5737
|
* @param init.loggerOptions SDK 输出的日志类型;默认为空。
|
|
4977
5738
|
*/
|
|
4978
5739
|
constructor(init) {
|
|
4979
|
-
/**
|
|
5740
|
+
/**
|
|
5741
|
+
* 服务身份与环境
|
|
5742
|
+
*/
|
|
5743
|
+
/**
|
|
5744
|
+
* 调用 Xmax 服务使用的 API Key。
|
|
5745
|
+
*/
|
|
4980
5746
|
__publicField(this, "apiKey");
|
|
4981
|
-
/**
|
|
5747
|
+
/**
|
|
5748
|
+
* SDK 连接的服务环境,同时决定日志细项语言:国内为中文,海外为英文。
|
|
5749
|
+
*/
|
|
4982
5750
|
__publicField(this, "environment");
|
|
4983
|
-
/**
|
|
5751
|
+
/**
|
|
5752
|
+
* 日志配置
|
|
5753
|
+
*/
|
|
5754
|
+
/**
|
|
5755
|
+
* SDK 输出的日志类型;默认为不输出日志。
|
|
5756
|
+
*/
|
|
4984
5757
|
__publicField(this, "loggerOptions");
|
|
4985
5758
|
this.apiKey = init.apiKey.trim();
|
|
4986
5759
|
this.environment = init.environment ?? "china" /* china */;
|
|
@@ -5009,9 +5782,16 @@ var XmaxClient = class {
|
|
|
5009
5782
|
* 在实际调用时校验 API Key。
|
|
5010
5783
|
*/
|
|
5011
5784
|
constructor(configuration) {
|
|
5012
|
-
/**
|
|
5785
|
+
/**
|
|
5786
|
+
* 客户端配置
|
|
5787
|
+
*/
|
|
5788
|
+
/**
|
|
5789
|
+
* 客户端使用的全局配置。
|
|
5790
|
+
*/
|
|
5013
5791
|
__publicField(this, "configuration");
|
|
5014
|
-
|
|
5792
|
+
/**
|
|
5793
|
+
* 服务层组件
|
|
5794
|
+
*/
|
|
5015
5795
|
__publicField(this, "apiService");
|
|
5016
5796
|
this.configuration = configuration;
|
|
5017
5797
|
XmaxLogger.configure(
|
|
@@ -5057,19 +5837,33 @@ var RealtimeConfiguration = class {
|
|
|
5057
5837
|
*
|
|
5058
5838
|
* @param init.model 实时生成业务使用的模型。
|
|
5059
5839
|
* @param init.isFrameInterpolationEnabled 是否默认开启远端生成画面的插帧。
|
|
5840
|
+
* @param init.frameInterpolation 插帧开关和显示帧率上限的详细配置。
|
|
5060
5841
|
*/
|
|
5061
5842
|
constructor(init) {
|
|
5062
|
-
/**
|
|
5843
|
+
/**
|
|
5844
|
+
* 模型配置
|
|
5845
|
+
*/
|
|
5846
|
+
/**
|
|
5847
|
+
* 实时生成业务使用的模型。
|
|
5848
|
+
*/
|
|
5063
5849
|
__publicField(this, "model");
|
|
5064
|
-
/**
|
|
5850
|
+
/**
|
|
5851
|
+
* 远端插帧配置
|
|
5852
|
+
*/
|
|
5853
|
+
/**
|
|
5854
|
+
* 是否默认请求开启远端生成画面的插帧。
|
|
5855
|
+
*/
|
|
5065
5856
|
__publicField(this, "isFrameInterpolationEnabled");
|
|
5857
|
+
/**
|
|
5858
|
+
* 已补全默认值的只读插帧配置。
|
|
5859
|
+
*/
|
|
5066
5860
|
__publicField(this, "frameInterpolation");
|
|
5067
5861
|
this.model = init.model;
|
|
5068
5862
|
const targetFrameRate = init.frameInterpolation?.targetFrameRate ?? 60;
|
|
5069
5863
|
if (!Number.isFinite(targetFrameRate) || targetFrameRate < 1 || targetFrameRate > 60) {
|
|
5070
5864
|
throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Interpolation targetFrameRate must be between 1 and 60");
|
|
5071
5865
|
}
|
|
5072
|
-
this.isFrameInterpolationEnabled = init.frameInterpolation?.enabled ?? init.isFrameInterpolationEnabled ??
|
|
5866
|
+
this.isFrameInterpolationEnabled = init.frameInterpolation?.enabled ?? init.isFrameInterpolationEnabled ?? false;
|
|
5073
5867
|
this.frameInterpolation = Object.freeze({ enabled: this.isFrameInterpolationEnabled, targetFrameRate });
|
|
5074
5868
|
}
|
|
5075
5869
|
};
|
|
@@ -5083,15 +5877,24 @@ var VideoContentMode = /* @__PURE__ */ ((VideoContentMode2) => {
|
|
|
5083
5877
|
|
|
5084
5878
|
// src/Service/Realtime/RealtimeContext.ts
|
|
5085
5879
|
var RealtimeContext = class {
|
|
5086
|
-
/**
|
|
5880
|
+
/**
|
|
5881
|
+
* 创建实时生成条件并规范化文本和参考路径。
|
|
5882
|
+
*/
|
|
5087
5883
|
constructor(init) {
|
|
5088
|
-
/**
|
|
5884
|
+
/**
|
|
5885
|
+
* 生成条件
|
|
5886
|
+
*/
|
|
5887
|
+
/**
|
|
5888
|
+
* 实时生成使用的文本条件。
|
|
5889
|
+
*/
|
|
5089
5890
|
__publicField(this, "prompt");
|
|
5090
|
-
/**
|
|
5891
|
+
/**
|
|
5892
|
+
* 已上传参考图片的可选远端路径。
|
|
5893
|
+
*/
|
|
5091
5894
|
__publicField(this, "referencePath");
|
|
5092
5895
|
this.prompt = init.prompt.trim();
|
|
5093
5896
|
const normalizedReferencePath = init.referencePath?.trim();
|
|
5094
|
-
this.referencePath = normalizedReferencePath
|
|
5897
|
+
this.referencePath = normalizedReferencePath || void 0;
|
|
5095
5898
|
}
|
|
5096
5899
|
};
|
|
5097
5900
|
|
|
@@ -7067,7 +7870,7 @@ struct Output { @builtin(position) position: vec4f, @location(0) uv: vec2f }
|
|
|
7067
7870
|
out.uv = vec2f((pos[i].x + 1) * 0.5, (1 - pos[i].y) * 0.5);
|
|
7068
7871
|
return out;
|
|
7069
7872
|
}`;
|
|
7070
|
-
var
|
|
7873
|
+
var _FrameInterpolationManager = class _FrameInterpolationManager {
|
|
7071
7874
|
constructor(device, canvas, size, onFailure) {
|
|
7072
7875
|
__publicField(this, "device", device);
|
|
7073
7876
|
__publicField(this, "canvas", canvas);
|
|
@@ -7075,7 +7878,7 @@ var FrameInterpolationManager = class _FrameInterpolationManager {
|
|
|
7075
7878
|
__publicField(this, "onFailure", onFailure);
|
|
7076
7879
|
__publicField(this, "runtime");
|
|
7077
7880
|
__publicField(this, "textures", []);
|
|
7078
|
-
__publicField(this, "
|
|
7881
|
+
__publicField(this, "nextOriginal", 0);
|
|
7079
7882
|
__publicField(this, "capturePipeline");
|
|
7080
7883
|
__publicField(this, "displayPipeline");
|
|
7081
7884
|
__publicField(this, "sampler");
|
|
@@ -7140,7 +7943,7 @@ var FrameInterpolationManager = class _FrameInterpolationManager {
|
|
|
7140
7943
|
throw new Error("Interpolation initialization cancelled");
|
|
7141
7944
|
}
|
|
7142
7945
|
this.runtime = runtime;
|
|
7143
|
-
for (let i = 0; i <
|
|
7946
|
+
for (let i = 0; i < _FrameInterpolationManager.originalSlots + 1; i++) {
|
|
7144
7947
|
this.textures.push(device.createTexture({
|
|
7145
7948
|
size: [w, h],
|
|
7146
7949
|
format: "rgba8unorm",
|
|
@@ -7183,21 +7986,21 @@ var FrameInterpolationManager = class _FrameInterpolationManager {
|
|
|
7183
7986
|
}
|
|
7184
7987
|
}
|
|
7185
7988
|
capture(video) {
|
|
7186
|
-
|
|
7187
|
-
this.
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
this.runtime.
|
|
7194
|
-
this.runtime.runT(0.5, this.textures[2]);
|
|
7989
|
+
const slot = this.nextOriginal;
|
|
7990
|
+
this.nextOriginal = (this.nextOriginal + 1) % _FrameInterpolationManager.originalSlots;
|
|
7991
|
+
this.draw(this.capturePipeline, this.device.importExternalTexture({ source: video }), this.textures[slot].createView());
|
|
7992
|
+
return slot;
|
|
7993
|
+
}
|
|
7994
|
+
async interpolate(previous, current) {
|
|
7995
|
+
this.runtime.prepPair(this.textures[previous], this.textures[current]);
|
|
7996
|
+
this.runtime.runT(0.5, this.textures[_FrameInterpolationManager.originalSlots]);
|
|
7195
7997
|
await this.device.queue.onSubmittedWorkDone();
|
|
7998
|
+
return _FrameInterpolationManager.originalSlots;
|
|
7196
7999
|
}
|
|
7197
|
-
|
|
7198
|
-
this.
|
|
8000
|
+
present(slot) {
|
|
8001
|
+
this.presentTexture(this.textures[slot]);
|
|
7199
8002
|
}
|
|
7200
|
-
|
|
8003
|
+
presentTexture(texture) {
|
|
7201
8004
|
this.draw(this.displayPipeline, texture.createView(), this.context.getCurrentTexture().createView());
|
|
7202
8005
|
}
|
|
7203
8006
|
draw(pipeline, source, target) {
|
|
@@ -7225,6 +8028,9 @@ var FrameInterpolationManager = class _FrameInterpolationManager {
|
|
|
7225
8028
|
this.device.destroy();
|
|
7226
8029
|
}
|
|
7227
8030
|
};
|
|
8031
|
+
/** 原帧环形缓冲的槽位数;插值结果固定在最后一个槽位。 */
|
|
8032
|
+
__publicField(_FrameInterpolationManager, "originalSlots", 3);
|
|
8033
|
+
var FrameInterpolationManager = _FrameInterpolationManager;
|
|
7228
8034
|
|
|
7229
8035
|
// src/Render/Video/RemoteVideoFramePipeline.ts
|
|
7230
8036
|
var RemoteVideoFramePipeline = class {
|
|
@@ -7236,18 +8042,35 @@ var RemoteVideoFramePipeline = class {
|
|
|
7236
8042
|
__publicField(this, "processor");
|
|
7237
8043
|
__publicField(this, "abort", new AbortController());
|
|
7238
8044
|
__publicField(this, "callback");
|
|
7239
|
-
__publicField(this, "
|
|
8045
|
+
__publicField(this, "raf");
|
|
7240
8046
|
__publicField(this, "stallTimer");
|
|
7241
8047
|
__publicField(this, "initializationTimer");
|
|
7242
|
-
__publicField(this, "animation");
|
|
7243
8048
|
__publicField(this, "loading", false);
|
|
7244
8049
|
__publicField(this, "busy", false);
|
|
7245
8050
|
__publicField(this, "stopped", false);
|
|
7246
|
-
__publicField(this, "
|
|
8051
|
+
__publicField(this, "generation", 0);
|
|
8052
|
+
/** 播放时钟锚点:最近一次输入帧的媒体时间与对应的 vsync 时刻。 */
|
|
8053
|
+
__publicField(this, "anchor");
|
|
8054
|
+
/** 输出延迟,取最近一次有效源帧间隔,为中间帧留出计算窗口。 */
|
|
8055
|
+
__publicField(this, "delay", 0);
|
|
8056
|
+
__publicField(this, "queue", []);
|
|
8057
|
+
__publicField(this, "displayedTime");
|
|
7247
8058
|
__publicField(this, "previousTime");
|
|
8059
|
+
__publicField(this, "previousSlot");
|
|
7248
8060
|
__publicField(this, "previousPresented");
|
|
7249
8061
|
__publicField(this, "slowPairs", 0);
|
|
7250
8062
|
__publicField(this, "active", false);
|
|
8063
|
+
/** vsync 显示节拍:弹出到点的帧并显示最新的一张。 */
|
|
8064
|
+
__publicField(this, "tick", (now) => {
|
|
8065
|
+
if (this.stopped) return;
|
|
8066
|
+
this.raf = requestAnimationFrame(this.tick);
|
|
8067
|
+
const position = this.position(now);
|
|
8068
|
+
let frame;
|
|
8069
|
+
while (this.queue.length > 0 && this.queue[0].time <= position) {
|
|
8070
|
+
frame = this.queue.shift();
|
|
8071
|
+
}
|
|
8072
|
+
if (frame) this.present(frame);
|
|
8073
|
+
});
|
|
7251
8074
|
}
|
|
7252
8075
|
start() {
|
|
7253
8076
|
if (this.stopped || this.callback !== void 0) return;
|
|
@@ -7259,78 +8082,101 @@ var RemoteVideoFramePipeline = class {
|
|
|
7259
8082
|
}
|
|
7260
8083
|
observe() {
|
|
7261
8084
|
if (this.stopped) return;
|
|
7262
|
-
this.callback = this.video.requestVideoFrameCallback((
|
|
8085
|
+
this.callback = this.video.requestVideoFrameCallback((now, metadata) => {
|
|
7263
8086
|
this.callback = void 0;
|
|
7264
8087
|
if (this.stopped) return;
|
|
7265
8088
|
try {
|
|
7266
|
-
this.frame(metadata);
|
|
8089
|
+
this.frame(now, metadata);
|
|
7267
8090
|
} catch (error) {
|
|
7268
8091
|
this.fail(error);
|
|
7269
8092
|
}
|
|
7270
8093
|
this.observe();
|
|
7271
8094
|
});
|
|
7272
8095
|
}
|
|
7273
|
-
frame(metadata) {
|
|
8096
|
+
frame(now, metadata) {
|
|
7274
8097
|
if (typeof document !== "undefined" && document.hidden) {
|
|
7275
|
-
this.
|
|
8098
|
+
this.resetTimeline();
|
|
7276
8099
|
return;
|
|
7277
8100
|
}
|
|
7278
8101
|
if (metadata.width !== this.options.size.width || metadata.height !== this.options.size.height) {
|
|
7279
|
-
this.
|
|
8102
|
+
this.resetTimeline();
|
|
7280
8103
|
return;
|
|
7281
8104
|
}
|
|
7282
8105
|
if (!this.processor) {
|
|
7283
8106
|
if (!this.loading) void this.initialize();
|
|
7284
8107
|
return;
|
|
7285
8108
|
}
|
|
7286
|
-
|
|
7287
|
-
|
|
8109
|
+
const time = metadata.mediaTime * 1e3;
|
|
8110
|
+
if (!Number.isFinite(time) || this.previousTime !== void 0 && time <= this.previousTime) return;
|
|
8111
|
+
const previous = this.previousTime;
|
|
8112
|
+
const interval = previous === void 0 ? 0 : time - previous;
|
|
7288
8113
|
const consecutive = this.previousPresented === void 0 || metadata.presentedFrames === this.previousPresented + 1;
|
|
7289
|
-
this.
|
|
7290
|
-
|
|
7291
|
-
this.
|
|
7292
|
-
this.previousTime = metadata.mediaTime;
|
|
8114
|
+
const previousSlot = this.previousSlot;
|
|
8115
|
+
this.anchor = { media: time, at: now };
|
|
8116
|
+
this.previousTime = time;
|
|
7293
8117
|
this.previousPresented = metadata.presentedFrames;
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
8118
|
+
const slot = this.processor.capture(this.video);
|
|
8119
|
+
this.previousSlot = slot;
|
|
8120
|
+
if (previous === void 0 || interval > 250) {
|
|
8121
|
+
this.queue = [];
|
|
8122
|
+
this.delay = 0;
|
|
8123
|
+
this.present({ slot, time });
|
|
8124
|
+
this.startTick();
|
|
7297
8125
|
return;
|
|
7298
8126
|
}
|
|
8127
|
+
this.delay = interval;
|
|
8128
|
+
this.enqueue({ slot, time });
|
|
8129
|
+
this.startTick();
|
|
8130
|
+
if (!consecutive || interval < 2e3 / this.options.targetFrameRate - 1 || previousSlot === void 0 || this.busy) return;
|
|
8131
|
+
this.interpolate(previousSlot, slot, previous, time, interval);
|
|
8132
|
+
}
|
|
8133
|
+
/** 帧按显示时间升序入队;同一时间的内容后到先出。 */
|
|
8134
|
+
enqueue(frame) {
|
|
8135
|
+
const index = this.queue.findIndex((queued) => queued.time >= frame.time);
|
|
8136
|
+
if (index < 0) this.queue.push(frame);
|
|
8137
|
+
else this.queue.splice(index, 0, frame);
|
|
8138
|
+
}
|
|
8139
|
+
interpolate(previousSlot, currentSlot, previousTime, currentTime, interval) {
|
|
8140
|
+
const generation = this.generation;
|
|
8141
|
+
const midpoint = (previousTime + currentTime) / 2;
|
|
7299
8142
|
const startedAt = performance.now();
|
|
7300
|
-
this.processor.presentPrevious();
|
|
7301
|
-
this.canvas.style.visibility = "visible";
|
|
7302
8143
|
this.busy = true;
|
|
7303
8144
|
this.stallTimer = setTimeout(() => {
|
|
7304
8145
|
if (this.busy) this.fail(new Error("Interpolation GPU submission timed out"));
|
|
7305
|
-
else this.resetPair();
|
|
7306
8146
|
}, Math.max(100, interval * 3));
|
|
7307
|
-
void this.processor.interpolate().then(() => {
|
|
8147
|
+
void this.processor.interpolate(previousSlot, currentSlot).then((slot) => {
|
|
7308
8148
|
if (this.stopped) return;
|
|
7309
8149
|
this.busy = false;
|
|
7310
|
-
|
|
8150
|
+
clearTimeout(this.stallTimer);
|
|
8151
|
+
this.stallTimer = void 0;
|
|
8152
|
+
if (generation !== this.generation) return;
|
|
7311
8153
|
const elapsed = performance.now() - startedAt;
|
|
7312
8154
|
this.slowPairs = elapsed > interval / 2 ? this.slowPairs + 1 : 0;
|
|
7313
8155
|
if (this.slowPairs >= 5) {
|
|
7314
8156
|
this.fail(new Error("Interpolation exceeded the GPU frame budget for 5 consecutive pairs"));
|
|
7315
8157
|
return;
|
|
7316
8158
|
}
|
|
7317
|
-
if (
|
|
7318
|
-
this.
|
|
7319
|
-
this.animation = requestAnimationFrame(() => {
|
|
7320
|
-
this.animation = void 0;
|
|
7321
|
-
if (this.stopped || pair !== this.pair || performance.now() - startedAt >= interval) return;
|
|
7322
|
-
try {
|
|
7323
|
-
this.processor.presentInterpolated();
|
|
7324
|
-
this.setActive(true);
|
|
7325
|
-
} catch (error) {
|
|
7326
|
-
this.fail(error);
|
|
7327
|
-
}
|
|
7328
|
-
});
|
|
7329
|
-
}, Math.max(0, interval / 2 - elapsed));
|
|
8159
|
+
if (this.position(performance.now()) >= midpoint) return;
|
|
8160
|
+
this.enqueue({ slot, time: midpoint });
|
|
7330
8161
|
}).catch((error) => {
|
|
7331
8162
|
if (!this.stopped) this.fail(error);
|
|
7332
8163
|
});
|
|
7333
8164
|
}
|
|
8165
|
+
/** 显示时钟位置:锚点媒体时间随墙钟推进,整体后移一个源帧间隔。 */
|
|
8166
|
+
position(now) {
|
|
8167
|
+
if (!this.anchor) return -Infinity;
|
|
8168
|
+
return this.anchor.media + (now - this.anchor.at) - this.delay;
|
|
8169
|
+
}
|
|
8170
|
+
startTick() {
|
|
8171
|
+
if (this.raf === void 0) this.raf = requestAnimationFrame(this.tick);
|
|
8172
|
+
}
|
|
8173
|
+
present(frame) {
|
|
8174
|
+
if (this.displayedTime === frame.time) return;
|
|
8175
|
+
this.processor.present(frame.slot);
|
|
8176
|
+
this.displayedTime = frame.time;
|
|
8177
|
+
this.canvas.style.visibility = "visible";
|
|
8178
|
+
this.setActive(true);
|
|
8179
|
+
}
|
|
7334
8180
|
async initialize() {
|
|
7335
8181
|
this.loading = true;
|
|
7336
8182
|
this.initializationTimer = setTimeout(() => this.fail(new Error("Interpolation initialization timed out")), 15e3);
|
|
@@ -7359,16 +8205,18 @@ var RemoteVideoFramePipeline = class {
|
|
|
7359
8205
|
this.active = active;
|
|
7360
8206
|
this.options.onActiveChange?.(active);
|
|
7361
8207
|
}
|
|
7362
|
-
|
|
7363
|
-
|
|
8208
|
+
resetTimeline() {
|
|
8209
|
+
this.generation++;
|
|
8210
|
+
this.queue = [];
|
|
7364
8211
|
clearTimeout(this.stallTimer);
|
|
7365
|
-
|
|
7366
|
-
this.
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
this.
|
|
7370
|
-
this.
|
|
8212
|
+
this.stallTimer = void 0;
|
|
8213
|
+
if (this.raf !== void 0) cancelAnimationFrame(this.raf);
|
|
8214
|
+
this.raf = void 0;
|
|
8215
|
+
this.anchor = void 0;
|
|
8216
|
+
this.delay = 0;
|
|
8217
|
+
this.displayedTime = void 0;
|
|
7371
8218
|
this.previousTime = void 0;
|
|
8219
|
+
this.previousSlot = void 0;
|
|
7372
8220
|
this.previousPresented = void 0;
|
|
7373
8221
|
this.canvas.style.visibility = "hidden";
|
|
7374
8222
|
this.setActive(false);
|
|
@@ -7385,7 +8233,7 @@ var RemoteVideoFramePipeline = class {
|
|
|
7385
8233
|
if (this.callback !== void 0) this.video.cancelVideoFrameCallback(this.callback);
|
|
7386
8234
|
this.callback = void 0;
|
|
7387
8235
|
clearTimeout(this.initializationTimer);
|
|
7388
|
-
this.
|
|
8236
|
+
this.resetTimeline();
|
|
7389
8237
|
this.processor?.destroy();
|
|
7390
8238
|
this.processor = void 0;
|
|
7391
8239
|
}
|
|
@@ -7403,25 +8251,46 @@ var XmaxVideoView = class {
|
|
|
7403
8251
|
* @param options.videoContentMode 视频内容显示模式,默认 fill。
|
|
7404
8252
|
*/
|
|
7405
8253
|
constructor(options) {
|
|
7406
|
-
|
|
7407
|
-
|
|
8254
|
+
/**
|
|
8255
|
+
* 视图元素
|
|
8256
|
+
*/
|
|
8257
|
+
/**
|
|
8258
|
+
* 视图根元素(div,内含一个 video 元素)。
|
|
8259
|
+
*/
|
|
7408
8260
|
__publicField(this, "element");
|
|
7409
|
-
/**
|
|
8261
|
+
/**
|
|
8262
|
+
* @internal 渲染视频帧的元素,供绑定管线使用。
|
|
8263
|
+
*/
|
|
7410
8264
|
__publicField(this, "videoElement");
|
|
7411
|
-
|
|
7412
|
-
|
|
8265
|
+
/**
|
|
8266
|
+
* 公共配置
|
|
8267
|
+
*/
|
|
8268
|
+
/**
|
|
8269
|
+
* 首帧实际提交显示时调用(一次性;换轨后重新武装)。
|
|
8270
|
+
*/
|
|
7413
8271
|
__publicField(this, "frameDisplayHandler");
|
|
7414
|
-
|
|
8272
|
+
/**
|
|
8273
|
+
* 渲染状态
|
|
8274
|
+
*/
|
|
7415
8275
|
__publicField(this, "currentTrack");
|
|
7416
8276
|
__publicField(this, "contentMode");
|
|
7417
8277
|
__publicField(this, "mirrored", false);
|
|
8278
|
+
/**
|
|
8279
|
+
* 首帧显示通知与异步回调版本
|
|
8280
|
+
*/
|
|
7418
8281
|
__publicField(this, "hasNotifiedFrameDisplay", false);
|
|
7419
8282
|
__publicField(this, "onPlaying");
|
|
7420
8283
|
__publicField(this, "videoFrameCallbackID");
|
|
7421
8284
|
__publicField(this, "frameNotificationSequence", 0);
|
|
8285
|
+
/**
|
|
8286
|
+
* 远端插帧配置与渲染资源
|
|
8287
|
+
*/
|
|
7422
8288
|
__publicField(this, "interpolationOptions");
|
|
7423
8289
|
__publicField(this, "interpolationPipeline");
|
|
7424
8290
|
__publicField(this, "interpolationCanvas");
|
|
8291
|
+
/**
|
|
8292
|
+
* DOM 挂载状态
|
|
8293
|
+
*/
|
|
7425
8294
|
__publicField(this, "detached", false);
|
|
7426
8295
|
this.contentMode = options?.videoContentMode ?? "fill" /* fill */;
|
|
7427
8296
|
this.element = document.createElement("div");
|
|
@@ -7439,10 +8308,15 @@ var XmaxVideoView = class {
|
|
|
7439
8308
|
this.applyContentMode();
|
|
7440
8309
|
this.element.appendChild(this.videoElement);
|
|
7441
8310
|
}
|
|
7442
|
-
/**
|
|
8311
|
+
/**
|
|
8312
|
+
* 当前显示的视频轨道;切换时自动解绑旧轨道。
|
|
8313
|
+
*/
|
|
7443
8314
|
get track() {
|
|
7444
8315
|
return this.currentTrack;
|
|
7445
8316
|
}
|
|
8317
|
+
/**
|
|
8318
|
+
* 解绑旧轨道后接入新轨道,并为新内容重新启用一次性首帧通知。
|
|
8319
|
+
*/
|
|
7446
8320
|
set track(track) {
|
|
7447
8321
|
if (this.currentTrack === track) {
|
|
7448
8322
|
return;
|
|
@@ -7454,10 +8328,15 @@ var XmaxVideoView = class {
|
|
|
7454
8328
|
this.attachTrack(track);
|
|
7455
8329
|
}
|
|
7456
8330
|
}
|
|
7457
|
-
/**
|
|
8331
|
+
/**
|
|
8332
|
+
* 视频内容在容器中的显示模式。
|
|
8333
|
+
*/
|
|
7458
8334
|
get videoContentMode() {
|
|
7459
8335
|
return this.contentMode;
|
|
7460
8336
|
}
|
|
8337
|
+
/**
|
|
8338
|
+
* 更新视频及插帧画布的内容缩放方式;相同配置不重复应用。
|
|
8339
|
+
*/
|
|
7461
8340
|
set videoContentMode(mode) {
|
|
7462
8341
|
if (this.contentMode === mode) {
|
|
7463
8342
|
return;
|
|
@@ -7465,10 +8344,15 @@ var XmaxVideoView = class {
|
|
|
7465
8344
|
this.contentMode = mode;
|
|
7466
8345
|
this.applyContentMode();
|
|
7467
8346
|
}
|
|
7468
|
-
/**
|
|
8347
|
+
/**
|
|
8348
|
+
* 本地预览是否镜像显示(仅影响显示,不影响发布流)。
|
|
8349
|
+
*/
|
|
7469
8350
|
get isMirrored() {
|
|
7470
8351
|
return this.mirrored;
|
|
7471
8352
|
}
|
|
8353
|
+
/**
|
|
8354
|
+
* 更新视图镜像变换,仅改变呈现方式,不改动媒体流。
|
|
8355
|
+
*/
|
|
7472
8356
|
set isMirrored(mirrored) {
|
|
7473
8357
|
if (this.mirrored === mirrored) {
|
|
7474
8358
|
return;
|
|
@@ -7476,7 +8360,9 @@ var XmaxVideoView = class {
|
|
|
7476
8360
|
this.mirrored = mirrored;
|
|
7477
8361
|
this.applyContentMode();
|
|
7478
8362
|
}
|
|
7479
|
-
/**
|
|
8363
|
+
/**
|
|
8364
|
+
* 挂载到容器元素。
|
|
8365
|
+
*/
|
|
7480
8366
|
attach(to) {
|
|
7481
8367
|
this.detached = false;
|
|
7482
8368
|
to.appendChild(this.element);
|
|
@@ -7491,14 +8377,18 @@ var XmaxVideoView = class {
|
|
|
7491
8377
|
this.armFrameDisplayNotification();
|
|
7492
8378
|
}
|
|
7493
8379
|
}
|
|
7494
|
-
/**
|
|
8380
|
+
/**
|
|
8381
|
+
* 从容器元素移除。
|
|
8382
|
+
*/
|
|
7495
8383
|
detach() {
|
|
7496
8384
|
this.detached = true;
|
|
7497
8385
|
this.stopInterpolation();
|
|
7498
8386
|
this.cancelFrameDisplayNotification();
|
|
7499
8387
|
this.element.remove();
|
|
7500
8388
|
}
|
|
7501
|
-
/**
|
|
8389
|
+
/**
|
|
8390
|
+
* 直接设置渲染用的媒体流(绑定管线和直接渲染共用)。 @internal
|
|
8391
|
+
*/
|
|
7502
8392
|
setMediaStream(stream) {
|
|
7503
8393
|
if (this.videoElement.srcObject === stream) {
|
|
7504
8394
|
return;
|
|
@@ -7512,13 +8402,18 @@ var XmaxVideoView = class {
|
|
|
7512
8402
|
this.startInterpolation();
|
|
7513
8403
|
}
|
|
7514
8404
|
}
|
|
7515
|
-
/**
|
|
8405
|
+
/**
|
|
8406
|
+
* 由远端绑定注入配置;本地预览不走此管线。 @internal
|
|
8407
|
+
*/
|
|
7516
8408
|
setFrameInterpolation(options) {
|
|
7517
8409
|
if (options === this.interpolationOptions) return;
|
|
7518
8410
|
this.stopInterpolation();
|
|
7519
8411
|
this.interpolationOptions = options;
|
|
7520
8412
|
this.startInterpolation();
|
|
7521
8413
|
}
|
|
8414
|
+
/**
|
|
8415
|
+
* 视图已挂载且媒体与配置就绪时创建插帧画布和管线,避免重复启动。
|
|
8416
|
+
*/
|
|
7522
8417
|
startInterpolation() {
|
|
7523
8418
|
if (this.detached || this.interpolationPipeline || !this.interpolationOptions || !this.videoElement.srcObject) return;
|
|
7524
8419
|
const canvas = document.createElement("canvas");
|
|
@@ -7538,6 +8433,9 @@ var XmaxVideoView = class {
|
|
|
7538
8433
|
this.interpolationPipeline = pipeline;
|
|
7539
8434
|
pipeline.start();
|
|
7540
8435
|
}
|
|
8436
|
+
/**
|
|
8437
|
+
* 停止并释放插帧管线,移除辅助画布,保留原始视频元素。
|
|
8438
|
+
*/
|
|
7541
8439
|
stopInterpolation() {
|
|
7542
8440
|
const pipeline = this.interpolationPipeline;
|
|
7543
8441
|
this.interpolationPipeline = void 0;
|
|
@@ -7545,7 +8443,9 @@ var XmaxVideoView = class {
|
|
|
7545
8443
|
this.interpolationCanvas?.remove();
|
|
7546
8444
|
this.interpolationCanvas = void 0;
|
|
7547
8445
|
}
|
|
7548
|
-
/**
|
|
8446
|
+
/**
|
|
8447
|
+
* 接入轨道画面:优先走注册的渲染绑定,否则按媒体轨直接渲染。
|
|
8448
|
+
*/
|
|
7549
8449
|
attachTrack(track) {
|
|
7550
8450
|
const binding = VideoRenderRegistry.binding(track);
|
|
7551
8451
|
if (binding) {
|
|
@@ -7560,7 +8460,9 @@ var XmaxVideoView = class {
|
|
|
7560
8460
|
() => `\u8F68\u9053 ${track.id} \u65E2\u6CA1\u6709\u6E32\u67D3\u7ED1\u5B9A\u4E5F\u6CA1\u6709\u5A92\u4F53\u8F68\uFF0C\u65E0\u6CD5\u663E\u793A`
|
|
7561
8461
|
);
|
|
7562
8462
|
}
|
|
7563
|
-
/**
|
|
8463
|
+
/**
|
|
8464
|
+
* 解绑当前轨道并清空渲染内容。
|
|
8465
|
+
*/
|
|
7564
8466
|
detachCurrentTrack() {
|
|
7565
8467
|
const track = this.currentTrack;
|
|
7566
8468
|
if (track) {
|
|
@@ -7570,7 +8472,9 @@ var XmaxVideoView = class {
|
|
|
7570
8472
|
this.cancelFrameDisplayNotification();
|
|
7571
8473
|
this.videoElement.srcObject = null;
|
|
7572
8474
|
}
|
|
7573
|
-
/**
|
|
8475
|
+
/**
|
|
8476
|
+
* 首帧提交合成器时通知;旧浏览器回退到 playing 近似判断。
|
|
8477
|
+
*/
|
|
7574
8478
|
armFrameDisplayNotification() {
|
|
7575
8479
|
this.cancelFrameDisplayNotification();
|
|
7576
8480
|
const sequence = this.frameNotificationSequence;
|
|
@@ -7597,7 +8501,9 @@ var XmaxVideoView = class {
|
|
|
7597
8501
|
queueMicrotask(notify);
|
|
7598
8502
|
}
|
|
7599
8503
|
}
|
|
7600
|
-
/**
|
|
8504
|
+
/**
|
|
8505
|
+
* 换流、解绑或卸载时取消观察,防止迟到的首帧归入下一条流。
|
|
8506
|
+
*/
|
|
7601
8507
|
cancelFrameDisplayNotification() {
|
|
7602
8508
|
this.frameNotificationSequence += 1;
|
|
7603
8509
|
if (this.videoFrameCallbackID !== void 0) {
|
|
@@ -7609,7 +8515,9 @@ var XmaxVideoView = class {
|
|
|
7609
8515
|
this.onPlaying = void 0;
|
|
7610
8516
|
}
|
|
7611
8517
|
}
|
|
7612
|
-
/**
|
|
8518
|
+
/**
|
|
8519
|
+
* 应用显示模式与镜像样式。
|
|
8520
|
+
*/
|
|
7613
8521
|
applyContentMode() {
|
|
7614
8522
|
this.videoElement.style.objectFit = OBJECT_FIT[this.contentMode];
|
|
7615
8523
|
this.videoElement.style.transform = this.mirrored ? "scaleX(-1)" : "";
|
|
@@ -7631,13 +8539,21 @@ var XmaxRealtimeVideoView = class {
|
|
|
7631
8539
|
* @param options.videoContentMode 本地与远端视频的内容显示模式,默认 fill。
|
|
7632
8540
|
*/
|
|
7633
8541
|
constructor(options) {
|
|
7634
|
-
|
|
7635
|
-
|
|
8542
|
+
/**
|
|
8543
|
+
* 视图元素
|
|
8544
|
+
*/
|
|
8545
|
+
/**
|
|
8546
|
+
* 视图根元素。
|
|
8547
|
+
*/
|
|
7636
8548
|
__publicField(this, "element");
|
|
7637
|
-
|
|
8549
|
+
/**
|
|
8550
|
+
* 视频视图
|
|
8551
|
+
*/
|
|
7638
8552
|
__publicField(this, "localVideoView");
|
|
7639
8553
|
__publicField(this, "remoteVideoView");
|
|
7640
|
-
|
|
8554
|
+
/**
|
|
8555
|
+
* 公共配置
|
|
8556
|
+
*/
|
|
7641
8557
|
__publicField(this, "currentLocalTrack");
|
|
7642
8558
|
__publicField(this, "currentRemoteTrack");
|
|
7643
8559
|
__publicField(this, "contentMode");
|
|
@@ -7658,10 +8574,15 @@ var XmaxRealtimeVideoView = class {
|
|
|
7658
8574
|
this.localVideoView.track = this.currentLocalTrack;
|
|
7659
8575
|
this.updateRemoteTrack();
|
|
7660
8576
|
}
|
|
7661
|
-
/**
|
|
8577
|
+
/**
|
|
8578
|
+
* 当前显示的本地视频轨道。
|
|
8579
|
+
*/
|
|
7662
8580
|
get localTrack() {
|
|
7663
8581
|
return this.currentLocalTrack;
|
|
7664
8582
|
}
|
|
8583
|
+
/**
|
|
8584
|
+
* 替换本地预览轨道;轨道未变化时保留当前绑定。
|
|
8585
|
+
*/
|
|
7665
8586
|
set localTrack(track) {
|
|
7666
8587
|
if (this.currentLocalTrack === track) {
|
|
7667
8588
|
return;
|
|
@@ -7676,6 +8597,9 @@ var XmaxRealtimeVideoView = class {
|
|
|
7676
8597
|
get remoteTrack() {
|
|
7677
8598
|
return this.currentRemoteTrack;
|
|
7678
8599
|
}
|
|
8600
|
+
/**
|
|
8601
|
+
* 替换远端轨道并重新等待首帧渐入;清空轨道时恢复本地预览。
|
|
8602
|
+
*/
|
|
7679
8603
|
set remoteTrack(track) {
|
|
7680
8604
|
if (this.currentRemoteTrack === track) {
|
|
7681
8605
|
return;
|
|
@@ -7683,10 +8607,15 @@ var XmaxRealtimeVideoView = class {
|
|
|
7683
8607
|
this.currentRemoteTrack = track;
|
|
7684
8608
|
this.updateRemoteTrack();
|
|
7685
8609
|
}
|
|
7686
|
-
/**
|
|
8610
|
+
/**
|
|
8611
|
+
* 视频内容在容器中的显示模式。
|
|
8612
|
+
*/
|
|
7687
8613
|
get videoContentMode() {
|
|
7688
8614
|
return this.contentMode;
|
|
7689
8615
|
}
|
|
8616
|
+
/**
|
|
8617
|
+
* 同步调整本地与远端视频在容器中的缩放和裁剪方式。
|
|
8618
|
+
*/
|
|
7690
8619
|
set videoContentMode(mode) {
|
|
7691
8620
|
if (this.contentMode === mode) {
|
|
7692
8621
|
return;
|
|
@@ -7695,26 +8624,37 @@ var XmaxRealtimeVideoView = class {
|
|
|
7695
8624
|
this.localVideoView.videoContentMode = mode;
|
|
7696
8625
|
this.remoteVideoView.videoContentMode = mode;
|
|
7697
8626
|
}
|
|
7698
|
-
/**
|
|
8627
|
+
/**
|
|
8628
|
+
* 本地预览是否镜像显示(前置摄像头)。
|
|
8629
|
+
*/
|
|
7699
8630
|
get isLocalMirrored() {
|
|
7700
8631
|
return this.localVideoView.isMirrored;
|
|
7701
8632
|
}
|
|
8633
|
+
/**
|
|
8634
|
+
* 更新本地预览镜像;不修改采集帧或远端画面。
|
|
8635
|
+
*/
|
|
7702
8636
|
set isLocalMirrored(mirrored) {
|
|
7703
8637
|
this.localVideoView.isMirrored = mirrored;
|
|
7704
8638
|
}
|
|
7705
|
-
/**
|
|
8639
|
+
/**
|
|
8640
|
+
* 挂载到容器元素。
|
|
8641
|
+
*/
|
|
7706
8642
|
attach(to) {
|
|
7707
8643
|
to.appendChild(this.element);
|
|
7708
8644
|
this.localVideoView.attach(this.element);
|
|
7709
8645
|
this.remoteVideoView.attach(this.element);
|
|
7710
8646
|
}
|
|
7711
|
-
/**
|
|
8647
|
+
/**
|
|
8648
|
+
* 从容器元素移除。
|
|
8649
|
+
*/
|
|
7712
8650
|
detach() {
|
|
7713
8651
|
this.localVideoView.detach();
|
|
7714
8652
|
this.remoteVideoView.detach();
|
|
7715
8653
|
this.element.remove();
|
|
7716
8654
|
}
|
|
7717
|
-
/**
|
|
8655
|
+
/**
|
|
8656
|
+
* 更新远端轨道绑定:重置渐入状态,等待首帧提交后再显示远端画面。
|
|
8657
|
+
*/
|
|
7718
8658
|
updateRemoteTrack() {
|
|
7719
8659
|
const remoteTrack = this.currentRemoteTrack;
|
|
7720
8660
|
this.remoteVideoView.frameDisplayHandler = void 0;
|
|
@@ -7733,7 +8673,9 @@ var XmaxRealtimeVideoView = class {
|
|
|
7733
8673
|
};
|
|
7734
8674
|
this.remoteVideoView.track = remoteTrack;
|
|
7735
8675
|
}
|
|
7736
|
-
/**
|
|
8676
|
+
/**
|
|
8677
|
+
* 远端首帧已提交:渐入远端画面。
|
|
8678
|
+
*/
|
|
7737
8679
|
showRemoteVideo() {
|
|
7738
8680
|
const element = this.remoteVideoView.element;
|
|
7739
8681
|
if (element.style.visibility === "visible" && element.style.opacity === "1") {
|
|
@@ -7776,4 +8718,4 @@ export {
|
|
|
7776
8718
|
XmaxVideoView,
|
|
7777
8719
|
XmaxRealtimeVideoView
|
|
7778
8720
|
};
|
|
7779
|
-
//# sourceMappingURL=chunk-
|
|
8721
|
+
//# sourceMappingURL=chunk-NW6YF4JT.js.map
|