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