@flashphoner/websdk 2.0.219 → 2.0.221

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.
@@ -1,4 +1,4 @@
1
- Web SDK - 2.0.219
1
+ Web SDK - 2.0.221
2
2
 
3
3
  [Download builds](https://docs.flashphoner.com/display/WEBSDK2EN/Web+SDK+release+notes)
4
4
 
@@ -6,12 +6,14 @@
6
6
  <link rel="stylesheet" href="../../dependencies/bootstrap/css/bootstrap.css">
7
7
  <link rel="stylesheet" href="../../dependencies/bootstrap/font-awesome/css/font-awesome.min.css">
8
8
  <link rel="stylesheet" href="console.css">
9
+ <link rel="stylesheet" type="text/css" href="jquery.dataTables.css">
9
10
  <title>Console</title>
10
11
  <script type="text/javascript" src="../../dependencies/jquery/jquery-1.12.0.js"></script>
11
12
  <script type="text/javascript" src="../../dependencies/jquery/jquery-ui.js"></script>
12
13
  <script type="text/javascript" src="../../dependencies/bootstrap/js/bootstrap.min.js"></script>
13
14
  <script type="text/javascript" src="../../dependencies/js/utils.js"></script>
14
15
  <script type="text/javascript" src="../../../../flashphoner-rest-api.js"></script>
16
+ <script type="text/javascript" charset="utf8" src="jquery.dataTables.js"></script>
15
17
  <script type="text/javascript" src="console.js"></script>
16
18
  </head>
17
19
  <body>
@@ -19,7 +21,7 @@
19
21
  <div class="row vdivide" id="controls">
20
22
  <div class="col-sm-4">
21
23
  <div id="addNodeForm" class="input-group">
22
- <input class="form-control" type="text" id="nodeIp" value="" placeholder="Node ip/domain name">
24
+ <input class="form-control" type="text" id="nodeIp" placeholder="Node ip/domain name">
23
25
  <span class="input-group-btn">
24
26
  <button id="addNodeFormSubmit" type="button" class="btn btn-primary">Add node</button>
25
27
  </span>
@@ -59,6 +61,8 @@
59
61
  <button id="callStressBatchModalBtn" data-toggle='modal' data-target='#callStressBatchModal' type="button" class="btn btn-default btn-success btn-block">Stress Call</button>
60
62
  <button id="streamStressBatchModalBtn" data-toggle='modal' data-target='#streamStressBatchModal' type="button" class="btn btn-default btn-success btn-block">Stress Play Stream</button>
61
63
  <button id="streamPublishStressBatchModalBtn" data-toggle='modal' data-target='#streamPublishStressBatchModal' type="button" class="btn btn-default btn-success btn-block">Stress Publish Stream</button>
64
+ <hr>
65
+ <button id="metricsModalBtn" data-toggle='modal' data-target='#streamMetricsModal' type="button" class="btn btn-default btn-success btn-block">Monitoring</button>
62
66
  </div>
63
67
  <div class="col-sm-9">
64
68
  <form class="form-inline" action="">
@@ -560,6 +564,150 @@
560
564
  </div>
561
565
  </div>
562
566
  </div>
567
+ <div id="streamMetricsModal" class="modal fade" role="dialog">
568
+ <div class="modal-dialog" style="width: 1250px">
569
+ <div class="modal-content">
570
+ <div class="modal-header">
571
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
572
+ <h4>Stream monitoring</h4>
573
+ </div>
574
+ <div class="modal-body">
575
+ <form action="" id="streamMonitoringForm">
576
+ <div class="form-group">
577
+ <label for="mediaTypeFilter">Stream type</label>
578
+ <select name="mediaTypeFilter" id="mediaTypeFilter">
579
+ <option value="all" selected>All</option>
580
+ <option value="publish">Publish</option>
581
+ <option value="play">Play</option>
582
+ </select>
583
+ </div>
584
+ <div class="form-group">
585
+ <label for="allStreams"></label>
586
+ <input type="checkbox" id="allStreams" value="true" checked> All streams
587
+ </div>
588
+ <div class="form-group">
589
+ <label for="monitoredStreamName"><span class="glyphicon glyphicon-play-circle"></span> Stream name</label>
590
+ <input type="text" class="form-control" id="monitoredStreamName" placeholder="Stream name" disabled >
591
+ </div>
592
+ <!-- <div class="form-group">-->
593
+ <!-- <label for="monitoringPollInterval"><span class="glyphicon glyphicon-play-circle"></span> Poll interval</label>-->
594
+ <!-- <input type="text" class="form-control" id="monitoringPollInterval" value="1000" placeholder="Polling interval (ms)">-->
595
+ <!-- </div>-->
596
+ <div class="form-group">
597
+ <label for="mediaProviderFilter">Media provider</label>
598
+ <select name="mediaProviderFilter" id="mediaProviderFilter">
599
+ <option value="all" selected>All</option>
600
+ <option value="WebRTC">WebRTC</option>
601
+ <option value="Flash">RTMP</option>
602
+ </select>
603
+ </div>
604
+ <div class="form-group">
605
+ <label for="streamTypeThresholdFilter">Apply filters to</label>
606
+ <select name="streamTypeThresholdFilter" id="streamTypeThresholdFilter">
607
+ <option value="all" selected>All</option>
608
+ <option value="publish">Published</option>
609
+ <option value="play">Playing</option>
610
+ </select>
611
+ </div>
612
+ <div class="panel panel-default">
613
+ <div class="panel-heading">Metric thresholds</div>
614
+ <table class="table">
615
+ <thead>
616
+ <tr>
617
+ <th>VIDEO_RATE</th>
618
+ <th>VIDEO_FPS</th>
619
+ <th>VIDEO_NACK</th>
620
+ <th>VIDEO_B_FRAMES</th>
621
+ </tr>
622
+ </thead>
623
+ <tbody>
624
+ <tr>
625
+ <td>
626
+ <input id="videoRateThreshold" type="text" class="text" value="0">
627
+ <select id="videoRateThresholdFilter">
628
+ <option value="eq">Equal</option>
629
+ <option selected value="gt">Greater than</option>
630
+ <option value="goe">Greater or equal</option>
631
+ <option value="lt">Lower than</option>
632
+ <option value="loe">Lower or equal</option>
633
+ </select>
634
+ </td>
635
+ <td>
636
+ <input id="videoFpsThreshold" type="text" class="text" value="0">
637
+ <select id="videoFpsThresholdFilter">
638
+ <option value="eq">Equal</option>
639
+ <option selected value="gt">Greater than</option>
640
+ <option value="goe">Greater or equal</option>
641
+ <option value="lt">Lower than</option>
642
+ <option value="loe">Lower or equal</option>
643
+ </select>
644
+ </td>
645
+ <td>
646
+ <input id="videoNackThreshold" type="text" class="text" value="0">
647
+ <select id="videoNackThresholdFilter">
648
+ <option selected value="eq">Equal</option>
649
+ <option value="gt">Greater than</option>
650
+ <option value="goe">Greater or equal</option>
651
+ <option value="lt">Lower than</option>
652
+ <option value="loe">Lower or equal</option>
653
+ </select>
654
+ </td>
655
+ <td>
656
+ <input id="videoBFramesThreshold" type="text" class="text" value="0">
657
+ <select id="videoBFramesThresholdFilter">
658
+ <option selected value="eq">Equal</option>
659
+ <option value="gt">Greater than</option>
660
+ <option value="goe">Greater or equal</option>
661
+ <option value="lt">Lower than</option>
662
+ <option value="loe">Lower or equal</option>
663
+ </select>
664
+ </td>
665
+ </tr>
666
+ </tbody>
667
+ </table>
668
+ </div>
669
+
670
+ <button id="streamMonitoring" type="button" class="btn btn-default btn-success btn-block"><span class="glyphicon glyphicon-off"></span> Start</button>
671
+ </form>
672
+ </div>
673
+ <div class="modal-footer">
674
+ <button type="button" class="btn btn-default pull-left" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> Cancel</button>
675
+ </div>
676
+ </div>
677
+ </div>
678
+ </div>
679
+ <div id="streamMetricsDetailsModal" class="modal fade" role="dialog">
680
+ <div class="modal-dialog" style="width: 100%">
681
+ <div class="modal-content">
682
+ <div class="modal-header">
683
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
684
+ <h4>Stream metrics details</h4>
685
+ </div>
686
+ <div class="modal-body">
687
+ <table id="metricsTable" class="metricsTable" width="100%">
688
+ <thead>
689
+ <tr>
690
+ <th></th>
691
+ <th>Name</th>
692
+ <th>Type</th>
693
+ <th>WIDTH</th>
694
+ <th>HEIGHT</th>
695
+ <th>V_RATE</th>
696
+ <th>NACK</th>
697
+ <th>B_FRAMES</th>
698
+ <th>V_LOST</th>
699
+ <th>FPS</th>
700
+ </tr>
701
+ </thead>
702
+ </table>
703
+ </div>
704
+ <div class="modal-footer">
705
+ <button type="button" class="btn btn-default pull-left" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> Cancel</button>
706
+ <button type="button" class="btn btn-default pull-right" id="refreshMetricsBtn"><span class="glyphicon glyphicon-refresh"></span> Refresh</button>
707
+ </div>
708
+ </div>
709
+ </div>
710
+ </div>
563
711
  <div id="warningModal" class="modal fade" role="dialog">
564
712
  <div class="modal-dialog">
565
713
  <div class="modal-content">
@@ -112,6 +112,25 @@ $(function() {
112
112
  $("#streamPublishStressBatch").on("click", function(e){
113
113
  streamPublishStressTest();
114
114
  });
115
+ $("#streamMonitoring").on("click", function(e){
116
+ streamMonitoring();
117
+ });
118
+ $('#streamMetricsModal').on('show.bs.modal', function(e) {
119
+ populateNodes($("#streamMonitoringNodes"));
120
+ });
121
+ $("#allStreams").on("click", function(e){
122
+ $("#monitoredStreamName").prop('disabled', $('#allStreams').is(':checked'));
123
+ })
124
+ $('#streamMetricsDetailsModal').on('hidden.bs.modal', function(e) {
125
+ $('#refreshMetricsBtn').off('click');
126
+ if ( $.fn.dataTable.isDataTable('#metricsTable') ) {
127
+ console.log("Destroy data table");
128
+ const table = $('#metricsTable').DataTable();
129
+ $('#metricsTable tbody').off('click', 'td.dt-control');
130
+ table.destroy();
131
+ }
132
+ });
133
+
115
134
 
116
135
  $("#generalInfoTable").sortable();
117
136
  $("#streamStressMode").change(function() {
@@ -126,9 +145,16 @@ $(function() {
126
145
  });
127
146
 
128
147
  function createNode(id, ip) {
129
- var api = FlashphonerRestApi.instance("http://"+ip+":8081", "http://"+ip+":8081");
148
+ let nodeIp = ip;
149
+ let port = 8081;
150
+ if (ip.indexOf(':') !== -1) {
151
+ port = ip.substring(ip.indexOf(":") + 1);
152
+ nodeIp = ip.substring(0, ip.indexOf(":"));
153
+ }
154
+ var api = FlashphonerRestApi.instance("http://"+nodeIp+":"+port, "http://"+nodeIp+":"+port);
130
155
  api.id = id;
131
- api.ip = ip;
156
+ api.ip = nodeIp;
157
+ api.port = port;
132
158
  api.tests = [];
133
159
  var state = NODE_STATE.NEW;
134
160
  var pollState = function(){
@@ -242,11 +268,19 @@ function createNode(id, ip) {
242
268
  break;
243
269
  }
244
270
  };
271
+ api.getTranscodingGroupStat = async function() {
272
+ const result = await fetch("http://" + nodeIp + ":" + port + "/?action=stat&format=json&groups=transcoding_stats")
273
+ const json = await result.json();
274
+ return json;
275
+ }
245
276
  return api;
246
277
  }
247
278
 
248
279
  function addNode(ip) {
249
280
  var id = ip.replace(/\./g, "");
281
+ if (ip.indexOf(':') !== -1) {
282
+ id = ip.substring(ip.indexOf(":") + 1).replace(/\./g, "");
283
+ }
250
284
  if (nodes[id]) {
251
285
  return;
252
286
  }
@@ -1037,6 +1071,252 @@ function streamPlayStressTest() {
1037
1071
  $("#streamStressBatchModal").modal('hide');
1038
1072
  }
1039
1073
 
1074
+ async function streamMonitoring() {
1075
+
1076
+ const node = getActiveNode();
1077
+ const mediaTypeFilter = $('#mediaTypeFilter option:selected').val();
1078
+ const mediaProviderFilter = $('#mediaProviderFilter option:selected').val();
1079
+ const streamTypeThresholdFilter = $('#streamTypeThresholdFilter option:selected').val();
1080
+ const isAllStreams = $('#allStreams').is(':checked');
1081
+ const streamName = $('#monitoredStreamName').val();
1082
+
1083
+
1084
+ // Metric thresholds
1085
+ const thresholds = {
1086
+ VIDEO_RATE: {
1087
+ value: parseInt($('#videoRateThreshold').val()),
1088
+ filter: $('#videoRateThresholdFilter option:selected').val()
1089
+ },
1090
+ VIDEO_FPS: {
1091
+ value: parseInt($('#videoFpsThreshold').val()),
1092
+ filter: $('#videoFpsThresholdFilter option:selected').val(),
1093
+ },
1094
+ VIDEO_NACK: {
1095
+ value: parseInt($('#videoNackThreshold').val()),
1096
+ filter: $('#videoNackThresholdFilter option:selected').val()
1097
+ },
1098
+ VIDEO_B_FRAMES: {
1099
+ value: parseInt($('#videoBFramesThreshold').val()),
1100
+ filter: $('#videoBFramesThresholdFilter option:selected').val()
1101
+ }
1102
+ }
1103
+
1104
+ const formatExtendedData = (row, data) => {
1105
+ const distributors = data.distributors;
1106
+ const info = $(
1107
+ '<div> <b>SessionId:</b> ' + data.sessionId + '</div>' +
1108
+ '<div> <b>MediaProvider:</b> ' + data.mediaProvider + '</div>'
1109
+ );
1110
+
1111
+ // Append nested distributor stat table
1112
+ if (data.published) {
1113
+ const table = $('<table id="' + data.mediaSessionId + '" class="display" width="100%"/>');
1114
+ table.appendTo(info);
1115
+
1116
+ const distributorsData = [];
1117
+ Object.keys(distributors).forEach(key => {
1118
+ const stat = distributors[key];
1119
+ distributorsData.push({
1120
+ id: key,
1121
+ codec: stat.codec,
1122
+ queueSize: stat.queueSize,
1123
+ resolution: stat.resolution,
1124
+ videoGroupsSendStats: stat.videoGroupsSendStats
1125
+ })
1126
+ })
1127
+ const distributorsTable = table.DataTable({
1128
+ data: distributorsData,
1129
+ columns: [
1130
+ {
1131
+ className: 'sub-dt-control',
1132
+ orderable: false,
1133
+ data: null,
1134
+ defaultContent: ''
1135
+ },
1136
+ {data: 'id', title: 'Id'},
1137
+ {data: 'codec', title: 'Codec'},
1138
+ {data: 'resolution', title: 'Resolution'},
1139
+ {data: 'queueSize', title: 'QueueSize'}
1140
+ ],
1141
+ paging: false,
1142
+
1143
+ });
1144
+ const format = (d) => {
1145
+ let result = '';
1146
+ if (d.videoGroupsSendStats) {
1147
+ Object.keys(d.videoGroupsSendStats).forEach(key => {
1148
+ let groupInfo = '';
1149
+ const group = d.videoGroupsSendStats[key];
1150
+ groupInfo += '<div><b>ID:</b> ' + key + '</div>';
1151
+ groupInfo += '<p>';
1152
+ group.videoGroupSendStats.forEach(participant => {
1153
+ groupInfo += '<div><b>ID</b>: ' + participant.id + ' ; <b>maxSendTime:</b> ' + participant.maxSendTime + ' ; <b>minSendTime:</b> ' + participant.minSendTime + ' ; <b>averageSendTime:</b> ' + participant.averageSendTime + '</div>';
1154
+ });
1155
+ groupInfo += '</p>';
1156
+ result += groupInfo;
1157
+ })
1158
+ }
1159
+ return result;
1160
+ }
1161
+ // Add event listener for opening and closing details
1162
+ table.on('click', 'td.sub-dt-control', function () {
1163
+ const tr = $(this).closest('tr');
1164
+ const row = distributorsTable.row(tr);
1165
+
1166
+ if (row.child.isShown()) {
1167
+ // This row is already open - close it
1168
+ row.child.hide();
1169
+ tr.removeClass('shown');
1170
+ } else {
1171
+ // Open this row
1172
+ row.child(format(row.data())).show();
1173
+ tr.addClass('shown');
1174
+ }
1175
+ });
1176
+ }
1177
+ row.child( info ).show();
1178
+
1179
+ }
1180
+
1181
+ //Todo: remove netsted table and do off event listeners
1182
+ const destroyDataTable = () => {
1183
+ if ( $.fn.dataTable.isDataTable('#metricsTable') ) {
1184
+ const table = $('#metricsTable').DataTable();
1185
+ $('#metricsTable tbody').off('click', 'td.dt-control');
1186
+ table.destroy();
1187
+ }
1188
+ }
1189
+
1190
+ const createDataTable = () => {
1191
+ if ( $.fn.dataTable.isDataTable('#metricsTable') ) {
1192
+ destroyDataTable();
1193
+ }
1194
+ return $('#metricsTable').DataTable({
1195
+ columns: [
1196
+ {
1197
+ className: 'dt-control',
1198
+ orderable: false,
1199
+ data: null,
1200
+ defaultContent: ''
1201
+ },
1202
+ {data: 'name'},
1203
+ {data: 'mediaType'},
1204
+ {data: 'metrics.VIDEO_WIDTH'},
1205
+ {data: 'metrics.VIDEO_HEIGHT'},
1206
+ {data: 'metrics.VIDEO_RATE'},
1207
+ {data: 'metrics.VIDEO_NACK'},
1208
+ {data: 'metrics.VIDEO_B_FRAMES'},
1209
+ {data: 'metrics.VIDEO_LOST'},
1210
+ {data: 'metrics.VIDEO_FPS'}
1211
+ ]
1212
+ });
1213
+ }
1214
+
1215
+ const metricTable = createDataTable();
1216
+
1217
+ const feedDataTable = (data) => {
1218
+
1219
+ metricTable.clear();
1220
+ metricTable.rows.add(data);
1221
+ metricTable.draw();
1222
+
1223
+ // Add event listener for opening and closing details
1224
+ $('#metricsTable tbody').on('click', 'td.dt-control', function() {
1225
+ const tr = $(this).closest('tr');
1226
+ const row = metricTable.row(tr);
1227
+
1228
+ if (row.child.isShown()) {
1229
+ // This row is already open - close it
1230
+ row.child.hide();
1231
+ tr.removeClass('shown');
1232
+ } else {
1233
+ // Open this row
1234
+ row.child(formatExtendedData(row, row.data())).show();
1235
+ tr.addClass('shown');
1236
+ }
1237
+ });
1238
+ }
1239
+
1240
+ const transcodingGroupStat = await node.getTranscodingGroupStat();
1241
+
1242
+ const filterData = (data) => {
1243
+ const filteredData = [];
1244
+ let tmpData = data;
1245
+ if (mediaProviderFilter !== "all") {
1246
+ tmpData = data.filter(stream => stream.mediaProvider === mediaProviderFilter);
1247
+ }
1248
+ if (mediaTypeFilter === "play") {
1249
+ tmpData = data.filter(stream => stream.mediaType === mediaTypeFilter);
1250
+ }
1251
+
1252
+ const checkMetricThresholds = (metrics) => {
1253
+ const matched = [];
1254
+ Object.keys(thresholds).forEach(key => {
1255
+ if (thresholds[key].value >= 0) {
1256
+ const metric = parseInt(metrics[key]);
1257
+ switch (thresholds[key].filter) {
1258
+ case "eq":
1259
+ matched.push(metric === thresholds[key].value);
1260
+ break;
1261
+ case "gt":
1262
+ matched.push(metric > thresholds[key].value);
1263
+ break;
1264
+ case "lt":
1265
+ matched.push(metric < thresholds[key].value);
1266
+ break;
1267
+ case "goe":
1268
+ matched.push(metric >= thresholds[key].value);
1269
+ break;
1270
+ case "loe":
1271
+ matched.push(metric <= thresholds[key].value);
1272
+ break;
1273
+ }
1274
+ } else {
1275
+ matched.push(false);
1276
+ }
1277
+ })
1278
+ return !matched.some(el => el === false);
1279
+ }
1280
+
1281
+ tmpData.forEach(stream => {
1282
+ const published = stream.published;
1283
+ if (streamTypeThresholdFilter === "all" || stream.mediaType === streamTypeThresholdFilter) {
1284
+ if (checkMetricThresholds(stream.metrics)) {
1285
+ if (published) {
1286
+ const stat = transcodingGroupStat.transcoding_stats.transcoding_video_full_info[stream.name];
1287
+ stream['distributors'] = stat.distributors;
1288
+ }
1289
+ filteredData.push(stream);
1290
+ }
1291
+ }
1292
+ })
1293
+ return filteredData;
1294
+ }
1295
+
1296
+ const publishedOnly = (mediaTypeFilter === "publish") ? true : null;
1297
+
1298
+ const getStreams = () => {
1299
+ const streams = node.stream.find(null, (isAllStreams) ? null : streamName, publishedOnly, true);
1300
+ streams
1301
+ .then((result) => {
1302
+ console.log("rest response: ", result);
1303
+ feedDataTable(filterData(result));
1304
+ })
1305
+ .catch((e) => {
1306
+ console.log("Failed to get all streams ", e);
1307
+ })
1308
+ }
1309
+
1310
+ getStreams();
1311
+
1312
+ $('#refreshMetricsBtn').on('click', () => {
1313
+ getStreams();
1314
+ })
1315
+
1316
+ $("#streamMetricsModal").modal('hide');
1317
+ $("#streamMetricsDetailsModal").modal('show');
1318
+ }
1319
+
1040
1320
  function streamPublishStressTest() {
1041
1321
  if (!$("#streamPublishStressBatchNodes").val()) {
1042
1322
  $('#warningModal').modal();