zhyz-cloudrender-v5 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.
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "zhyz-cloudrender-v5",
3
+ "version": "1.0.0",
4
+ "description": "cloudrender zhyz",
5
+ "main": "cloudRender.es.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "",
10
+ "license": "ISC"
11
+ }
@@ -0,0 +1,575 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+ // universal module definition - read https://www.davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/
3
+
4
+ (function (root, factory) {
5
+ if (typeof define === 'function' && define.amd) {
6
+ // AMD. Register as an anonymous module.
7
+ define(["./adapter"], factory);
8
+ } else if (typeof exports === 'object') {
9
+ // Node. Does not work with strict CommonJS, but
10
+ // only CommonJS-like environments that support module.exports,
11
+ // like Node.
12
+ module.exports = factory(require("./adapter"));
13
+ } else {
14
+ // Browser globals (root is window)
15
+ root.webRtcPlayer = factory(root.adapter);
16
+ }
17
+ }(this, function (adapter) {
18
+
19
+ function webRtcPlayer(parOptions) {
20
+ parOptions = typeof parOptions !== 'undefined' ? parOptions : {};
21
+
22
+ var self = this;
23
+
24
+ //**********************
25
+ //Config setup
26
+ //**********************
27
+ this.cfg = {};// typeof parOptions.peerConnectionOptions !== 'undefined' ? parOptions.peerConnectionOptions : {};
28
+ this.cfg.sdpSemantics = 'unified-plan';
29
+ // this.cfg.rtcAudioJitterBufferMaxPackets = 10;
30
+ // this.cfg.rtcAudioJitterBufferFastAccelerate = true;
31
+ // this.cfg.rtcAudioJitterBufferMinDelayMs = 0;
32
+
33
+ // If this is true in Chrome 89+ SDP is sent that is incompatible with UE Pixel Streaming 4.26 and below.
34
+ // However 4.27 Pixel Streaming does not need this set to false as it supports `offerExtmapAllowMixed`.
35
+ // tdlr; uncomment this line for older versions of Pixel Streaming that need Chrome 89+.
36
+ this.cfg.offerExtmapAllowMixed = false;
37
+
38
+ //**********************
39
+ //Variables
40
+ //**********************
41
+ this.pcClient = null;
42
+ this.dcClient = null;
43
+ this.tnClient = null;
44
+
45
+ this.sdpConstraints = {
46
+ offerToReceiveAudio: 1, //Note: if you don't need audio you can get improved latency by turning this off.
47
+ offerToReceiveVideo: 1,
48
+
49
+ };
50
+
51
+ // See https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit for values (this is needed for Firefox to be consistent with Chrome.)
52
+ this.dataChannelOptions = {ordered: true};
53
+
54
+ // This is useful if the video/audio needs to autoplay (without user input) as browsers do not allow autoplay non-muted of sound sources without user interaction.
55
+ this.startVideoMuted = true; //typeof parOptions.startVideoMuted !== 'undefined' ? parOptions.startVideoMuted : false;
56
+ this.autoPlayAudio = true; //typeof parOptions.autoPlayAudio !== 'undefined' ? parOptions.autoPlayAudio : true;
57
+
58
+ // To enable mic in browser use SSL/localhost and have ?useMic in the query string.
59
+ const urlParams = new URLSearchParams(window.location.search);
60
+ this.useMic = urlParams.has('useMic');
61
+ if(!this.useMic)
62
+ {
63
+ // console.log("Microphone access is not enabled. Pass ?useMic in the url to enable it.");
64
+ }
65
+
66
+ // When ?useMic check for SSL or localhost
67
+ let isLocalhostConnection = location.hostname === "localhost" || location.hostname === "127.0.0.1";
68
+ let isHttpsConnection = location.protocol === 'https:';
69
+ if(this.useMic && !isLocalhostConnection && !isHttpsConnection)
70
+ {
71
+ this.useMic = false;
72
+ console.error("Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.");
73
+ console.error("For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'");
74
+ }
75
+
76
+ // Latency tester
77
+ this.latencyTestTimings =
78
+ {
79
+ TestStartTimeMs: null,
80
+ UEReceiptTimeMs: null,
81
+ UEPreCaptureTimeMs: null,
82
+ UEPostCaptureTimeMs: null,
83
+ UEPreEncodeTimeMs: null,
84
+ UEPostEncodeTimeMs: null,
85
+ UETransmissionTimeMs: null,
86
+ BrowserReceiptTimeMs: null,
87
+ FrameDisplayDeltaTimeMs: null,
88
+ Reset: function()
89
+ {
90
+ this.TestStartTimeMs = null;
91
+ this.UEReceiptTimeMs = null;
92
+ this.UEPreCaptureTimeMs = null;
93
+ this.UEPostCaptureTimeMs = null;
94
+ this.UEPreEncodeTimeMs = null;
95
+ this.UEPostEncodeTimeMs = null;
96
+ this.UETransmissionTimeMs = null;
97
+ this.BrowserReceiptTimeMs = null;
98
+ this.FrameDisplayDeltaTimeMs = null;
99
+ },
100
+ SetUETimings: function(UETimings)
101
+ {
102
+ this.UEReceiptTimeMs = UETimings.ReceiptTimeMs;
103
+ this.UEPreCaptureTimeMs = UETimings.PreCaptureTimeMs;
104
+ this.UEPostCaptureTimeMs = UETimings.PostCaptureTimeMs;
105
+ this.UEPreEncodeTimeMs = UETimings.PreEncodeTimeMs;
106
+ this.UEPostEncodeTimeMs = UETimings.PostEncodeTimeMs;
107
+ this.UETransmissionTimeMs = UETimings.TransmissionTimeMs;
108
+ this.BrowserReceiptTimeMs = Date.now();
109
+ this.OnAllLatencyTimingsReady(this);
110
+ },
111
+ SetFrameDisplayDeltaTime: function(DeltaTimeMs)
112
+ {
113
+ if(this.FrameDisplayDeltaTimeMs == null)
114
+ {
115
+ this.FrameDisplayDeltaTimeMs = Math.round(DeltaTimeMs);
116
+ this.OnAllLatencyTimingsReady(this);
117
+ }
118
+ },
119
+ OnAllLatencyTimingsReady: function(Timings){}
120
+ }
121
+
122
+ //**********************
123
+ //Functions
124
+ //**********************
125
+
126
+ //Create Video element and expose that as a parameter
127
+ this.createWebRtcVideo = function() {
128
+ if(document.getElementById('streamingVideo')){
129
+ return document.getElementById('streamingVideo');
130
+ }
131
+ var video = document.createElement('video');
132
+ console.log('----> create video !!!');
133
+ video.id = "streamingVideo";
134
+ video.playsInline = true;
135
+ video.disablepictureinpicture = true;
136
+ video.muted = self.startVideoMuted;
137
+ // video.hidden = true;
138
+ video.style.objectFit = 'fill';
139
+ video.addEventListener('loadedmetadata', function(e){
140
+ if(self.onVideoInitialised){
141
+ self.onVideoInitialised();
142
+ }
143
+ }, true);
144
+ video.addEventListener('contextmenu', function( e ) {
145
+ e.preventDefault()
146
+ })
147
+ // Check if request video frame callback is supported
148
+ if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
149
+ // The API is supported!
150
+
151
+ const onVideoFrameReady = (now, metadata) => {
152
+
153
+ if(metadata.receiveTime && metadata.expectedDisplayTime)
154
+ {
155
+ const receiveToCompositeMs = metadata.presentationTime - metadata.receiveTime;
156
+ self.aggregatedStats.receiveToCompositeMs = receiveToCompositeMs;
157
+ }
158
+
159
+
160
+ // Re-register the callback to be notified about the next frame.
161
+ video.requestVideoFrameCallback(onVideoFrameReady);
162
+ };
163
+
164
+ // Initially register the callback to be notified about the first frame.
165
+ video.requestVideoFrameCallback(onVideoFrameReady);
166
+ }
167
+
168
+ return video;
169
+ }
170
+
171
+ this.offsecreenvideo = this.createWebRtcVideo();
172
+
173
+ let onsignalingstatechange = function(state)
174
+ {
175
+ // console.log('iceconnectionstatechange ---> [' + self.pcClient.iceConnectionState + '] ^_^ !!!');
176
+ // console.info('signaling state change:', state)
177
+ };
178
+
179
+ let oniceconnectionstatechange = function(state) {
180
+ // console.log('iceconnectionstatechange ---> [' + self.pcClient.iceConnectionState + '] ^_^ !!!');
181
+ // console.info('ice connection state change:', state)
182
+ };
183
+
184
+ let onicegatheringstatechange = function(state) {
185
+ // console.log('iceconnectionstatechange ---> [' + self.pcClient.iceConnectionState + '] ^_^ !!!');
186
+ // console.info('ice gathering state change:', state)
187
+ };
188
+
189
+ let handleOnTrack = function(e) {
190
+ // console.log('handleOnTrack', e.streams);
191
+
192
+ if (e.track)
193
+ {
194
+ // console.log('Got track - ' + e.track.kind + ' id=' + e.track.id + ' readyState=' + e.track.readyState);
195
+ }
196
+
197
+ if(e.track.kind == "audio")
198
+ {
199
+ handleOnAudioTrack(e.streams[0]);
200
+ return;
201
+ }
202
+ else(e.track.kind == "video" && self.offsecreenvideo.srcObject !== e.streams[0])
203
+ {
204
+ self.offsecreenvideo.srcObject = e.streams[0];
205
+ // console.log('Set video source from video track ontrack.');
206
+ return;
207
+ }
208
+
209
+ };
210
+
211
+ let handleOnAudioTrack = function(audioMediaStream)
212
+ {
213
+ // do nothing the video has the same media stream as the audio track we have here (they are linked)
214
+ if(self.offsecreenvideo.srcObject == audioMediaStream)
215
+ {
216
+ return;
217
+ }
218
+ // video element has some other media stream that is not associated with this audio track
219
+ else if(self.offsecreenvideo.srcObject && self.offsecreenvideo.srcObject !== audioMediaStream)
220
+ {
221
+ // create a new audio element
222
+ let audioElem = document.createElement("Audio");
223
+ audioElem.srcObject = audioMediaStream;
224
+
225
+ // there is no way to autoplay audio (even muted), so we defer audio until first click
226
+ if(!self.autoPlayAudio) {
227
+
228
+ let clickToPlayAudio = function() {
229
+ audioElem && audioElem.play();
230
+ self.offsecreenvideo.removeEventListener("click", clickToPlayAudio);
231
+ };
232
+
233
+ self.offsecreenvideo.addEventListener("click", clickToPlayAudio);
234
+ }
235
+ // we assume the user has clicked somewhere on the page and autoplaying audio will work
236
+ else {
237
+ audioElem && audioElem.play();
238
+ }
239
+ // console.log('Created new audio element to play seperate audio stream.');
240
+ }
241
+
242
+ }
243
+
244
+ let setupDataChannel = function(pc, label, options) {
245
+ try {
246
+ let datachannel = pc.createDataChannel(label, options);
247
+ // console.log(`Created datachannel (${label})`)
248
+
249
+ // Inform browser we would like binary data as an ArrayBuffer (FF chooses Blob by default!)
250
+ datachannel.binaryType = "arraybuffer";
251
+
252
+ datachannel.onopen = function (e) {
253
+ // console.log(`data channel (${label}) connect`)
254
+ if(self.onDataChannelConnected){
255
+ self.onDataChannelConnected();
256
+ }
257
+ }
258
+
259
+ datachannel.onclose = function (e) {
260
+ // console.log(`data channel (${label}) closed`)
261
+ }
262
+
263
+ datachannel.onmessage = function (e) {
264
+ // console.log(`Got message (${label})`, e.data)
265
+ if (self.onDataChannelMessage)
266
+ self.onDataChannelMessage(e.data);
267
+ }
268
+
269
+ return datachannel;
270
+ } catch (e) {
271
+ console.warn('No data channel', e);
272
+ return null;
273
+ }
274
+ }
275
+
276
+ let onicecandidate = function (e) {
277
+ // console.log('ICE candidate', e)
278
+ if (e.candidate && e.candidate.candidate) {
279
+ self.onWebRtcCandidate(0, e.candidate);
280
+ }
281
+ };
282
+
283
+ let handleCreateOffer = function (pc) {
284
+ pc.createOffer( ).then(function (offer) {
285
+
286
+ // Munging is where we modifying the sdp string to set parameters that are not exposed to the browser's WebRTC API
287
+ // mungeSDPOffer(offer);
288
+ // console.log('offer =', offer);
289
+ // Set our munged SDP on the local peer connection so it is "set" and will be send across
290
+ pc.setLocalDescription(offer);
291
+ if (self.onWebRtcOffer) {
292
+ // console.log('rtctype == 0-');
293
+ self.onWebRtcOffer(0, offer);
294
+ }
295
+ },
296
+ function () { console.warn("Couldn't create offer") });
297
+ }
298
+
299
+ let mungeSDPOffer = function (offer) {
300
+
301
+ // turn off video-timing sdp sent from browser
302
+ //offer.sdp = offer.sdp.replace("http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", "");
303
+
304
+ // this indicate we support stereo (Chrome needs this)
305
+ offer.sdp = offer.sdp.replace('useinbandfec=1', 'useinbandfec=1;stereo=1;sprop-maxcapturerate=48000');
306
+
307
+ }
308
+
309
+ let setupPeerConnection = function (pc) {
310
+ if (pc.SetBitrate)
311
+ // console.log("Hurray! there's RTCPeerConnection.SetBitrate function");
312
+
313
+ //Setup peerConnection events
314
+ pc.onsignalingstatechange = onsignalingstatechange;
315
+ pc.oniceconnectionstatechange = oniceconnectionstatechange;
316
+ pc.onicegatheringstatechange = onicegatheringstatechange;
317
+
318
+ pc.ontrack = handleOnTrack;
319
+ pc.onicecandidate = onicecandidate;
320
+ };
321
+
322
+ let generateAggregatedStatsFunction = function(){
323
+ if(!self.aggregatedStats)
324
+ self.aggregatedStats = {};
325
+
326
+ return function(stats){
327
+ //console.log('Printing Stats');
328
+
329
+ let newStat = {};
330
+
331
+ stats.forEach(stat => {
332
+ // console.log(JSON.stringify(stat, undefined, 4));
333
+ if (stat.type == 'inbound-rtp'
334
+ && !stat.isRemote
335
+ && (stat.mediaType == 'video' || stat.id.toLowerCase().includes('video'))) {
336
+
337
+ newStat.timestamp = stat.timestamp;
338
+ newStat.bytesReceived = stat.bytesReceived;
339
+ newStat.framesDecoded = stat.framesDecoded;
340
+ newStat.packetsLost = stat.packetsLost;
341
+ newStat.bytesReceivedStart = self.aggregatedStats && self.aggregatedStats.bytesReceivedStart ? self.aggregatedStats.bytesReceivedStart : stat.bytesReceived;
342
+ newStat.framesDecodedStart = self.aggregatedStats && self.aggregatedStats.framesDecodedStart ? self.aggregatedStats.framesDecodedStart : stat.framesDecoded;
343
+ newStat.timestampStart = self.aggregatedStats && self.aggregatedStats.timestampStart ? self.aggregatedStats.timestampStart : stat.timestamp;
344
+
345
+ if(self.aggregatedStats && self.aggregatedStats.timestamp){
346
+ if(self.aggregatedStats.bytesReceived){
347
+ // bitrate = bits received since last time / number of ms since last time
348
+ //This is automatically in kbits (where k=1000) since time is in ms and stat we want is in seconds (so a '* 1000' then a '/ 1000' would negate each other)
349
+ newStat.bitrate = 8 * (newStat.bytesReceived - self.aggregatedStats.bytesReceived) / (newStat.timestamp - self.aggregatedStats.timestamp);
350
+ newStat.bitrate = Math.floor(newStat.bitrate);
351
+ newStat.lowBitrate = self.aggregatedStats.lowBitrate && self.aggregatedStats.lowBitrate < newStat.bitrate ? self.aggregatedStats.lowBitrate : newStat.bitrate
352
+ newStat.highBitrate = self.aggregatedStats.highBitrate && self.aggregatedStats.highBitrate > newStat.bitrate ? self.aggregatedStats.highBitrate : newStat.bitrate
353
+ }
354
+
355
+ if(self.aggregatedStats.bytesReceivedStart){
356
+ newStat.avgBitrate = 8 * (newStat.bytesReceived - self.aggregatedStats.bytesReceivedStart) / (newStat.timestamp - self.aggregatedStats.timestampStart);
357
+ newStat.avgBitrate = Math.floor(newStat.avgBitrate);
358
+ }
359
+
360
+ if(self.aggregatedStats.framesDecoded){
361
+ // framerate = frames decoded since last time / number of seconds since last time
362
+ newStat.framerate = (newStat.framesDecoded - self.aggregatedStats.framesDecoded) / ((newStat.timestamp - self.aggregatedStats.timestamp) / 1000);
363
+ newStat.framerate = Math.floor(newStat.framerate);
364
+ newStat.lowFramerate = self.aggregatedStats.lowFramerate && self.aggregatedStats.lowFramerate < newStat.framerate ? self.aggregatedStats.lowFramerate : newStat.framerate
365
+ newStat.highFramerate = self.aggregatedStats.highFramerate && self.aggregatedStats.highFramerate > newStat.framerate ? self.aggregatedStats.highFramerate : newStat.framerate
366
+ }
367
+
368
+ if(self.aggregatedStats.framesDecodedStart){
369
+ newStat.avgframerate = (newStat.framesDecoded - self.aggregatedStats.framesDecodedStart) / ((newStat.timestamp - self.aggregatedStats.timestampStart) / 1000);
370
+ newStat.avgframerate = Math.floor(newStat.avgframerate);
371
+ }
372
+ }
373
+ }
374
+
375
+ //Read video track stats
376
+ if(stat.type == 'track' && (stat.trackIdentifier == 'video_label' || stat.kind == 'video')) {
377
+ newStat.framesDropped = stat.framesDropped;
378
+ newStat.framesReceived = stat.framesReceived;
379
+ newStat.framesDroppedPercentage = stat.framesDropped / stat.framesReceived * 100;
380
+ newStat.frameHeight = stat.frameHeight;
381
+ newStat.frameWidth = stat.frameWidth;
382
+ newStat.frameHeightStart = self.aggregatedStats && self.aggregatedStats.frameHeightStart ? self.aggregatedStats.frameHeightStart : stat.frameHeight;
383
+ newStat.frameWidthStart = self.aggregatedStats && self.aggregatedStats.frameWidthStart ? self.aggregatedStats.frameWidthStart : stat.frameWidth;
384
+ }
385
+
386
+ if(stat.type =='candidate-pair' && stat.hasOwnProperty('currentRoundTripTime') && stat.currentRoundTripTime != 0){
387
+ newStat.currentRoundTripTime = stat.currentRoundTripTime;
388
+ }
389
+ });
390
+
391
+
392
+ if(self.aggregatedStats.receiveToCompositeMs)
393
+ {
394
+ newStat.receiveToCompositeMs = self.aggregatedStats.receiveToCompositeMs;
395
+ self.latencyTestTimings.SetFrameDisplayDeltaTime(self.aggregatedStats.receiveToCompositeMs);
396
+ }
397
+
398
+ self.aggregatedStats = newStat;
399
+
400
+ if(self.onAggregatedStats)
401
+ self.onAggregatedStats(newStat)
402
+ }
403
+ };
404
+
405
+ let setupTracksToSendAsync = async function(pc){
406
+
407
+
408
+
409
+ // Setup a transceiver for sending mic audio to UE and receiving audio from UE
410
+ if(!self.useMic)
411
+ {
412
+ pc.addTransceiver("audio", { direction: "recvonly" });
413
+ }
414
+ else
415
+ {
416
+ let audioSendOptions = self.useMic ?
417
+ {
418
+ autoGainControl: false,
419
+ channelCount: 1,
420
+ echoCancellation: false,
421
+ latency: 0,
422
+ noiseSuppression: false,
423
+ sampleRate: 16000,
424
+ volume: 1.0
425
+ } : false;
426
+
427
+ // Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
428
+ const stream = await navigator.mediaDevices.getUserMedia({video: false, audio: audioSendOptions});
429
+ if(stream)
430
+ {
431
+ for (const track of stream.getTracks()) {
432
+ if(track.kind && track.kind == "audio")
433
+ {
434
+ pc.addTransceiver(track, { direction: "sendrecv" });
435
+ }
436
+ }
437
+ }
438
+ else
439
+ {
440
+ pc.addTransceiver("audio", { direction: "recvonly" });
441
+ }
442
+ }
443
+ // Setup a transceiver for getting UE video
444
+ pc.addTransceiver("video", { direction: "recvonly" });
445
+ };
446
+
447
+
448
+ //**********************
449
+ //Public functions
450
+ //**********************
451
+
452
+ this.setVideoEnabled = function(enabled) {
453
+ self.offsecreenvideo.srcObject.getTracks().forEach(track => track.enabled = enabled);
454
+ }
455
+
456
+ this.startLatencyTest = function(onTestStarted) {
457
+ // Can't start latency test without a video element
458
+ if(!self.offsecreenvideo)
459
+ {
460
+ return;
461
+ }
462
+
463
+ self.latencyTestTimings.Reset();
464
+ self.latencyTestTimings.TestStartTimeMs = Date.now();
465
+ onTestStarted(self.latencyTestTimings.TestStartTimeMs);
466
+ }
467
+
468
+ //This is called when revceiving new ice candidates individually instead of part of the offer
469
+ //This is currently not used but would be called externally from this class
470
+ this.handleCandidateFromServer = function(iceCandidate) {
471
+ // console.log("ICE candidate: ", iceCandidate);
472
+
473
+ //?????Candidate??
474
+ // var candidate = new RTCIceCandidate({
475
+ // sdpMLineIndex :data.label,
476
+ // candidate:data.candidate
477
+ // });
478
+ //
479
+ let candidate = new RTCIceCandidate(iceCandidate);
480
+ self.pcClient.addIceCandidate(candidate).then(_=>{
481
+ // console.log('ICE candidate successfully added');
482
+ });
483
+ };
484
+
485
+ //Called externaly to create an offer for the server
486
+ this.createOffer = function() {
487
+ if(self.pcClient){
488
+ // console.log("Closing existing PeerConnection")
489
+ self.pcClient.close();
490
+ self.pcClient = null;
491
+ }
492
+ var pcConfig = {
493
+ 'iceServer' : [{
494
+ //TURN?????
495
+ 'urls': 'stun:stun.l.google.com:19302'
496
+ // TURN??????
497
+ //'username': 'xxx',
498
+ //TURN?????
499
+ //'credential': "xxx"
500
+ }],
501
+ // ????relay?????? turn ?? ?? ^_^
502
+ "iceTransportPolicy": "all",
503
+ //"iceTransportPolicy": "relay",
504
+ // ?????????
505
+ "bundlePolicy": "max-bundle",
506
+ // ???rtcp?rtp? ????Candidate ???????? ^_^
507
+ "rtcpMuxPolicy": "require",
508
+ "iceCandiatePoolSize": "0"
509
+ };
510
+
511
+ self.pcClient = new RTCPeerConnection( /*self.cfg*/);
512
+
513
+ setupTracksToSendAsync(self.pcClient).finally(function()
514
+ {
515
+ setupPeerConnection(self.pcClient);
516
+ self.dcClient = setupDataChannel(self.pcClient, 'rtc', self.dataChannelOptions);
517
+ handleCreateOffer(self.pcClient);
518
+ });
519
+
520
+ };
521
+
522
+ //Called externaly when an answer is received from the server
523
+ this.receiveAnswer = function(answer) {
524
+ // console.log('Received answer:');
525
+ // console.log(answer);
526
+ // var answerDesc = new RTCSessionDescription({type: 'answer', sdp: answer});
527
+ self.pcClient.setRemoteDescription(new RTCSessionDescription(answer));
528
+ // console.log('setremote sdp ok !!!');
529
+
530
+ let receivers = self.pcClient.getReceivers();
531
+ // console.log('setremote sdp getReceivers');
532
+ for(let receiver of receivers)
533
+ {
534
+ receiver.playoutDelayHint = 0;
535
+ }
536
+
537
+ };
538
+
539
+ this.close = function(){
540
+ if(self.pcClient){
541
+ // console.log("Closing existing peerClient")
542
+ self.pcClient.close();
543
+ self.pcClient = null;
544
+ }
545
+ if(self.aggregateStatsIntervalId)
546
+ clearInterval(self.aggregateStatsIntervalId);
547
+ }
548
+
549
+ //Sends data across the datachannel
550
+ this.send = function(data)
551
+ {
552
+ // console.log('---------------> self.dcClient.readyState = ', self.dcClient.readyState);
553
+ if(self.dcClient && self.dcClient.readyState == 'open'){
554
+ self.dcClient.send(data);
555
+ }
556
+ };
557
+
558
+ this.getStats = function(onStats){
559
+ if(self.pcClient && onStats){
560
+ self.pcClient.getStats(null).then((stats) => {
561
+ onStats(stats);
562
+ });
563
+ }
564
+ }
565
+
566
+ this.aggregateStats = function(checkInterval){
567
+ let calcAggregatedStats = generateAggregatedStatsFunction();
568
+ let printAggregatedStats = () => { self.getStats(calcAggregatedStats); }
569
+ self.aggregateStatsIntervalId = setInterval(printAggregatedStats, checkInterval);
570
+ }
571
+ }
572
+
573
+ return webRtcPlayer;
574
+
575
+ }));