@stampgis/webrtc 1.0.0

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,561 @@
1
+ /* eslint-disable no-undef */
2
+ let initTimeout = null;
3
+ let initialSettings = {
4
+ //ss: `ws://${streming_url}`,
5
+ // StreamerId: "DefaultStreamer", // 允许您选择要串流的主播。
6
+ AutoPlayVideo: true, // 当流准备就绪时,立即开始播放视频,而不是显示播放按钮。
7
+ AutoConnect: true, // 加载时浏览器将自动连接到流。避免必须单击才能开始
8
+ OfferToReceive: false, // 浏览器将启动 WebRTC 握手,而不是虚幻引擎应用程序。这是用户自定义前端的高级设置。主要是为了向后兼容 4.x 版本的引擎。
9
+ UseMic: false, // 将开始从麦克风接收音频输入并将其传输到虚幻引擎。
10
+ StartVideoMuted: true, // 流开始时音频静音。
11
+ HoveringMouse: true,
12
+ MatchViewportRes: true, // 调整虚幻引擎应用程序分辨率的大小以匹配浏览器的视频元素大小。
13
+ preferSFU: false, // 将尝试使用选择性转发单元 (SFU)(如果您正在运行)。
14
+ ControlsQuality: true, // 使像素流插件的编码器使用当前浏览器连接来确定可用带宽,从而确定流编码的质量。
15
+ ForceMonoAudio: false, // 强制浏览器在 SDP 中请求单声道音频。
16
+ ForceTURN: false, // 将尝试通过 TURN 服务器进行独占连接。如果没有活动的 TURN 服务器,则无法工作。
17
+ SuppressBrowserKeys: true, // 禁止或允许我们在 UE 中使用的某些键,例如 F5 以显示着色器复杂性而不是刷新页面。
18
+ TimeoutIfIdle: false, // 如果一段时间内没有检测到输入,则连接超时。
19
+ AFKTimeout: 180, // 允许您指定 AFK 超时时间。
20
+ MaxReconnectAttempts: 3, // 当流式传输器断开连接时应用程序将尝试重新连接的最大次数。
21
+ FakeMouseWithTouches: false,
22
+ MinQP: 0, // 编码器量化参数 (QP) 的下限。0 = 最佳质量,51 = 最差质量。
23
+ MaxQP: 51, // 编码器量化参数(QP)的上限。0 = 最佳质量,51 = 最差质量。
24
+ // PreferredCodec: "H264 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", // 编解码器协商期间要使用的首选编解码器。
25
+ KeyboardInput: false, // 如果启用,捕获键盘事件并将其发送到虚幻引擎应用程序。
26
+ MouseInput: true, // 如果启用,捕获鼠标事件并将其发送到虚幻引擎应用程序。
27
+ TouchInput: true, // 如果启用,则会捕获触摸事件并将其发送到虚幻引擎应用程序。
28
+ GamepadInput: false, // 如果启用,则会捕获游戏手柄事件并将其发送到虚幻引擎应用程序。
29
+ XRControllerInput: false, // 如果启用,则会捕获 XR 控制器事件并将其发送到虚幻引擎应用程序。
30
+ WebRTCFPS: 60, // WebRTC 将尝试传输帧的最大 FPS。
31
+ WebRTCMaxBitrate: 100000, // WebRTC 应使用的最大比特率。
32
+ WebRTCMinBitrate: 100, // WebRTC 应使用的最低比特率。
33
+ StreamerAutoJoinInterval: 500, //单位毫秒,如果网络情况不太好该值可以适当放大
34
+ };
35
+ var StampAPI = {
36
+ LibAx: new StampPixelStreaming.StampCoreApi(),
37
+ isLoadingMap: false,
38
+ isClickToStart: false, //点击连接
39
+ showTips: true, //默认显示提示信息
40
+ ip: "",
41
+ port: "",
42
+ //地图对象
43
+ lib_ax: null, //地图对象
44
+
45
+ //双屏的第二个地图
46
+ lib_ax1: null,
47
+ //三屏的第三个地图
48
+ lib_ax2: null,
49
+ initEarth() {
50
+ this.LibAx = new StampPixelStreaming.StampCoreApi();
51
+ this.lib_ax = new StampPixelStreaming.lib_ax(StampAPI.LibAx);
52
+ this.lib_ax1 = new StampPixelStreaming.lib_ax(StampAPI.LibAx);
53
+ this.lib_ax2 = new StampPixelStreaming.lib_ax(StampAPI.LibAx);
54
+ },
55
+ initErrorDiv: function ({ earthDomElement, errorDomElement, iIndex }) {
56
+ StampAPI.isClickToStart = false;
57
+ errorDomElement &&
58
+ errorDomElement.addEventListener("click", function () {
59
+ if (StampAPI.isClickToStart) {
60
+ StampAPI.hiddenError(errorDomElement);
61
+ StampAPI.initVideoPlayer({
62
+ earthDomElement,
63
+ errorDomElement,
64
+ iIndex,
65
+ });
66
+ }
67
+ });
68
+ },
69
+ getStreamingUrl: function (ip, port) {
70
+ var streamingUrl = "";
71
+ if (window.location.protocol === "https:") {
72
+ // https网站 走代理
73
+ streamingUrl = `${window.location.origin
74
+ .replace("https://", "wss://")
75
+ .replace("http://", "ws://")}/stampwebsocket/?target=${ip}:${port}`;
76
+ } else {
77
+ // http网站
78
+ streamingUrl = `ws://${ip}:${port}`;
79
+ }
80
+ return streamingUrl;
81
+ },
82
+ /**
83
+ * 初始化
84
+ */
85
+ initVideoPlayer: ({
86
+ earthDomElement,
87
+ errorDomElement,
88
+ iIndex = 0,
89
+ showTips = true,
90
+ }) => {
91
+ var earth = null;
92
+ switch (iIndex) {
93
+ case 0:
94
+ earth = StampAPI.lib_ax;
95
+ break;
96
+ case 1:
97
+ earth = StampAPI.lib_ax1;
98
+ break;
99
+ case 2:
100
+ earth = StampAPI.lib_ax2;
101
+ break;
102
+ }
103
+ earth.earthDomElement = earthDomElement;
104
+ earth.errorDomElement = errorDomElement;
105
+ StampAPI.showTips = !!showTips;
106
+ if (!earthDomElement) {
107
+ return;
108
+ }
109
+ StampAPI.isLoadingMap = true;
110
+ initTimeout && clearTimeout(initTimeout);
111
+ initTimeout = null;
112
+ StampAPI.hiddenError(errorDomElement);
113
+ StampAPI.initErrorDiv({ earthDomElement, errorDomElement, iIndex });
114
+ return new Promise((resolve) => {
115
+ if (stamp_core_config.use_match_maker) {
116
+ if (iIndex == 0) {
117
+ if (!StampAPI.ip) {
118
+ StampAPI.ip = localStorage.getItem("serverip");
119
+ }
120
+ if (!StampAPI.port) {
121
+ StampAPI.port = localStorage.getItem("serverport");
122
+ }
123
+ if (StampAPI.ip == null) {
124
+ //无IP启动,则自动获取IP
125
+ StampAPI.getIPPort(
126
+ earthDomElement,
127
+ errorDomElement,
128
+ stamp_core_config.match_maker,
129
+ iIndex
130
+ ).then(() => {
131
+ StampAPI.hiddenError();
132
+ resolve();
133
+ });
134
+ } else {
135
+ var streamingUrl = StampAPI.getStreamingUrl(
136
+ StampAPI.ip,
137
+ StampAPI.port
138
+ );
139
+ StampAPI.playerControlInit(
140
+ earthDomElement,
141
+ streamingUrl,
142
+ iIndex
143
+ ).then((res) => {
144
+ StampAPI.hiddenError();
145
+ resolve(res);
146
+ });
147
+ }
148
+ } else {
149
+ StampAPI.getIPPort(
150
+ earthDomElement,
151
+ errorDomElement,
152
+ stamp_core_config.match_maker,
153
+ iIndex
154
+ ).then(() => {
155
+ StampAPI.hiddenError();
156
+ resolve();
157
+ });
158
+ }
159
+ } else {
160
+ //本地调试,无需调用matchmaker服务,直接给信令IP和端口
161
+ StampAPI.hiddenError(errorDomElement);
162
+ const streamingUrl = stamp_core_config.signal_streaming;
163
+ StampAPI.ip = streamingUrl
164
+ .replace("ws://", "")
165
+ .replace("wss://", "")
166
+ .split(":")[0];
167
+ StampAPI.port = streamingUrl
168
+ .replace("ws://", "")
169
+ .replace("wss://", "")
170
+ .split(":")[1];
171
+ StampAPI.playerControlInit(earthDomElement, streamingUrl, iIndex).then(
172
+ (res) => {
173
+ resolve();
174
+ }
175
+ );
176
+ }
177
+ });
178
+ },
179
+
180
+ /**
181
+ * 创建地图视频流
182
+ * @param {*} initialSettings
183
+ * @param {*} earthDomElement
184
+ * @param {*} earth
185
+ * @returns
186
+ */
187
+ createPlayer: (initialSettings, earthDomElement, earth) => {
188
+ return new StampPixelStreaming.player53(
189
+ initialSettings,
190
+ earthDomElement,
191
+ earth
192
+ );
193
+ },
194
+
195
+ /**
196
+ * 关闭视频流并清除地图对象
197
+ * @param {*} earth
198
+ */
199
+ closeVideoWithClear: (earth) => {
200
+ if (earth == undefined) {
201
+ earth = StampAPI.lib_ax;
202
+ }
203
+ StampAPI.closeVideo(earth);
204
+ StampAPI.clearVideo(earth);
205
+ StampAPI.closeExe();
206
+ },
207
+ closeExe: function () {
208
+ const xhr = new XMLHttpRequest();
209
+ if (!StampAPI.ip) {
210
+ StampAPI.ip = localStorage.getItem("serverip");
211
+ }
212
+ if (!StampAPI.port) {
213
+ StampAPI.port = localStorage.getItem("serverport");
214
+ }
215
+ var url = `${stamp_core_config.match_maker}/closeServer?ip=${StampAPI.ip}&port=${StampAPI.port}`;
216
+ xhr.open("get", url, false);
217
+ xhr.send();
218
+ },
219
+ /**
220
+ * 关闭视频流
221
+ * @param {*} earth
222
+ */
223
+ closeVideo: function (earth) {
224
+ earth?.player?.webRtcController?.closeSignalingServer();
225
+ },
226
+ /**
227
+ * 清除地图对象
228
+ * @param {*} earth
229
+ */
230
+ clearVideo: function (earth) {
231
+ if (earth?.player) {
232
+ earth.player.earth = undefined;
233
+ earth.player = undefined;
234
+ }
235
+ },
236
+ /**
237
+ * 显示错误信息
238
+ * @param {*} domElement
239
+ * @param {*} message
240
+ * @returns
241
+ */
242
+ showError: function (domElement, message) {
243
+ if (!domElement) {
244
+ return;
245
+ }
246
+
247
+ domElement.innerHTML = message;
248
+ StampAPI.showTips && (domElement.style.visibility = "visible");
249
+ },
250
+ /**
251
+ * 关闭错误信息
252
+ * @param {*} domElement
253
+ * @returns
254
+ */
255
+ hiddenError: function (domElement) {
256
+ if (!domElement) {
257
+ return;
258
+ }
259
+ domElement.innerHTML = "";
260
+ domElement.style.visibility = "hidden";
261
+ },
262
+
263
+ /**
264
+ * 负载均衡,取服务端IP端口
265
+ * @param {*} earthDomElement
266
+ * @param {*} matchmakerUrl
267
+ */
268
+ getIPPort: function (
269
+ earthDomElement,
270
+ errorDomElement,
271
+ matchmakerUrl,
272
+ iIndex
273
+ ) {
274
+ initTimeout && clearTimeout(initTimeout);
275
+ initTimeout = null;
276
+ return new Promise((resolve) => {
277
+ const xhr = new XMLHttpRequest();
278
+ xhr.open("get", matchmakerUrl, false);
279
+
280
+ //get token
281
+ let token = localStorage.getItem("token");
282
+ if (token == null) {
283
+ token = "";
284
+ }
285
+ xhr.setRequestHeader("x-access-token", token);
286
+
287
+ try {
288
+ xhr.send();
289
+ } catch (ex) {
290
+ StampAPI.showError(errorDomElement, "连接服务器失败");
291
+ initTimeout = setTimeout(function () {
292
+ StampAPI.getIPPort(
293
+ earthDomElement,
294
+ errorDomElement,
295
+ matchmakerUrl,
296
+ iIndex
297
+ ).then((res) => {
298
+ resolve(res);
299
+ });
300
+ }, 3000);
301
+ }
302
+ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
303
+ const res = JSON.parse(xhr.responseText);
304
+ if (res && res.success) {
305
+ //save token
306
+ token = xhr.getResponseHeader("x-access-token");
307
+ localStorage.setItem("token", token);
308
+ //成功连接上
309
+ StampAPI.hiddenError(errorDomElement);
310
+ const streamingUrl = StampAPI.getStreamingUrl(res.ip, res.port);
311
+ StampAPI.ip = res.ip;
312
+ StampAPI.port = res.port;
313
+ StampAPI.playerControlInit(
314
+ earthDomElement,
315
+ streamingUrl,
316
+ iIndex
317
+ ).then((res) => {
318
+ resolve(res);
319
+ });
320
+ } else {
321
+ if (res.serverCount > 0) {
322
+ StampAPI.showError(
323
+ errorDomElement,
324
+ res.serverCount + "台服务器资源全被占用或正在启动中"
325
+ );
326
+ } else {
327
+ StampAPI.showError(errorDomElement, "信令服务器未启动");
328
+ }
329
+
330
+ initTimeout = setTimeout(function () {
331
+ StampAPI.getIPPort(
332
+ earthDomElement,
333
+ errorDomElement,
334
+ matchmakerUrl,
335
+ iIndex
336
+ ).then((res) => {
337
+ resolve(res);
338
+ });
339
+ }, 3000);
340
+ }
341
+ } else {
342
+ StampAPI.showError(errorDomElement, "连接服务器失败");
343
+ initTimeout = setTimeout(function () {
344
+ StampAPI.getIPPort(
345
+ earthDomElement,
346
+ errorDomElement,
347
+ matchmakerUrl,
348
+ iIndex
349
+ ).then((res) => {
350
+ resolve(res);
351
+ });
352
+ }, 3000);
353
+ }
354
+ });
355
+ },
356
+
357
+ /**
358
+ * 初始化地图控件
359
+ * @param {*} earthDiv 地图标签ID
360
+ * @param {*} streamingUrl 信令服务器websocket地址
361
+ * @returns
362
+ */
363
+ playerControlInit: function (earthDivElement, streamingUrl, iIndex) {
364
+ return new Promise((resolve) => {
365
+ StampAPI.isLoadingMap = false;
366
+ var earth = null;
367
+ switch (iIndex) {
368
+ case 0:
369
+ earth = StampAPI.lib_ax;
370
+ break;
371
+ case 1:
372
+ earth = StampAPI.lib_ax1;
373
+ break;
374
+ case 2:
375
+ earth = StampAPI.lib_ax2;
376
+ break;
377
+ default:
378
+ earth = StampAPI.lib_ax;
379
+ }
380
+ if (earth.player == undefined) {
381
+ initialSettings.ss = streamingUrl;
382
+ if (stamp_core_config.bigscreen) {
383
+ initialSettings.PreferredCodec = "AV1 level-idx=5;profile=0;tier=0";
384
+ }
385
+
386
+ StampAPI.createPlayer(initialSettings, earthDivElement, earth);
387
+ earthDivElement.onresize = () => {
388
+ earth?.player?.webRtcController?.videoPlayer?.resizePlayerStyle();
389
+ earth?.player?.webRtcController?.videoPlayer?.updateVideoStreamSize();
390
+ };
391
+ } else {
392
+ earth.player.config.textParameters.get("ss")._value = streamingUrl;
393
+ earth.player.webRtcController.connectToSignallingServer();
394
+ }
395
+ earth?.player?.webRtcController?.videoPlayer?.updateVideoStreamSize();
396
+ earth?.player?.webRtcController?.videoPlayer?.resizePlayerStyle();
397
+ earth.player.webRtcController.videoPlayer.client_size_factor =
398
+ stamp_core_config.windowDevicePixelRatio || window.devicePixelRatio;
399
+ if (window.devicePixelRatio > 1) {
400
+ var scale = window.devicePixelRatio;
401
+ earth.environment_interface.touch_screen_scale(scale);
402
+ }
403
+
404
+ if (iIndex == 0) {
405
+ window.onkeydown = (e) => {
406
+ if (
407
+ e.target &&
408
+ (e.target.type == "text" || e.target.type == "textarea")
409
+ ) {
410
+ return;
411
+ }
412
+ const global_control_interface = earth?.global_control_interface;
413
+ if (!global_control_interface) {
414
+ return;
415
+ }
416
+ switch (e.keyCode) {
417
+ case 82: //R
418
+ global_control_interface.north_top_view();
419
+ break;
420
+ case 78: //N
421
+ global_control_interface.north_view();
422
+ break;
423
+ case 85: //U
424
+ global_control_interface.top_view();
425
+ break;
426
+ case 71: //G
427
+ global_control_interface.resume();
428
+ break;
429
+ case 37: //左
430
+ global_control_interface.move_left();
431
+ break;
432
+ case 38: //上
433
+ global_control_interface.forward();
434
+ break;
435
+ case 39: //右
436
+ global_control_interface.move_right();
437
+ break;
438
+ case 40: //下
439
+ global_control_interface.backward();
440
+ break;
441
+ case 81: //Q
442
+ global_control_interface.turn_left();
443
+ break;
444
+ case 69: //E
445
+ global_control_interface.turn_right();
446
+ break;
447
+ case 90: //Z
448
+ global_control_interface.look_up();
449
+ break;
450
+ case 67: //C
451
+ global_control_interface.look_down();
452
+ break;
453
+ case 187: //+
454
+ global_control_interface.zoom_in(0.33);
455
+ break;
456
+ case 189: //-
457
+ global_control_interface.zoom_out(-0.5);
458
+ break;
459
+ case 76: //L
460
+ global_control_interface.stop();
461
+ break;
462
+ default:
463
+ break;
464
+ }
465
+ };
466
+
467
+ window.onmousedown = (e) => {
468
+ if (
469
+ e.target.id == "streamingVideo" &&
470
+ e.which == 1 &&
471
+ earth.LibAx.Cursor.enable_globe_cursor
472
+ ) {
473
+ this.setMouseCursor(earth, 2);
474
+ }
475
+ };
476
+
477
+ window.onmouseup = (e) => {
478
+ if (
479
+ e.target.id == "streamingVideo" &&
480
+ e.which == 1 &&
481
+ earth.LibAx.Cursor.enable_globe_cursor
482
+ ) {
483
+ this.setMouseCursor(earth, 1);
484
+ } else if (e.target.id == "streamingVideo" && e.which == 3) {
485
+ this.setMouseCursor(earth, 0);
486
+ }
487
+ };
488
+ }
489
+ this.initPlayEvent(earth).then((res) => {
490
+ earth?.player?.webRtcController?.videoPlayer?.resizePlayerStyle();
491
+ earth?.player?.webRtcController?.videoPlayer?.updateVideoStreamSize();
492
+ resolve(res);
493
+ });
494
+ });
495
+ },
496
+
497
+ initPlayEvent: function (earth) {
498
+ return new Promise((resolve) => {
499
+ earth.player.streaming.addEventListener(
500
+ "webRtcDisconnected",
501
+ ({ data: { eventString, showActionOrErrorOnDisconnect } }) => {
502
+ this.onDisconnect(earth, eventString, showActionOrErrorOnDisconnect);
503
+ },
504
+ false
505
+ );
506
+ if (earth.onPlaying) {
507
+ //先清除
508
+ earth.player.webRtcController.videoPlayer.videoElement.removeEventListener(
509
+ "playing",
510
+ earth.onPlaying,
511
+ false
512
+ );
513
+ }
514
+ earth.onPlaying = () => {
515
+ this.onPlaying();
516
+ this.hiddenError(earth.errorDomElement);
517
+ this.setMouseCursor(earth, 0);
518
+ resolve("success"); //连接播放成功后回调
519
+ };
520
+ earth.player.webRtcController.videoPlayer.videoElement.addEventListener(
521
+ "playing",
522
+ earth.onPlaying,
523
+ false
524
+ );
525
+ });
526
+ },
527
+
528
+ setMouseCursor(earth, isFlag) {
529
+ switch (isFlag) {
530
+ case 1: //正常浏览,手爪松手状态
531
+ earth.LibAx.Cursor.normal = "url(cursor/1.cur),default";
532
+ earth.LibAx.Cursor.update();
533
+ break;
534
+ case 2: //拖拽:鼠标按下,手爪握拳状态
535
+ earth.LibAx.Cursor.normal = "url(cursor/2.cur),default";
536
+ earth.LibAx.Cursor.update();
537
+ break;
538
+ default: //正常状态
539
+ earth.LibAx.Cursor.normal = "url(cursor/1.cur),default";
540
+ earth.LibAx.Cursor.edit = "crosshair";
541
+ earth.LibAx.Cursor.reset();
542
+ break;
543
+ }
544
+ },
545
+ onDisconnect(earth) {
546
+ console.log("断开连接");
547
+ if (StampAPI.isLoadingMap) {
548
+ return;
549
+ }
550
+ this.isClickToStart = true;
551
+ this.showError(
552
+ earth?.errorDomElement,
553
+ '<div style="cursor:pointer;right:0px;">断开连接</div>'
554
+ );
555
+ },
556
+ onPlaying() {
557
+ console.log("视频播放中");
558
+ },
559
+ };
560
+
561
+ StampAPI.initEarth();