mn-video-player-v2 1.4.9

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.
@@ -0,0 +1,1120 @@
1
+ // classic 模式:依赖(mn-common-c.js / mn-media.js / mn-media.wasm)需与 worker 脚本同目录部署
2
+ // 新版 Emscripten 不再支持 mainScriptUrlOrBlob:pthread worker 直接以当前 worker 脚本 URL
3
+ // (即本文件)创建,因此本文件会在 em-pthread 子线程里再次执行。
4
+ // 子线程只负责解码,绝不能接管消息处理,否则会覆盖 mn-media.js 安装的 handleMessage,
5
+ // 导致主线程等待 "loaded"(cmd 3)握手永远不返回、worker 一直加载中。
6
+ const isPthreadWorker = self.name === "em-pthread";
7
+ if (!isPthreadWorker) {
8
+ globalThis.Module = {
9
+ onRuntimeInitialized: function () {
10
+ // memory64 构建:SDK 导出未被胶水包装,先补 BigInt 转换再对外可用
11
+ wrapSdkBigIntExports();
12
+ onWasmLoaded();
13
+ },
14
+ // 过滤 wasm 内 FFmpeg 的良性日志(AV_LOG_WARNING,不影响解码):
15
+ // "[hevc @ 0x...] Unknown profile bitstream"
16
+ // FFmpeg 7.x hevc/ps.c 在 SPS 的 general_profile_idc 不在 {Main,Main10,Main Still,RExt} 时打印,
17
+ // 属常见设备码流(如用 compatibility_flag 声明 Main 的流),解码会继续,仅控制台噪音。
18
+ // pthread worker 的 stderr 会代理回主线程的 printErr,统一在这里过滤。
19
+ printErr: function (msg) {
20
+ if (msg && /profile bitstream/.test(msg)) {
21
+ return; // 丢弃已知良性警告
22
+ }
23
+ console.error(msg);
24
+ },
25
+ };
26
+ }
27
+
28
+ self.importScripts("./mn-common-c.js");
29
+ self.importScripts("./mn-media.js"); // 加载wasm视频解码器 直接作为JS运行
30
+ /**
31
+ * @description: web worker用于流媒体获取(websocket)及编(音频)、解码(音视频)、对讲
32
+ * @author: lantf
33
+ * @date: 2020/10/18 15:28
34
+ */
35
+
36
+ class MnWorker {
37
+ constructor() {
38
+ this.ws = "ws";
39
+ this.host = "";
40
+ this.port = 0;
41
+ this.deviceType = "";
42
+ this.phone = "";
43
+ this.channel = 0;
44
+ this.messageId = 0;
45
+ this.mediaType = 0;
46
+ this.streamType = 0;
47
+ this.beginTime = "";
48
+ this.playbackMode = 0;
49
+ this.ctrlSendTime0 = 0;
50
+ this.ctrlSendTime1 = 0;
51
+ this.netSpeedTimer = null;
52
+ this.realPlayTimer = null;
53
+ this.playBackTimer = null;
54
+ this.lang = null;
55
+ this.playAudio = false;
56
+ this.coreLogLevel = 0;
57
+ this.cacheReqQue = [];
58
+ this.cacheDataQue = [];
59
+ this.recorderLen = 0;
60
+ this.decoding = true;
61
+ this.wasmLoaded = false;
62
+ this.hasConnected = false;
63
+ this.hasInited = false;
64
+ this.hasCached = false;
65
+ this.decoderInited = false;
66
+ this.chunkSize = 1024 * 1024;
67
+ this.websocket = null;
68
+ this.cacheBuffer = null;
69
+ this.speakBuffer = null;
70
+ this.readyCallback = null;
71
+ this.videoCallback = null;
72
+ this.audioCallback = null;
73
+ this.preTimeStamp = 0;
74
+ this.pauseTime = 0;
75
+ this.resumeTime = 0;
76
+ this.delayTime = 1000;
77
+ this.keyFrame = null;
78
+ this.netSpeedTime = 1;
79
+ this.netSpeedSize = 0;
80
+ this.heartBeatTime = 0;
81
+ this.fastForward = false;
82
+ this.voiceScheduling = false; // 语音调度
83
+ this.prefix = "/mndp/stream";
84
+ this.logger = new Logger("MnWorker");
85
+ this.userId = null;
86
+ this.tenantId = null;
87
+ this.token = null;
88
+ // * 倍速 (0-无效,1-1倍,2-2倍,3-4倍,4-8倍,5-16倍)
89
+ this.speed = 0;
90
+ this.isPause = false;
91
+ this.speedValue = 1;
92
+ this.isStreamPause = false;
93
+ this.streamPauseTime = 0;
94
+ this.playMode = 0; // 0-实时 1-回放
95
+ this.voiceTimeStamp = 0;
96
+ this.offsetTime = 0;
97
+ this.offsetDelay = 0;
98
+ this.isCacheLow = false;
99
+ this.isFirstLoaded = false;
100
+ this.checkCacheTimer = null;
101
+ }
102
+
103
+ /* 偏移 */
104
+ offset(time) {
105
+ /* 如果是倍速则不比较 */
106
+ if (this.speed > 2) {
107
+ return;
108
+ }
109
+ this.offsetDelay = 0;
110
+ if (!time) return;
111
+ this.offsetTime = parseInt(time);
112
+ let isVideoFast = false;
113
+ if (this.offsetTime > 0) {
114
+ /* 视频慢了减少等待时间 */
115
+ isVideoFast = false;
116
+ } else {
117
+ /* 视频快了增加等待时间 */
118
+ isVideoFast = true;
119
+ }
120
+
121
+ if (Math.abs(this.offsetTime) > 2000) {
122
+ /* 超过2s需要处理 */
123
+ /* 严重不同步 */
124
+ // console.log("严重不同步!", this.offsetTime);
125
+ this.offsetDelay = isVideoFast ? +200 : -200;
126
+ return;
127
+ }
128
+ if (Math.abs(this.offsetTime) > 1000) {
129
+ /* 超过1s需要处理 */
130
+ /* 稍微不同步 */
131
+ // console.log("延迟", this.offsetTime);
132
+ this.offsetDelay = isVideoFast ? +100 : -100;
133
+ return;
134
+ }
135
+ if (Math.abs(this.offsetTime) > 200) {
136
+ /* 超过200ms需要处理 */
137
+ /* 稍微不同步 */
138
+ // console.log("延迟", this.offsetTime);
139
+ this.offsetDelay = isVideoFast ? +10 : -10;
140
+ return;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * 打开解码器
146
+ **/
147
+ baseInit() {
148
+ this.cacheBuffer = Module._malloc(this.chunkSize);
149
+ this.speakBuffer = Module._malloc(1024);
150
+ let phoneBuffer = Module._malloc(16);
151
+ self.HEAPU8.set(this.toInt8Array(this.phone), phoneBuffer);
152
+ let ret = Module._baseInit(
153
+ phoneBuffer,
154
+ this.channel,
155
+ this.readyCallback,
156
+ this.videoCallback,
157
+ this.audioCallback,
158
+ this.speakCallback,
159
+ this.coreLogLevel,
160
+ );
161
+ if (phoneBuffer) {
162
+ Module._free(phoneBuffer);
163
+ }
164
+
165
+ let objData = {
166
+ t: kOpenDecoderRsp,
167
+ e: ret,
168
+ };
169
+ self.postMessage(objData);
170
+ }
171
+
172
+ /**
173
+ * 关闭解码器
174
+ **/
175
+ baseUnInit() {
176
+ let ret = 0;
177
+ Module._videoUnInit();
178
+ Module._baseUnInit();
179
+
180
+ if (this.cacheBuffer != null) {
181
+ Module._free(this.cacheBuffer);
182
+ this.cacheBuffer = null;
183
+ }
184
+ if (this.speakBuffer != null) {
185
+ Module._free(this.speakBuffer);
186
+ this.speakBuffer = null;
187
+ }
188
+
189
+ this.decoderInited = false;
190
+
191
+ let objData = {
192
+ t: kCloseDecoderRsp,
193
+ e: ret,
194
+ };
195
+ self.postMessage(objData);
196
+ }
197
+
198
+ /**
199
+ * 统计流量
200
+ **/
201
+ calNetSpeed() {
202
+ const speed = this.netSpeedSize / this.netSpeedTime;
203
+ const len = this.cacheDataQue.length;
204
+ this.netSpeedSize = 0;
205
+ let objData = {
206
+ t: kNetSpeedFrame,
207
+ d: speed,
208
+ len,
209
+ };
210
+ self.postMessage(objData);
211
+
212
+ // 心跳(this.netSpeedTime的10倍秒数)
213
+ this.heartBeatTime += this.netSpeedTime;
214
+ if (this.websocket != null && this.hasConnected) {
215
+ if (this.heartBeatTime % (this.netSpeedTime * 10) == 0) {
216
+ let req = {};
217
+ req.phone = this.phone;
218
+ req.channel = this.channel;
219
+ req.messageId = 0x0000;
220
+ let json = JSON.stringify(req);
221
+ this.websocket.send(json);
222
+ }
223
+ }
224
+ }
225
+ uuid() {
226
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
227
+ /[xy]/g,
228
+ function (c) {
229
+ const r = (Math.random() * 16) | 0;
230
+ const v = c === "x" ? r : (r & 0x3) | 0x8;
231
+ return v.toString(16);
232
+ },
233
+ );
234
+ }
235
+ /**
236
+ * 打开websocket码流链路
237
+ **/
238
+ openStream() {
239
+ const uid = this.uuid();
240
+ let url =
241
+ this.ws +
242
+ "://" +
243
+ this.host +
244
+ ":" +
245
+ this.port +
246
+ this.prefix +
247
+ "/" +
248
+ this.phone +
249
+ "-" +
250
+ this.channel +
251
+ `?user_id=${this.userId}&tenant_id=${this.tenantId}&access_token=${this.token}&host_uuid=${uid}`;
252
+ this.websocket = new WebSocket(url);
253
+ this.websocket.binaryType = "arraybuffer";
254
+
255
+ this.websocket.onopen = function () {
256
+ self.decoder.onOpen();
257
+ };
258
+
259
+ this.websocket.onmessage = function (ev) {
260
+ if (typeof ev.data === "string") {
261
+ let objData = {
262
+ t: kStringFrame,
263
+ d: ev.data,
264
+ };
265
+ self.postMessage(objData);
266
+ } else {
267
+ self.decoder.sendData(ev.data);
268
+ }
269
+ };
270
+
271
+ this.websocket.onerror = function () {
272
+ let message = "1连接视频服务器失败";
273
+ switch (self.decoder.lang) {
274
+ case "zh-CN,zh;":
275
+ message = "1连接视频服务器失败";
276
+ break;
277
+ case "zh-TW,zh;":
278
+ message = "1連接視訊伺服器失敗";
279
+ break;
280
+ case "en-US,en;":
281
+ message = "1Failed to connect to video server";
282
+ break;
283
+ case "ru-RU,ru;":
284
+ message = "1Ошибка подключения к видео серверу";
285
+ break;
286
+ case "kk-KZ,kk;":
287
+ message = "1Видео серверіне қосылу жаңылысы";
288
+ break;
289
+ }
290
+ let objData = {
291
+ t: kSocketErrorRsp,
292
+ d: message,
293
+ };
294
+ self.postMessage(objData);
295
+ // 注意:此处 this 指向 WebSocket 自身,须通过 self.decoder 操作
296
+ self.decoder.websocket.close();
297
+ self.decoder.websocket = null;
298
+ };
299
+
300
+ this.websocket.onclose = function () {
301
+ self.decoder.stopCheckCache();
302
+ let message = "1网络连接断开,请稍候再试";
303
+
304
+ switch (self.decoder.lang) {
305
+ case "zh-CN,zh;":
306
+ message = "1网络连接断开,请稍候再试";
307
+ break;
308
+ case "zh-TW,zh;":
309
+ message = "1網絡連接斷開,請稍候再試";
310
+ break;
311
+ case "en-US,en;":
312
+ message = "1Disconnected, please try again later";
313
+ break;
314
+ case "ru-RU,ru;":
315
+ message = "1Соединение прервано, повторите попытку позже.";
316
+ break;
317
+ case "kk-KZ,kk;":
318
+ message = "1Байланыс ажыратылды, әрекетті кейінірек қайталаңыз.";
319
+ break;
320
+ case "th-TH,th;":
321
+ message = "การเชื่อมต่อถูกตัดการเชื่อมต่อ โปรดลองอีกครั้งในภายหลัง";
322
+ break;
323
+ }
324
+ let objData = {
325
+ t: kSocketCloseRsp,
326
+ d: message,
327
+ };
328
+ if (self.decoder.serverError != "") {
329
+ // 用后端返回的提示(若有)
330
+ objData.d = self.decoder.serverError;
331
+ }
332
+ self.decoder.websocket = null;
333
+ self.decoder.hasConnected = false;
334
+ self.decoder.mnWorker = null;
335
+ self.postMessage(objData);
336
+ this.websocket = null;
337
+ };
338
+ }
339
+
340
+ /**
341
+ * 切换码流
342
+ **/
343
+ switchStream(mediaType, streamType) {
344
+ if (this.websocket != null && this.hasConnected) {
345
+ let req = {};
346
+ req.phone = this.phone;
347
+ req.channel = this.channel;
348
+ req.messageId = 0x9102;
349
+ req.command = 1;
350
+ req.closeType = 0;
351
+
352
+ if (mediaType != null) {
353
+ req.mediaType = mediaType;
354
+ this.mediaType = mediaType;
355
+ }
356
+
357
+ if (streamType != null) {
358
+ req.streamType = streamType;
359
+ this.streamType = streamType;
360
+ }
361
+
362
+ let json = JSON.stringify(req);
363
+ this.websocket.send(json);
364
+ } else {
365
+ this.logger.logInfo("Web socket is null or not connected.");
366
+ }
367
+ }
368
+
369
+ /**
370
+ * 发送制命令
371
+ **/
372
+ play() {
373
+ if (this.websocket == null) {
374
+ this.logger.logInfo("Web socket is null");
375
+ return;
376
+ }
377
+ let req = {};
378
+ req.phone = this.phone;
379
+ req.channel = this.channel;
380
+ req.messageId = this.messageId;
381
+ req.mediaType = this.mediaType;
382
+ req.streamType = this.streamType;
383
+ req.beginTime = this.beginTime;
384
+ this.playMode = this.beginTime ? 1 : 0;
385
+ req.acceptLanguage = this.lang;
386
+ req.ws = this.ws;
387
+ req.host = this.host;
388
+ req.port = this.port;
389
+ req.deviceType = this.deviceType;
390
+ let json = JSON.stringify(req);
391
+ this.websocket.send(json);
392
+ }
393
+
394
+ /**
395
+ * ws建立后初始化
396
+ **/
397
+ onOpen() {
398
+ this.baseInit();
399
+ this.resume();
400
+ this.startCheckCache();
401
+ this.play();
402
+ this.hasConnected = true;
403
+ if (this.mediaType !== kMediaSpeak) {
404
+ this.netSpeedTimer = setInterval(
405
+ this.calNetSpeed.bind(this),
406
+ this.netSpeedTime * 1000,
407
+ );
408
+ }
409
+ }
410
+
411
+ /**
412
+ * 恢复解码
413
+ **/
414
+ resume() {
415
+ // console.log('resume解码')
416
+ this.isPause = false;
417
+ if (this.mediaType !== kMediaSpeak) {
418
+ if (this.beginTime) {
419
+ this.playBack();
420
+ }
421
+ }
422
+ this.decoding = true;
423
+ }
424
+
425
+ /**
426
+ * 暂停解码
427
+ **/
428
+ pause() {
429
+ this.isPause = true;
430
+ if (!this.beginTime) {
431
+ this.decoding = false;
432
+ clearTimeout(this.realPlayTimer);
433
+ this.realPlayTimer = null;
434
+ this.cacheReqQue.length = 0;
435
+ this.hasInited = false;
436
+ this.hasCached = false;
437
+ } else {
438
+ this.pauseTime = new Date().getTime();
439
+ // console.log("暂停");
440
+ // this.decoding = false;
441
+ clearTimeout(this.playBackTimer);
442
+ }
443
+ }
444
+
445
+ realPlay(u8a, frameType, ptRaw) {
446
+ if (this.hasInited == false) {
447
+ // 获取编码类型,初始化视频解码器
448
+ if (this.renderMode == 1) {
449
+ if (u8a) {
450
+ const ti = this.timeStamp(u8a);
451
+ // console.log("收到帧时间", ti);
452
+ let dataString = "";
453
+ let dataLen = u8a.length - 16;
454
+ for (let i = 0; i < 16; i++) {
455
+ dataString += String.fromCharCode(u8a[dataLen + i]);
456
+ }
457
+ let frameType = Number(dataString.substr(13, 1));
458
+ let isKey = false;
459
+ if (frameType == 0) {
460
+ isKey = true;
461
+ }
462
+ self.postMessage({
463
+ t: kVideoFrameOriginRsp,
464
+ ti: ti,
465
+ isKey: isKey,
466
+ d: u8a,
467
+ });
468
+ }
469
+ } else {
470
+ if (!this.decoderInited) {
471
+ let decoderType = kDecoderH264;
472
+ if (ptRaw == 99) {
473
+ decoderType = kDecoderH265;
474
+ }
475
+ Module._videoInit(decoderType);
476
+ this.decoderInited = true;
477
+ }
478
+ // 获取帧类型,必须先出个I帧,否则丢弃
479
+ if (frameType == 0) {
480
+ this.hasInited = true;
481
+ }
482
+ }
483
+ }
484
+
485
+ if (this.decoding && this.hasInited) {
486
+ self.HEAPU8.set(u8a, this.cacheBuffer);
487
+ Module._decodeVideo(this.cacheBuffer, u8a.length - 16); // 同步解码,返回前回调已触发
488
+ this.preTimeStamp = this.timeStamp(u8a);
489
+ }
490
+ }
491
+ /* 检测缓存 */
492
+ startCheckCache() {
493
+ setInterval(() => {
494
+ clearInterval(this.checkCacheTimer);
495
+ this.checkCacheAndCtrl();
496
+ this.ctrlSendTime0 = 0;
497
+ this.ctrlSendTime1 = 0;
498
+ }, 3000);
499
+ }
500
+ stopCheckCache() {
501
+ clearInterval(this.checkCacheTimer);
502
+ }
503
+ /**
504
+ * 回放视频解码
505
+ * 时间控制统一交由主线程 mn-player 的 timerCtrl(实时/回放、wasm/webdecoder 一致),
506
+ * 此处仅按缓存顺序取帧并解码/透传,不再做 sleep/跳帧等节奏控制
507
+ **/
508
+ async playBack() {
509
+ try {
510
+ clearTimeout(this.playBackTimer);
511
+ if (this.isPause) return;
512
+
513
+ do {
514
+ this.checkCacheAndCtrl();
515
+ let queLen = this.cacheDataQue.length;
516
+ if (queLen == 0) {
517
+ break;
518
+ }
519
+ // 首先渲染出I帧(wasm 模式需要先初始化解码器)
520
+ if (this.hasInited === false && this.renderMode == 0) {
521
+ let u8a = this.cacheDataQue.shift();
522
+
523
+ // 取出帧尾16个字符
524
+ let dataString = "";
525
+ let dataLen = u8a.length - 16;
526
+ for (let i = 0; i < 16; i++) {
527
+ dataString += String.fromCharCode(u8a[dataLen + i]);
528
+ }
529
+
530
+ // 获取时间戳
531
+ this.preTimeStamp = Number(dataString.substr(0, 13));
532
+ // 获取编码类型,初始化视频解码器
533
+ if (!this.decoderInited) {
534
+ let decoderType = kDecoderH264;
535
+ if (dataString.substr(14) == 99) {
536
+ decoderType = kDecoderH265;
537
+ }
538
+ Module._videoInit(decoderType);
539
+ this.decoderInited = true;
540
+ }
541
+
542
+ // 获取帧类型,若是I帧,则立即解码
543
+ let frameType = Number(dataString.substr(13, 1));
544
+ if (this.decoding && frameType == 0) {
545
+ self.HEAPU8.set(u8a, this.cacheBuffer);
546
+ Module._decodeVideo(this.cacheBuffer, dataLen); // 同步解码,返回前回调已触发
547
+ this.hasInited = true;
548
+ }
549
+ break;
550
+ }
551
+
552
+ let u8aHead = this.cacheDataQue.shift();
553
+ let headTimeStamp = this.timeStamp(u8aHead);
554
+
555
+ if (this.renderMode == 1) {
556
+ let u8a = u8aHead;
557
+ let dataString = "";
558
+ let dataLen = u8a.length - 16;
559
+ for (let i = 0; i < 16; i++) {
560
+ dataString += String.fromCharCode(u8a[dataLen + i]);
561
+ }
562
+ let frameType = Number(dataString.substr(13, 1));
563
+ let isKey = false;
564
+ if (frameType == 0) {
565
+ isKey = true;
566
+ // console.log("this.timeStamp(u8a)", this.timeStamp(u8a));
567
+ }
568
+ self.postMessage({
569
+ t: kVideoFrameOriginRsp,
570
+ ti: this.timeStamp(u8a),
571
+ isKey: isKey,
572
+ d: u8a,
573
+ });
574
+ } else if (this.decoding) {
575
+ self.HEAPU8.set(u8aHead, this.cacheBuffer);
576
+ Module._decodeVideo(this.cacheBuffer, u8aHead.length - 16); // 同步解码,返回前回调已触发
577
+ }
578
+ this.preTimeStamp = headTimeStamp;
579
+ } while (false);
580
+ this.playBackTimer = setTimeout(this.playBack.bind(this), 4);
581
+ } catch (e) {
582
+ this.playBackTimer = setTimeout(this.playBack.bind(this), 4);
583
+ }
584
+ }
585
+
586
+ dateFormat(fmt, date) {
587
+ let ret;
588
+ const opt = {
589
+ "Y+": date.getFullYear().toString(), // 年
590
+ "m+": (date.getMonth() + 1).toString(), // 月
591
+ "d+": date.getDate().toString(), // 日
592
+ "H+": date.getHours().toString(), // 时
593
+ "M+": date.getMinutes().toString(), // 分
594
+ "S+": date.getSeconds().toString(), // 秒
595
+ // 有其他格式化字符需求可以继续添加,必须转化成字符串
596
+ };
597
+ for (let k in opt) {
598
+ ret = new RegExp("(" + k + ")").exec(fmt);
599
+ if (ret) {
600
+ fmt = fmt.replace(
601
+ ret[1],
602
+ ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0"),
603
+ );
604
+ }
605
+ }
606
+ return fmt;
607
+ }
608
+
609
+ /**
610
+ * 历史回放控制
611
+ **/
612
+ playBackCtrl(pause) {
613
+ // 兜底添加一个节流
614
+ if (new Date().getTime() - this.streamPauseTime < 1000) {
615
+ return;
616
+ }
617
+ // console.log("下发回放控制命令", pause, this.cacheDataQue.length);
618
+ this.streamPauseTime = new Date().getTime();
619
+ if (pause) {
620
+ this.isStreamPause = true;
621
+ if (this.playbackMode == 1) {
622
+ this.ctrlSendTime1++;
623
+ /* 已经发送过了 正常不再发送防止消息丢失 重发机制 */
624
+ if(this.ctrlSendTime1 > 2){
625
+ return
626
+ }
627
+ }
628
+ this.ctrlSendTime0 = 0;
629
+ this.playbackMode = 1;
630
+ } else {
631
+ this.isStreamPause = false;
632
+ if (this.playbackMode == 0) {
633
+ this.ctrlSendTime0++;
634
+ if(this.ctrlSendTime0 > 2){
635
+ return
636
+ }
637
+ }
638
+ this.ctrlSendTime1 = 0;
639
+ this.playbackMode = 0;
640
+ }
641
+
642
+ let req = {};
643
+ req.phone = this.phone;
644
+ req.channel = this.channel;
645
+ req.messageId = 0x9202;
646
+ req.mediaType = this.mediaType;
647
+ req.streamType = this.streamType;
648
+ req.beginTime = this.beginTime;
649
+ req.acceptLanguage = this.lang;
650
+ req.playbackMode = this.playbackMode;
651
+ let json = JSON.stringify(req);
652
+
653
+ if (this.websocket != null) {
654
+ if (this.websocket.readyState === this.websocket.OPEN) {
655
+ this.websocket.send(json);
656
+ } else {
657
+ this.websocket = null;
658
+ }
659
+ }
660
+ }
661
+
662
+ /**
663
+ * 获取时间戳
664
+ **/
665
+ timeStamp(u8a) {
666
+ let dataString = "";
667
+ let dataLen = u8a.length - 16;
668
+
669
+ for (let i = 0; i < 16; i++) {
670
+ dataString += String.fromCharCode(u8a[dataLen + i]);
671
+ }
672
+
673
+ return Number(dataString.substr(0, 13));
674
+ }
675
+
676
+ /**
677
+ * 获取数组中最后一个I帧的索引
678
+ **/
679
+ frameIndex() {
680
+ let index = -1;
681
+ for (let i = 0; i < this.cacheDataQue.length; i++) {
682
+ let u8a = this.cacheDataQue[i];
683
+ let dataLen = u8a.length - 16;
684
+ if (u8a[dataLen + 13] == 48) {
685
+ // 48 <=> '0'
686
+ index = i;
687
+ }
688
+ }
689
+ return index;
690
+ }
691
+
692
+ /**
693
+ * 往码流链路发音频控制命令
694
+ **/
695
+ enableAudio(d) {
696
+ this.playAudio = d;
697
+ /* if (this.websocket != null) {
698
+ let req = {};
699
+ req.phone = this.phone;
700
+ req.channel = this.channel;
701
+ if (d) {
702
+ req.messageId = 0x9601;
703
+ } else {
704
+ req.messageId = 0x9600;
705
+ }
706
+ let json = JSON.stringify(req);
707
+ this.websocket.send(json);
708
+ } else {
709
+ this.logger.logInfo("Web socket is null");
710
+ }*/
711
+ }
712
+
713
+ /**
714
+ * 关闭websocket码流链路
715
+ **/
716
+ closeStream() {
717
+ this.pause();
718
+
719
+ if (this.websocket != null) {
720
+ this.websocket.onmessage = null;
721
+ this.websocket.onerror = null;
722
+ this.websocket.onopen = null;
723
+ this.websocket.close();
724
+ this.websocket = null;
725
+ }
726
+
727
+ if (this.netSpeedTimer != null) {
728
+ clearInterval(this.netSpeedTimer);
729
+ this.netSpeedTimer = null;
730
+ }
731
+
732
+ this.baseUnInit();
733
+ }
734
+
735
+ /**
736
+ * 码流信息解码
737
+ **/
738
+ sendData(data) {
739
+ if (!this.decoding) {
740
+ return;
741
+ }
742
+ let u8a = new Uint8Array(data);
743
+ this.netSpeedSize += u8a.length;
744
+ //u8a = u8a.subarray(26, 114); // 去掉rtp头,可用于对讲测试
745
+ let dataString = "";
746
+ let dataLen = u8a.length - 16;
747
+ for (let i = 0; i < 16; i++) {
748
+ dataString += String.fromCharCode(u8a[dataLen + i]);
749
+ }
750
+
751
+ let frameType = Number(dataString.substr(13, 1));
752
+ let ptRaw = dataString.substr(14);
753
+ if (
754
+ u8a[0] == 0 &&
755
+ u8a[1] == 0 &&
756
+ u8a[2] == 0 &&
757
+ u8a[3] == 1 &&
758
+ (ptRaw == 98 || ptRaw == 99)
759
+ ) {
760
+ if (this.beginTime) {
761
+ // 回放,走流控
762
+ this.cacheDataQue.push(u8a);
763
+ this.checkCacheAndCtrl();
764
+ } else {
765
+ this.realPlay(u8a, frameType, ptRaw); // 实时,直接解码
766
+ }
767
+ } else {
768
+ // if (this.mediaType !== kMediaSpeak && !this.playAudio) {
769
+ // // 监听时,按声音开关控制播放音频
770
+ // return;
771
+ // }
772
+ if (!this.voiceScheduling) {
773
+ // 语音调度不播放上行音频
774
+ // 最后一字节为音频编码类型
775
+ if(u8a.length<100)return
776
+ if (dataString.indexOf("306") > 0) {
777
+ u8a = u8a.slice(0, -15);
778
+ u8a[u8a.length - 1] = 6;
779
+ const nowAudioTime = dataString.slice(0, 13);
780
+ if (
781
+ this.voiceTimeStamp !== 0 &&
782
+ Math.abs(nowAudioTime - this.voiceTimeStamp) > 1000
783
+ ) {
784
+ /* 如果音频跨段主动flush一次 */
785
+ let objData = {
786
+ t: kFlushAudio,
787
+ d: this.voiceTimeStamp,
788
+ };
789
+ self.postMessage(objData);
790
+ }
791
+ this.voiceTimeStamp = nowAudioTime;
792
+ }
793
+ // console.log('dataString',dataString)
794
+ self.HEAPU8.set(u8a, this.cacheBuffer);
795
+ Module._decodeAudio(
796
+ this.cacheBuffer,
797
+ u8a.length - 1,
798
+ u8a[u8a.length - 1],
799
+ );
800
+ }
801
+ }
802
+ }
803
+
804
+ checkCacheAndCtrl() {
805
+ if (this.playMode == 0) {
806
+ return;
807
+ }
808
+ let min = 10;
809
+ let max = 30;
810
+ if (this.playbackMode == 6) {
811
+ // 帧播放
812
+ min = 1;
813
+ max = 5;
814
+ }
815
+ let queLen = this.cacheDataQue.length;
816
+ if (queLen == 0 && !this.isFirstLoaded) {
817
+ this.isCacheLow = true;
818
+ // console.log("缓存不足提示");
819
+ let objData = {
820
+ t: kNoCache,
821
+ len: queLen,
822
+ };
823
+ self.postMessage(objData);
824
+ }
825
+ if (queLen > 0 && !this.isFirstLoaded) {
826
+ this.isFirstLoaded = true;
827
+ this.isCacheLow = false;
828
+ // console.log("缓存恢复提示");
829
+ let objData = {
830
+ t: kCacheAvailable,
831
+ len: queLen,
832
+ };
833
+ self.postMessage(objData);
834
+ }
835
+ if (this.isCacheLow && queLen >= min * 10) {
836
+ this.isCacheLow = false;
837
+ // console.log("缓存恢复提示");
838
+ let objData = {
839
+ t: kCacheAvailable,
840
+ len: queLen,
841
+ };
842
+ self.postMessage(objData);
843
+ }
844
+ if (queLen < min * 25) {
845
+ // 恢复终端码流传输
846
+ this.playBackCtrl(false);
847
+ } else if (queLen > max * 25) {
848
+ // 暂停终端码流传输
849
+ this.playBackCtrl(true);
850
+ }
851
+ }
852
+
853
+ /**
854
+ * 处理来自MnPlayer的命令
855
+ **/
856
+ processReq(req) {
857
+ switch (req.t) {
858
+ case kVideoAudioOffset:
859
+ this.offset(req.data);
860
+ break;
861
+ case kOpenDecoderReq:
862
+ this.ws = req.ws;
863
+ this.host = req.host;
864
+ this.port = req.port;
865
+ this.phone = req.phone;
866
+ ((this.userId = req.userId),
867
+ (this.tenantId = req.tenantId),
868
+ (this.token = req.token),
869
+ (this.channel = req.channel));
870
+ this.lang = req.lang;
871
+ this.deviceType = req.deviceType;
872
+ this.renderMode = req.renderMode;
873
+ break;
874
+ case kChangeRenderTypeReq:
875
+ this.renderMode = req.renderMode;
876
+ break;
877
+ case kResumeDecodingReq:
878
+ this.resume();
879
+ break;
880
+ case kPauseDecodingReq:
881
+ this.pause();
882
+ break;
883
+ case kOpenStreamReq:
884
+ this.messageId = req.messageId;
885
+ this.mediaType = req.mediaType;
886
+ this.streamType = req.streamType;
887
+ this.beginTime = req.beginTime;
888
+ this.lang = req.lang;
889
+ this.openStream();
890
+ break;
891
+ case kEnableAudioReq:
892
+ this.enableAudio(req.d);
893
+ break;
894
+ case kSwitchStreamReq:
895
+ this.switchStream(req.m, req.s);
896
+ break;
897
+ case kRecorderSpeakReq:
898
+ this.voiceScheduling = true;
899
+ this.encodeAudio(req.d);
900
+ break;
901
+ case kRecorderTalkReq:
902
+ this.voiceScheduling = false;
903
+ this.encodeAudio(req.d);
904
+ break;
905
+ case kCloseStreamReq:
906
+ this.closeStream();
907
+ break;
908
+ case kFastForward:
909
+ this.fastForward = req.d;
910
+ break;
911
+ case kReplayReq:
912
+ this.play();
913
+ break;
914
+ case kReplaySpeedReq:
915
+ let beginTime = this.beginTime;
916
+ this.playbackMode = req.playbackMode;
917
+ if (req.time) {
918
+ beginTime = req.time;
919
+ } else if (this.preTimeStamp) {
920
+ let t = new Date(this.preTimeStamp);
921
+ beginTime = `${t.getUTCFullYear()}-${(t.getUTCMonth() + 1)
922
+ .toString()
923
+ .padStart(2, "0")}-${t.getUTCDate().toString().padStart(2, "0")} ${t
924
+ .getUTCHours()
925
+ .toString()
926
+ .padStart(2, "0")}:${t
927
+ .getUTCMinutes()
928
+ .toString()
929
+ .padStart(
930
+ 2,
931
+ "0",
932
+ )}:${t.getUTCSeconds().toString().padStart(2, "0")}`;
933
+ }
934
+
935
+ this.websocket.send(
936
+ JSON.stringify({
937
+ playbackSpeed: req.d,
938
+ messageId: 0x9202,
939
+ channel: this.channel,
940
+ phone: this.phone,
941
+ deviceType: this.deviceType,
942
+ streamType: this.streamType,
943
+ mediaType: this.mediaType,
944
+ host: this.host,
945
+ hostUuid: this.host_uuid,
946
+ port: this.port,
947
+ ws: this.ws,
948
+ beginTime,
949
+ acceptLanguage: this.lang,
950
+ playbackMode: this.playbackMode,
951
+ }),
952
+ );
953
+ this.speed = req.v;
954
+ this.speedValue = req.d;
955
+ break;
956
+ default:
957
+ this.logger.logError("Unsupport messsage " + req.t);
958
+ }
959
+ }
960
+
961
+ /**
962
+ * 录音编码(对讲)
963
+ **/
964
+ //判断设备 6 等于QM003 26 = 磁北
965
+ encodeAudio(data) {
966
+ if (this.websocket != null) {
967
+ self.HEAP8.set(data, this.speakBuffer);
968
+ Module._encodeAudio(
969
+ this.speakBuffer,
970
+ data.length,
971
+ this.deviceType == "6" ? 6 : 26,
972
+ );
973
+ }
974
+ }
975
+
976
+ /**
977
+ * 字符串转int8Array
978
+ **/
979
+ toInt8Array(str) {
980
+ let i8a = [];
981
+ for (let i = 0; i < str.length; i++) {
982
+ i8a.push(str.charCodeAt(i));
983
+ }
984
+ return new Int8Array(i8a);
985
+ }
986
+
987
+ /**
988
+ * 缓存来自MnPlayer的命令
989
+ **/
990
+ cacheReq(req) {
991
+ if (req) {
992
+ this.cacheReqQue.push(req);
993
+ }
994
+ }
995
+
996
+ /**
997
+ * WASM初始化
998
+ **/
999
+ onWasmLoaded() {
1000
+ this.wasmLoaded = true;
1001
+ this.readyCallback = Module.addFunction(function (width, height) {
1002
+ let objData = {
1003
+ t: kVideoReady,
1004
+ w: width,
1005
+ h: height,
1006
+ };
1007
+ self.postMessage(objData);
1008
+ }, "vii");
1009
+ let that = this;
1010
+ this.videoCallback = Module.addFunction(function (buff, size) {
1011
+ // 解码耗时已在调用侧用 token 精确计时(见 _decodeVideo 调用点),此处不再配对
1012
+ // memory64 构建:buff 是 i64 指针(BigInt),转 Number 后再访问 HEAP
1013
+ let outArray = self.HEAPU8.subarray(Number(buff), Number(buff) + size);
1014
+ let data = new Uint8Array(outArray);
1015
+ let objData = {
1016
+ t: kVideoFrame,
1017
+ d: data,
1018
+ ti: that.preTimeStamp,
1019
+ };
1020
+ self.postMessage(objData, [objData.d.buffer]);
1021
+ }, "vji");
1022
+ this.audioCallback = Module.addFunction(function (buff, size) {
1023
+ let outArray = self.HEAPU8.subarray(Number(buff), Number(buff) + size);
1024
+ let data = new Uint8Array(outArray);
1025
+ let objData = {
1026
+ t: kAudioFrame,
1027
+ d: data,
1028
+ ti: that.voiceTimeStamp,
1029
+ };
1030
+ self.postMessage(objData, [objData.d.buffer]);
1031
+ }, "vji");
1032
+
1033
+ this.speakCallback = Module.addFunction(function (buff, size) {
1034
+ let outArray = self.HEAPU8.subarray(Number(buff), Number(buff) + size);
1035
+ let data = new Int8Array(outArray);
1036
+ if (
1037
+ self.decoder.websocket !== null &&
1038
+ self.decoder.websocket.readyState === self.decoder.websocket.OPEN
1039
+ ) {
1040
+ self.decoder.websocket.send(data);
1041
+ }
1042
+ }, "vji");
1043
+
1044
+ // 执行缓存中的命令
1045
+ while (this.cacheReqQue.length > 0) {
1046
+ let req = this.cacheReqQue.shift();
1047
+ this.processReq(req);
1048
+ }
1049
+ }
1050
+ }
1051
+
1052
+ // 仅主 worker 实例化 MnWorker;em-pthread 子线程必须保留 mn-media.js 的 handleMessage
1053
+ // (若在此覆盖 self.onmessage,pthread 握手 cmd 1 永远得不到 cmd 3 回复,主 worker 卡在加载中)
1054
+ if (!isPthreadWorker) {
1055
+ self.decoder = new MnWorker();
1056
+
1057
+ /**
1058
+ * 接收主线程消息
1059
+ **/
1060
+ self.onmessage = function (evt) {
1061
+ // console.log("接收主线程消息", evt, self.decoder);
1062
+ if (!self.decoder) {
1063
+ console.log("[ER] MnWorker not initialized!");
1064
+ return;
1065
+ }
1066
+
1067
+ let req = evt.data;
1068
+ if (!self.decoder.wasmLoaded) {
1069
+ // 若尚未初始化,则缓存该命令
1070
+ // console.log("缓存命令", req);
1071
+ self.decoder.cacheReq(req);
1072
+ return;
1073
+ }
1074
+ // console.log("接收主线程消息", req);
1075
+ self.decoder.processReq(req);
1076
+ };
1077
+ }
1078
+
1079
+ // 每 5 秒自动打印解码性能汇总(仅主 worker;有新增样本才输出,避免空闲刷屏)
1080
+
1081
+
1082
+ /**
1083
+ * memory64(wasm64)构建适配:新版 wasm 使用 64 位指针,SDK 导出的
1084
+ * 指针/函数指针参数在 JS 侧调用时必须传 BigInt。
1085
+ * 胶水的 applySignatureConversions 只包装了内部函数(_malloc/_free 等),
1086
+ * 没有包装 _baseInit/_decodeVideo/_decodeAudio/_encodeAudio,这里补上。
1087
+ * 对应 wasm 签名:_baseInit(i64,i32,i64,i64,i64,i64,i32)、
1088
+ * _decodeVideo(i64,i64)、_decodeAudio(i64,i64,i32)、_encodeAudio(i64,i64,i32)。
1089
+ **/
1090
+ function wrapSdkBigIntExports() {
1091
+ const rawBaseInit = Module._baseInit;
1092
+ const rawDecodeVideo = Module._decodeVideo;
1093
+ const rawDecodeAudio = Module._decodeAudio;
1094
+ const rawEncodeAudio = Module._encodeAudio;
1095
+ Module._baseInit = (phone, channel, readyCb, videoCb, audioCb, speakCb, logLevel) =>
1096
+ rawBaseInit(
1097
+ BigInt(phone),
1098
+ channel,
1099
+ BigInt(readyCb),
1100
+ BigInt(videoCb),
1101
+ BigInt(audioCb),
1102
+ BigInt(speakCb),
1103
+ logLevel,
1104
+ );
1105
+ Module._decodeVideo = (buf, len) => rawDecodeVideo(BigInt(buf), BigInt(len));
1106
+ Module._decodeAudio = (buf, len, type) => rawDecodeAudio(BigInt(buf), BigInt(len), type);
1107
+ Module._encodeAudio = (buf, len, type) => rawEncodeAudio(BigInt(buf), BigInt(len), type);
1108
+ }
1109
+
1110
+ /**
1111
+ * 初始化
1112
+ **/
1113
+ function onWasmLoaded() {
1114
+ if (self.decoder) {
1115
+ // console.log("WASM加载完成");
1116
+ self.decoder.onWasmLoaded();
1117
+ } else {
1118
+ console.log("[ER] No decoder!");
1119
+ }
1120
+ }