@flashphoner/websdk 2.0.238 → 2.0.239
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/docTemplate/README.md +1 -1
- package/flashphoner-no-flash.js +135 -22
- package/flashphoner-no-flash.min.js +2 -2
- package/flashphoner-no-webrtc.js +113 -20
- package/flashphoner-no-webrtc.min.js +2 -2
- package/flashphoner-no-wsplayer.js +135 -22
- package/flashphoner-no-wsplayer.min.js +2 -2
- package/flashphoner-room-api.js +156 -43
- package/flashphoner-room-api.min.js +2 -2
- package/flashphoner-temasys-flash-websocket-without-adapterjs.js +113 -20
- package/flashphoner-temasys-flash-websocket.js +113 -20
- package/flashphoner-temasys-flash-websocket.min.js +1 -1
- package/flashphoner-webrtc-only.js +135 -22
- package/flashphoner-webrtc-only.min.js +1 -1
- package/flashphoner.js +135 -22
- package/flashphoner.min.js +2 -2
- package/package.json +1 -1
- package/src/flashphoner-core.js +62 -39
- package/src/util.js +70 -1
- package/src/webrtc-media-provider.js +24 -3
package/flashphoner-room-api.js
CHANGED
|
@@ -9137,35 +9137,46 @@ module.exports = {
|
|
|
9137
9137
|
'use strict';
|
|
9138
9138
|
|
|
9139
9139
|
const { v1: uuid_v1 } = require('uuid');
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9140
|
+
const constants = require("./constants");
|
|
9141
|
+
const util = require('./util');
|
|
9142
|
+
const LoggerObject = require('./util').logger;
|
|
9143
|
+
const Promise = require('promise-polyfill');
|
|
9144
|
+
const KalmanFilter = require('kalmanjs');
|
|
9145
|
+
const browserDetails = require('webrtc-adapter').default.browserDetails;
|
|
9146
|
+
const LOG_PREFIX = "core";
|
|
9143
9147
|
var coreLogger;
|
|
9144
9148
|
var loggerConf = {push: false, severity: "INFO"};
|
|
9145
|
-
var Promise = require('promise-polyfill');
|
|
9146
|
-
var KalmanFilter = require('kalmanjs');
|
|
9147
|
-
var browserDetails = require('webrtc-adapter').default.browserDetails;
|
|
9148
|
-
var LOG_PREFIX = "core";
|
|
9149
9149
|
var isUsingTemasysPlugin = false;
|
|
9150
9150
|
|
|
9151
9151
|
/**
|
|
9152
9152
|
* @namespace Flashphoner
|
|
9153
9153
|
*/
|
|
9154
9154
|
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9155
|
+
const SESSION_STATUS = constants.SESSION_STATUS;
|
|
9156
|
+
const STREAM_EVENT = constants.STREAM_EVENT;
|
|
9157
|
+
const STREAM_EVENT_TYPE = constants.STREAM_EVENT_TYPE;
|
|
9158
|
+
const STREAM_STATUS = constants.STREAM_STATUS;
|
|
9159
|
+
const CALL_STATUS = constants.CALL_STATUS;
|
|
9160
|
+
const CONNECTION_QUALITY = constants.CONNECTION_QUALITY;
|
|
9161
|
+
const ERROR_INFO = constants.ERROR_INFO;
|
|
9162
|
+
const VIDEO_RATE_GOOD_QUALITY_PERCENT_DIFFERENCE = 20;
|
|
9163
|
+
const VIDEO_RATE_BAD_QUALITY_PERCENT_DIFFERENCE = 50;
|
|
9164
|
+
const LOW_VIDEO_RATE_THRESHOLD_BAD_PERFECT = 50000;
|
|
9165
|
+
const LOW_VIDEO_RATE_BAD_QUALITY_PERCENT_DIFFERENCE = 150;
|
|
9166
|
+
const OUTBOUND_VIDEO_RATE = "outboundVideoRate";
|
|
9167
|
+
const INBOUND_VIDEO_RATE = "inboundVideoRate";
|
|
9168
|
+
const CONSTRAINT_AUDIO = "audio";
|
|
9169
|
+
const CONSTRAINT_AUDIO_STEREO = "stereo";
|
|
9170
|
+
const CONSTRAINT_AUDIO_BITRATE = "bitrate";
|
|
9171
|
+
const CONSTRAINT_AUDIO_FEC = "fec";
|
|
9172
|
+
const CONSTRAINT_AUDIO_OUTPUT_ID = "outputId";
|
|
9173
|
+
const CONSTRAINT_VIDEO = "video";
|
|
9174
|
+
const CONSTRAINT_VIDEO_BITRATE = "video.bitrate";
|
|
9175
|
+
const CONSTRAINT_VIDEO_MIN_BITRATE = "video.minBitrate";
|
|
9176
|
+
const CONSTRAINT_VIDEO_MAX_BITRATE = "video.maxBitrate";
|
|
9177
|
+
const CONSTRAINT_VIDEO_QUALITY = "video.quality";
|
|
9178
|
+
const CONSTRAINT_VIDEO_WIDTH = "video.width";
|
|
9179
|
+
const CONSTRAINT_VIDEO_HEIGHT = "video.height";
|
|
9169
9180
|
var MediaProvider = {};
|
|
9170
9181
|
var sessions = {};
|
|
9171
9182
|
var initialized = false;
|
|
@@ -10002,9 +10013,9 @@ var createSession = function (options) {
|
|
|
10002
10013
|
}
|
|
10003
10014
|
|
|
10004
10015
|
var audioOutputId;
|
|
10005
|
-
var audioProperty = getConstraintsProperty(constraints,
|
|
10016
|
+
var audioProperty = getConstraintsProperty(constraints, CONSTRAINT_AUDIO, undefined);
|
|
10006
10017
|
if (typeof audioProperty === 'object') {
|
|
10007
|
-
audioOutputId = getConstraintsProperty(audioProperty,
|
|
10018
|
+
audioOutputId = getConstraintsProperty(audioProperty, CONSTRAINT_AUDIO_OUTPUT_ID, 0);
|
|
10008
10019
|
}
|
|
10009
10020
|
|
|
10010
10021
|
var stripCodecs = options.stripCodecs || [];
|
|
@@ -10021,6 +10032,10 @@ var createSession = function (options) {
|
|
|
10021
10032
|
var sipHeaders = options.sipHeaders;
|
|
10022
10033
|
var videoContentHint = options.videoContentHint;
|
|
10023
10034
|
var useControls = options.useControls;
|
|
10035
|
+
|
|
10036
|
+
var minBitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_MIN_BITRATE, 0);
|
|
10037
|
+
var maxBitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_MAX_BITRATE, 0);
|
|
10038
|
+
|
|
10024
10039
|
/**
|
|
10025
10040
|
* Represents sip call.
|
|
10026
10041
|
*
|
|
@@ -10057,8 +10072,9 @@ var createSession = function (options) {
|
|
|
10057
10072
|
//set remote sdp
|
|
10058
10073
|
if (sdp && sdp !== '') {
|
|
10059
10074
|
sdp = sdpHookHandler(sdp, sdpHook);
|
|
10060
|
-
|
|
10061
|
-
|
|
10075
|
+
// Adjust publishing bitrate #WCS-4013
|
|
10076
|
+
sdp = util.setPublishingBitrate(sdp, mediaConnection, minBitrate, maxBitrate);
|
|
10077
|
+
mediaConnection.setRemoteSdp(sdp, hasTransferredCall, id_).then(function () {});
|
|
10062
10078
|
return;
|
|
10063
10079
|
}
|
|
10064
10080
|
var event = callInfo.status;
|
|
@@ -10220,6 +10236,8 @@ var createSession = function (options) {
|
|
|
10220
10236
|
status_ = CALL_STATUS.PENDING;
|
|
10221
10237
|
var sdp;
|
|
10222
10238
|
var sdpHook = answerOptions.sdpHook;
|
|
10239
|
+
var minBitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_MIN_BITRATE, 0);
|
|
10240
|
+
var maxBitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_MAX_BITRATE, 0);
|
|
10223
10241
|
sipSDP = answerOptions.sipSDP;
|
|
10224
10242
|
sipHeaders = answerOptions.sipHeaders;
|
|
10225
10243
|
if (!remoteSdpCache[id_]) {
|
|
@@ -10227,6 +10245,8 @@ var createSession = function (options) {
|
|
|
10227
10245
|
throw new Error("No remote sdp available");
|
|
10228
10246
|
} else {
|
|
10229
10247
|
sdp = sdpHookHandler(remoteSdpCache[id_], sdpHook);
|
|
10248
|
+
// Adjust publishing bitrate #WCS-4013
|
|
10249
|
+
sdp = util.setPublishingBitrate(sdp, null, minBitrate, maxBitrate);
|
|
10230
10250
|
delete remoteSdpCache[id_];
|
|
10231
10251
|
}
|
|
10232
10252
|
if (util.SDP.matchPrefix(sdp, "m=video").length == 0) {
|
|
@@ -10260,6 +10280,8 @@ var createSession = function (options) {
|
|
|
10260
10280
|
useControls: useControls
|
|
10261
10281
|
}).then(function (newConnection) {
|
|
10262
10282
|
mediaConnection = newConnection;
|
|
10283
|
+
// Set publishing bitrate via sender encodings if SDP feature is not supported
|
|
10284
|
+
mediaConnection.setPublishingBitrate(minBitrate, maxBitrate);
|
|
10263
10285
|
return mediaConnection.setRemoteSdp(sdp);
|
|
10264
10286
|
}).then(function () {
|
|
10265
10287
|
return mediaConnection.createAnswer({
|
|
@@ -10787,15 +10809,15 @@ var createSession = function (options) {
|
|
|
10787
10809
|
// Receive media
|
|
10788
10810
|
var receiveAudio;
|
|
10789
10811
|
var audioOutputId;
|
|
10790
|
-
var audioProperty = getConstraintsProperty(constraints,
|
|
10812
|
+
var audioProperty = getConstraintsProperty(constraints, CONSTRAINT_AUDIO, undefined);
|
|
10791
10813
|
if (typeof audioProperty === 'boolean') {
|
|
10792
10814
|
receiveAudio = audioProperty;
|
|
10793
10815
|
} else if (typeof audioProperty === 'object') {
|
|
10794
10816
|
receiveAudio = true;
|
|
10795
|
-
var _stereo = getConstraintsProperty(audioProperty,
|
|
10796
|
-
var _bitrate = getConstraintsProperty(audioProperty,
|
|
10797
|
-
var _fec = getConstraintsProperty(audioProperty,
|
|
10798
|
-
audioOutputId = getConstraintsProperty(audioProperty,
|
|
10817
|
+
var _stereo = getConstraintsProperty(audioProperty, CONSTRAINT_AUDIO_STEREO, 0);
|
|
10818
|
+
var _bitrate = getConstraintsProperty(audioProperty, CONSTRAINT_AUDIO_BITRATE, 0);
|
|
10819
|
+
var _fec = getConstraintsProperty(audioProperty, CONSTRAINT_AUDIO_FEC, 0);
|
|
10820
|
+
audioOutputId = getConstraintsProperty(audioProperty, CONSTRAINT_AUDIO_OUTPUT_ID, 0);
|
|
10799
10821
|
var _codecOptions = "";
|
|
10800
10822
|
if (_bitrate) _codecOptions += "maxaveragebitrate=" + _bitrate + ";";
|
|
10801
10823
|
if (_stereo) _codecOptions += "stereo=1;sprop-stereo=1;";
|
|
@@ -10804,7 +10826,7 @@ var createSession = function (options) {
|
|
|
10804
10826
|
receiveAudio = (typeof options.receiveAudio !== 'undefined') ? options.receiveAudio : true;
|
|
10805
10827
|
}
|
|
10806
10828
|
var receiveVideo;
|
|
10807
|
-
var videoProperty = getConstraintsProperty(constraints,
|
|
10829
|
+
var videoProperty = getConstraintsProperty(constraints, CONSTRAINT_VIDEO, undefined);
|
|
10808
10830
|
if (typeof videoProperty === 'boolean') {
|
|
10809
10831
|
receiveVideo = videoProperty;
|
|
10810
10832
|
} else if (typeof videoProperty === 'object') {
|
|
@@ -10813,16 +10835,16 @@ var createSession = function (options) {
|
|
|
10813
10835
|
receiveVideo = (typeof options.receiveVideo !== 'undefined') ? options.receiveVideo : true;
|
|
10814
10836
|
}
|
|
10815
10837
|
// Bitrate
|
|
10816
|
-
var bitrate = getConstraintsProperty(constraints,
|
|
10817
|
-
var minBitrate = getConstraintsProperty(constraints,
|
|
10818
|
-
var maxBitrate = getConstraintsProperty(constraints,
|
|
10838
|
+
var bitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_BITRATE, 0);
|
|
10839
|
+
var minBitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_MIN_BITRATE, 0);
|
|
10840
|
+
var maxBitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_MAX_BITRATE, 0);
|
|
10819
10841
|
|
|
10820
10842
|
// Quality
|
|
10821
|
-
var quality = getConstraintsProperty(constraints,
|
|
10843
|
+
var quality = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_QUALITY, 0);
|
|
10822
10844
|
if (quality > 100) quality = 100;
|
|
10823
10845
|
// Play resolution
|
|
10824
|
-
var playWidth = (typeof options.playWidth !== 'undefined') ? options.playWidth : getConstraintsProperty(constraints,
|
|
10825
|
-
var playHeight = (typeof options.playHeight !== 'undefined') ? options.playHeight : getConstraintsProperty(constraints,
|
|
10846
|
+
var playWidth = (typeof options.playWidth !== 'undefined') ? options.playWidth : getConstraintsProperty(constraints, CONSTRAINT_VIDEO_WIDTH, 0);
|
|
10847
|
+
var playHeight = (typeof options.playHeight !== 'undefined') ? options.playHeight : getConstraintsProperty(constraints, CONSTRAINT_VIDEO_HEIGHT, 0);
|
|
10826
10848
|
var stripCodecs = options.stripCodecs || [];
|
|
10827
10849
|
var resolution = {};
|
|
10828
10850
|
|
|
@@ -10883,8 +10905,9 @@ var createSession = function (options) {
|
|
|
10883
10905
|
var _sdp = sdp;
|
|
10884
10906
|
if (_codecOptions) _sdp = util.SDP.writeFmtp(sdp, _codecOptions, "opus");
|
|
10885
10907
|
_sdp = sdpHookHandler(_sdp, sdpHook);
|
|
10886
|
-
|
|
10887
|
-
|
|
10908
|
+
// Adjust publishing bitrate #WCS-4013
|
|
10909
|
+
_sdp = util.setPublishingBitrate(_sdp, mediaConnection, minBitrate, maxBitrate);
|
|
10910
|
+
mediaConnection.setRemoteSdp(_sdp).then(function () {});
|
|
10888
10911
|
return;
|
|
10889
10912
|
}
|
|
10890
10913
|
|
|
@@ -13326,6 +13349,61 @@ const SDP = {
|
|
|
13326
13349
|
}
|
|
13327
13350
|
}
|
|
13328
13351
|
return result;
|
|
13352
|
+
},
|
|
13353
|
+
setPublishingBitrate: function (sdp, minBitrate, maxBitrate) {
|
|
13354
|
+
if(sdp && (minBitrate || maxBitrate)) {
|
|
13355
|
+
let sdpArray = sdp.split("\n");
|
|
13356
|
+
let i;
|
|
13357
|
+
let rtpmap = -1, codec = "";
|
|
13358
|
+
let matches;
|
|
13359
|
+
let bitrateString = "";
|
|
13360
|
+
for (i = 0; i < sdpArray.length; i++) {
|
|
13361
|
+
if (sdpArray[i].startsWith("a=rtpmap")) {
|
|
13362
|
+
matches = sdpArray[i].match("a=rtpmap:(.+) (.+)/.*");
|
|
13363
|
+
if (matches && matches.length > 2) {
|
|
13364
|
+
rtpmap = parseInt(matches[1], 10);
|
|
13365
|
+
codec = matches[2];
|
|
13366
|
+
}
|
|
13367
|
+
} else {
|
|
13368
|
+
if (codec === "H264" || codec === "VP8") {
|
|
13369
|
+
if (sdpArray[i].startsWith("a=fmtp:" + rtpmap)) {
|
|
13370
|
+
bitrateString = this.getBitrateString(sdpArray[i], minBitrate, maxBitrate);
|
|
13371
|
+
if (bitrateString) {
|
|
13372
|
+
sdpArray[i] += ";" + bitrateString;
|
|
13373
|
+
}
|
|
13374
|
+
} else {
|
|
13375
|
+
bitrateString = this.getBitrateString("", minBitrate, maxBitrate);
|
|
13376
|
+
if (bitrateString) {
|
|
13377
|
+
sdpArray[i] = "a=fmtp:" + rtpmap + " " + bitrateString + "\r\n" + sdpArray[i];
|
|
13378
|
+
}
|
|
13379
|
+
}
|
|
13380
|
+
codec = "";
|
|
13381
|
+
rtpmap = -1;
|
|
13382
|
+
}
|
|
13383
|
+
}
|
|
13384
|
+
}
|
|
13385
|
+
let newSDP = "";
|
|
13386
|
+
for (i = 0; i < sdpArray.length; i++) {
|
|
13387
|
+
if (sdpArray[i] != "") {
|
|
13388
|
+
newSDP += sdpArray[i] + "\n";
|
|
13389
|
+
}
|
|
13390
|
+
}
|
|
13391
|
+
return newSDP;
|
|
13392
|
+
}
|
|
13393
|
+
return sdp;
|
|
13394
|
+
},
|
|
13395
|
+
getBitrateString: function (string, minBitrate, maxBitrate) {
|
|
13396
|
+
let bitrateString = "";
|
|
13397
|
+
if (minBitrate && string.indexOf("x-google-min-bitrate") == -1) {
|
|
13398
|
+
bitrateString += "x-google-min-bitrate=" + minBitrate;
|
|
13399
|
+
}
|
|
13400
|
+
if (maxBitrate && string.indexOf("x-google-max-bitrate") == -1) {
|
|
13401
|
+
if (bitrateString) {
|
|
13402
|
+
bitrateString += ";";
|
|
13403
|
+
}
|
|
13404
|
+
bitrateString += "x-google-max-bitrate=" + maxBitrate;
|
|
13405
|
+
}
|
|
13406
|
+
return bitrateString;
|
|
13329
13407
|
}
|
|
13330
13408
|
};
|
|
13331
13409
|
|
|
@@ -13575,6 +13653,19 @@ const isPromise = function(object) {
|
|
|
13575
13653
|
return false;
|
|
13576
13654
|
};
|
|
13577
13655
|
|
|
13656
|
+
const setPublishingBitrate = function(sdp, mediaConnection, minBitrate, maxBitrate) {
|
|
13657
|
+
if (minBitrate || maxBitrate) {
|
|
13658
|
+
if (sdp && Browser.isChrome() || Browser.isSafariWebRTC()) {
|
|
13659
|
+
// Set publishing bitrate constraints via remote SDP
|
|
13660
|
+
sdp = SDP.setPublishingBitrate(sdp, minBitrate, maxBitrate);
|
|
13661
|
+
} else if (mediaConnection) {
|
|
13662
|
+
// Set publishing bitrate via sender encodings if SDP feature is not supported
|
|
13663
|
+
mediaConnection.setPublishingBitrate(minBitrate, maxBitrate);
|
|
13664
|
+
}
|
|
13665
|
+
}
|
|
13666
|
+
return sdp;
|
|
13667
|
+
};
|
|
13668
|
+
|
|
13578
13669
|
module.exports = {
|
|
13579
13670
|
isEmptyObject,
|
|
13580
13671
|
copyObjectToArray,
|
|
@@ -13585,7 +13676,8 @@ module.exports = {
|
|
|
13585
13676
|
logger,
|
|
13586
13677
|
stripCodecs,
|
|
13587
13678
|
getCurrentCodecAndSampleRate,
|
|
13588
|
-
isPromise
|
|
13679
|
+
isPromise,
|
|
13680
|
+
setPublishingBitrate
|
|
13589
13681
|
};
|
|
13590
13682
|
|
|
13591
13683
|
},{}],45:[function(require,module,exports){
|
|
@@ -13660,7 +13752,7 @@ var createConnection = function (options) {
|
|
|
13660
13752
|
if (localVideo.srcObject) {
|
|
13661
13753
|
localVideo.id = id + "-local";
|
|
13662
13754
|
setContentHint(localVideo.srcObject, videoContentHint);
|
|
13663
|
-
connection
|
|
13755
|
+
addStreamTracks(connection, localVideo.srcObject);
|
|
13664
13756
|
} else {
|
|
13665
13757
|
localVideo = null;
|
|
13666
13758
|
}
|
|
@@ -13704,7 +13796,7 @@ var createConnection = function (options) {
|
|
|
13704
13796
|
localVideo = cachedVideo;
|
|
13705
13797
|
localVideo.id = id;
|
|
13706
13798
|
setContentHint(localVideo.srcObject, videoContentHint);
|
|
13707
|
-
connection
|
|
13799
|
+
addStreamTracks(connection, localVideo.srcObject);
|
|
13708
13800
|
}
|
|
13709
13801
|
} else {
|
|
13710
13802
|
// There is a custom video element, get its id if set #WCS-3606
|
|
@@ -13750,7 +13842,12 @@ var createConnection = function (options) {
|
|
|
13750
13842
|
track.contentHint = hint;
|
|
13751
13843
|
}
|
|
13752
13844
|
});
|
|
13753
|
-
}
|
|
13845
|
+
}
|
|
13846
|
+
function addStreamTracks(pc, stream) {
|
|
13847
|
+
stream.getTracks().forEach((track) => {
|
|
13848
|
+
pc.addTrack(track, stream);
|
|
13849
|
+
});
|
|
13850
|
+
}
|
|
13754
13851
|
connection.ontrack = function (event) {
|
|
13755
13852
|
if (remoteVideo) {
|
|
13756
13853
|
remoteVideo.srcObject = event.streams[0];
|
|
@@ -14364,6 +14461,21 @@ var createConnection = function (options) {
|
|
|
14364
14461
|
screenShare = false;
|
|
14365
14462
|
};
|
|
14366
14463
|
|
|
14464
|
+
var setPublishingBitrate = function(minBitrate, maxBitrate) {
|
|
14465
|
+
let senders = connection.getSenders();
|
|
14466
|
+
senders.forEach((sender) => {
|
|
14467
|
+
if (sender.track.kind == "video" && maxBitrate) {
|
|
14468
|
+
let parameters = sender.getParameters();
|
|
14469
|
+
for (let i = 0; i < parameters.encodings.length; i++) {
|
|
14470
|
+
if (!parameters.encodings[i].maxBitrate) {
|
|
14471
|
+
parameters.encodings[i].maxBitrate = maxBitrate * 1000;
|
|
14472
|
+
}
|
|
14473
|
+
}
|
|
14474
|
+
sender.setParameters(parameters).then(() => {});
|
|
14475
|
+
}
|
|
14476
|
+
});
|
|
14477
|
+
};
|
|
14478
|
+
|
|
14367
14479
|
var exports = {};
|
|
14368
14480
|
exports.state = state;
|
|
14369
14481
|
exports.createOffer = createOffer;
|
|
@@ -14390,6 +14502,7 @@ var createConnection = function (options) {
|
|
|
14390
14502
|
exports.switchMic = switchMic;
|
|
14391
14503
|
exports.switchToScreen = switchToScreen;
|
|
14392
14504
|
exports.switchToCam = switchToCam;
|
|
14505
|
+
exports.setPublishingBitrate = setPublishingBitrate;
|
|
14393
14506
|
connections[id] = exports;
|
|
14394
14507
|
resolve(exports);
|
|
14395
14508
|
});
|