@ceeblue/web-utils 2.6.1 → 3.1.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/README.md +7 -0
- package/dist/web-utils.d.ts +262 -107
- package/dist/web-utils.js +559 -195
- package/dist/web-utils.js.map +1 -1
- package/dist/web-utils.min.js +1 -1
- package/dist/web-utils.min.js.map +1 -1
- package/package.json +1 -1
package/dist/web-utils.js
CHANGED
|
@@ -275,87 +275,6 @@ class BinaryWriter {
|
|
|
275
275
|
this._view = undefined; // release view
|
|
276
276
|
return this;
|
|
277
277
|
}
|
|
278
|
-
}/**
|
|
279
|
-
* Copyright 2024 Ceeblue B.V.
|
|
280
|
-
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
281
|
-
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
282
|
-
*/
|
|
283
|
-
/**
|
|
284
|
-
* BitReader allows to read binary data bit by bit
|
|
285
|
-
*/
|
|
286
|
-
class BitReader {
|
|
287
|
-
constructor(data) {
|
|
288
|
-
if ('buffer' in data) {
|
|
289
|
-
this._data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
this._data = new Uint8Array(data);
|
|
293
|
-
}
|
|
294
|
-
this._size = this._data.byteLength;
|
|
295
|
-
this._position = 0;
|
|
296
|
-
this._bit = 0;
|
|
297
|
-
}
|
|
298
|
-
data() {
|
|
299
|
-
return this._data;
|
|
300
|
-
}
|
|
301
|
-
size() {
|
|
302
|
-
return this._size;
|
|
303
|
-
}
|
|
304
|
-
available() {
|
|
305
|
-
return (this._size - this._position) * 8 - this._bit;
|
|
306
|
-
}
|
|
307
|
-
next(count = 1) {
|
|
308
|
-
let gotten = 0;
|
|
309
|
-
while (this._position !== this._size && count--) {
|
|
310
|
-
++gotten;
|
|
311
|
-
if (++this._bit === 8) {
|
|
312
|
-
this._bit = 0;
|
|
313
|
-
++this._position;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return gotten;
|
|
317
|
-
}
|
|
318
|
-
read(count = 1) {
|
|
319
|
-
let result = 0;
|
|
320
|
-
while (this._position !== this._size && count--) {
|
|
321
|
-
result <<= 1;
|
|
322
|
-
if (this._data[this._position] & (0x80 >> this._bit++)) {
|
|
323
|
-
result |= 1;
|
|
324
|
-
}
|
|
325
|
-
if (this._bit === 8) {
|
|
326
|
-
this._bit = 0;
|
|
327
|
-
++this._position;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return result;
|
|
331
|
-
}
|
|
332
|
-
read8() {
|
|
333
|
-
return this.read(8);
|
|
334
|
-
}
|
|
335
|
-
read16() {
|
|
336
|
-
return this.read(16);
|
|
337
|
-
}
|
|
338
|
-
read24() {
|
|
339
|
-
return this.read(24);
|
|
340
|
-
}
|
|
341
|
-
read32() {
|
|
342
|
-
return this.read(32);
|
|
343
|
-
}
|
|
344
|
-
readExpGolomb() {
|
|
345
|
-
let i = 0;
|
|
346
|
-
while (!this.read()) {
|
|
347
|
-
if (!this.available()) {
|
|
348
|
-
return 0;
|
|
349
|
-
}
|
|
350
|
-
++i;
|
|
351
|
-
}
|
|
352
|
-
const result = this.read(i);
|
|
353
|
-
if (i > 15) {
|
|
354
|
-
console.warn('Exponential-Golomb code exceeding unsigned 16 bits');
|
|
355
|
-
return 0;
|
|
356
|
-
}
|
|
357
|
-
return result + (1 << i) - 1;
|
|
358
|
-
}
|
|
359
278
|
}/******************************************************************************
|
|
360
279
|
Copyright (c) Microsoft Corporation.
|
|
361
280
|
|
|
@@ -563,12 +482,15 @@ function stringify(obj, params = {}) {
|
|
|
563
482
|
}
|
|
564
483
|
return (res += space + ']');
|
|
565
484
|
}
|
|
566
|
-
let res = '';
|
|
485
|
+
let res = '{';
|
|
567
486
|
for (const name in obj) {
|
|
568
|
-
|
|
569
|
-
|
|
487
|
+
if (res.length > 1) {
|
|
488
|
+
res += ',';
|
|
489
|
+
}
|
|
490
|
+
res += space + name + ':';
|
|
491
|
+
res += stringify(obj[name], Object.assign(Object.assign({}, params), { recursion: params.recursion - 1 })) + space;
|
|
570
492
|
}
|
|
571
|
-
return (res +=
|
|
493
|
+
return (res += '}');
|
|
572
494
|
}
|
|
573
495
|
/**
|
|
574
496
|
* Encode a string to a binary representation
|
|
@@ -728,38 +650,337 @@ function trimEnd(value, chars = ' ') {
|
|
|
728
650
|
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
729
651
|
*/
|
|
730
652
|
/**
|
|
731
|
-
*
|
|
653
|
+
* Log levels
|
|
654
|
+
*/
|
|
655
|
+
var LogLevel;
|
|
656
|
+
(function (LogLevel) {
|
|
657
|
+
LogLevel["ERROR"] = "error";
|
|
658
|
+
LogLevel["WARN"] = "warn";
|
|
659
|
+
LogLevel["INFO"] = "info";
|
|
660
|
+
LogLevel["DEBUG"] = "debug";
|
|
661
|
+
})(LogLevel || (LogLevel = {}));
|
|
662
|
+
// check coder issuer: everytime we don't forget to use the built Log
|
|
663
|
+
let _logging = 0;
|
|
664
|
+
setInterval(() => {
|
|
665
|
+
console.assert(_logging === 0, _logging.toFixed(), 'calls to log was useless');
|
|
666
|
+
}, 10000);
|
|
667
|
+
// !cb-override-log-level
|
|
668
|
+
const _overrideLogLevel = options()['!cb-override-log-level'];
|
|
669
|
+
const _charLevels = new Array(128);
|
|
670
|
+
_charLevels[101] = _charLevels[69] = 1; // error
|
|
671
|
+
_charLevels[119] = _charLevels[87] = 2; // warn
|
|
672
|
+
_charLevels[105] = _charLevels[73] = 3; // info
|
|
673
|
+
_charLevels[100] = _charLevels[68] = 4; // debug
|
|
674
|
+
/**
|
|
675
|
+
* Log instance
|
|
676
|
+
*/
|
|
677
|
+
class Log {
|
|
678
|
+
get error() {
|
|
679
|
+
return this._bind(LogLevel.ERROR);
|
|
680
|
+
}
|
|
681
|
+
get warn() {
|
|
682
|
+
return this._bind(LogLevel.WARN);
|
|
683
|
+
}
|
|
684
|
+
get info() {
|
|
685
|
+
return this._bind(LogLevel.INFO);
|
|
686
|
+
}
|
|
687
|
+
get debug() {
|
|
688
|
+
return this._bind(LogLevel.DEBUG);
|
|
689
|
+
}
|
|
690
|
+
constructor(log, ...args) {
|
|
691
|
+
if (!args.length) {
|
|
692
|
+
// cannot have 0 args to be called correctly!
|
|
693
|
+
args.push(undefined);
|
|
694
|
+
}
|
|
695
|
+
this._args = args;
|
|
696
|
+
this._log = log;
|
|
697
|
+
++_logging;
|
|
698
|
+
}
|
|
699
|
+
_onLog(localLog, level) {
|
|
700
|
+
var _a, _b;
|
|
701
|
+
// we take like log-level by priority order:
|
|
702
|
+
// 1- the overrideLogLevel
|
|
703
|
+
// 2- the localLog.level
|
|
704
|
+
// 3- the global log.level
|
|
705
|
+
// 4- LogLevel.INFO
|
|
706
|
+
const logLevel = (_b = (_a = _overrideLogLevel !== null && _overrideLogLevel !== void 0 ? _overrideLogLevel : localLog.level) !== null && _a !== void 0 ? _a : log.level) !== null && _b !== void 0 ? _b : LogLevel.INFO;
|
|
707
|
+
if (logLevel === false) {
|
|
708
|
+
// explicit null, no log at all!
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
if (logLevel !== true && _charLevels[level.charCodeAt(0)] > _charLevels[logLevel.charCodeAt(0)]) {
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
if (localLog.on) {
|
|
715
|
+
localLog.on(level, this._args);
|
|
716
|
+
}
|
|
717
|
+
return this._args.length ? true : false;
|
|
718
|
+
}
|
|
719
|
+
_bind(level) {
|
|
720
|
+
if (!this._done) {
|
|
721
|
+
this._done = true;
|
|
722
|
+
--_logging;
|
|
723
|
+
}
|
|
724
|
+
// call the global onLog in first (global filter)
|
|
725
|
+
if (!this._onLog(log, level)) {
|
|
726
|
+
return EMPTY_FUNCTION;
|
|
727
|
+
}
|
|
728
|
+
// call the local onLog (local filter)
|
|
729
|
+
if (this._log !== log && !this._onLog(this._log, level)) {
|
|
730
|
+
return EMPTY_FUNCTION;
|
|
731
|
+
}
|
|
732
|
+
// if not intercepted display the log
|
|
733
|
+
return console[level].bind(console, ...this._args);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Inherits from this class to use logs
|
|
738
|
+
*/
|
|
739
|
+
class Loggable {
|
|
740
|
+
constructor() {
|
|
741
|
+
/**
|
|
742
|
+
* Start a log
|
|
743
|
+
* @param args
|
|
744
|
+
* @returns a Log object with the levels of log to call
|
|
745
|
+
*/
|
|
746
|
+
this.log = ((...args) => {
|
|
747
|
+
return new Log(this.log, ...args);
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Global log
|
|
753
|
+
*/
|
|
754
|
+
const log = ((...args) => {
|
|
755
|
+
return new Log(log, ...args);
|
|
756
|
+
});/**
|
|
757
|
+
* Copyright 2024 Ceeblue B.V.
|
|
758
|
+
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
759
|
+
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
760
|
+
*/
|
|
761
|
+
/**
|
|
762
|
+
* BitReader allows to read binary data bit by bit
|
|
763
|
+
*/
|
|
764
|
+
class BitReader extends Loggable {
|
|
765
|
+
constructor(data) {
|
|
766
|
+
super();
|
|
767
|
+
if ('buffer' in data) {
|
|
768
|
+
this._data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
this._data = new Uint8Array(data);
|
|
772
|
+
}
|
|
773
|
+
this._size = this._data.byteLength;
|
|
774
|
+
this._position = 0;
|
|
775
|
+
this._bit = 0;
|
|
776
|
+
}
|
|
777
|
+
data() {
|
|
778
|
+
return this._data;
|
|
779
|
+
}
|
|
780
|
+
size() {
|
|
781
|
+
return this._size;
|
|
782
|
+
}
|
|
783
|
+
available() {
|
|
784
|
+
return (this._size - this._position) * 8 - this._bit;
|
|
785
|
+
}
|
|
786
|
+
next(count = 1) {
|
|
787
|
+
let gotten = 0;
|
|
788
|
+
while (this._position !== this._size && count--) {
|
|
789
|
+
++gotten;
|
|
790
|
+
if (++this._bit === 8) {
|
|
791
|
+
this._bit = 0;
|
|
792
|
+
++this._position;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return gotten;
|
|
796
|
+
}
|
|
797
|
+
read(count = 1) {
|
|
798
|
+
let result = 0;
|
|
799
|
+
while (this._position !== this._size && count--) {
|
|
800
|
+
result <<= 1;
|
|
801
|
+
if (this._data[this._position] & (0x80 >> this._bit++)) {
|
|
802
|
+
result |= 1;
|
|
803
|
+
}
|
|
804
|
+
if (this._bit === 8) {
|
|
805
|
+
this._bit = 0;
|
|
806
|
+
++this._position;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return result;
|
|
810
|
+
}
|
|
811
|
+
read8() {
|
|
812
|
+
return this.read(8);
|
|
813
|
+
}
|
|
814
|
+
read16() {
|
|
815
|
+
return this.read(16);
|
|
816
|
+
}
|
|
817
|
+
read24() {
|
|
818
|
+
return this.read(24);
|
|
819
|
+
}
|
|
820
|
+
read32() {
|
|
821
|
+
return this.read(32);
|
|
822
|
+
}
|
|
823
|
+
readExpGolomb() {
|
|
824
|
+
let i = 0;
|
|
825
|
+
while (!this.read()) {
|
|
826
|
+
if (!this.available()) {
|
|
827
|
+
return 0;
|
|
828
|
+
}
|
|
829
|
+
++i;
|
|
830
|
+
}
|
|
831
|
+
const result = this.read(i);
|
|
832
|
+
if (i > 15) {
|
|
833
|
+
this.log('Exponential-Golomb code exceeding unsigned 16 bits').warn();
|
|
834
|
+
return 0;
|
|
835
|
+
}
|
|
836
|
+
return result + (1 << i) - 1;
|
|
837
|
+
}
|
|
838
|
+
}/**
|
|
839
|
+
* Copyright 2024 Ceeblue B.V.
|
|
840
|
+
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
841
|
+
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
842
|
+
*/
|
|
843
|
+
/**
|
|
844
|
+
* Class to compute a weighted average byte rate over a specified time interval.
|
|
845
|
+
*
|
|
846
|
+
* This class continuously tracks data transmission and computes the byte rate
|
|
847
|
+
* based on a weighted average, considering both the duration and the number of
|
|
848
|
+
* bytes in each sample. It allows for real-time monitoring of bandwidth usage
|
|
849
|
+
* and provides mechanisms to dynamically adjust the measurement interval.
|
|
850
|
+
*
|
|
851
|
+
* Features:
|
|
852
|
+
* - Computes the byte rate using a **weighted average** approach.
|
|
853
|
+
* - Allows setting a custom interval for tracking.
|
|
854
|
+
* - Supports dynamic clipping to manually shrink the observation window.
|
|
732
855
|
*/
|
|
733
856
|
class ByteRate {
|
|
857
|
+
/**
|
|
858
|
+
* Raised when new bytes are added
|
|
859
|
+
*/
|
|
734
860
|
onBytes(bytes) { }
|
|
735
|
-
|
|
736
|
-
|
|
861
|
+
/**
|
|
862
|
+
* Returns the interval used for computing the byte rate
|
|
863
|
+
*/
|
|
864
|
+
get interval() {
|
|
865
|
+
return this._interval;
|
|
737
866
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
this.
|
|
867
|
+
/**
|
|
868
|
+
* Sets a new interval for computing the average byte rate
|
|
869
|
+
*/
|
|
870
|
+
set interval(value) {
|
|
871
|
+
this._interval = value;
|
|
872
|
+
this.updateSamples();
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Constructor initializes the ByteRate object with a specified interval (default: 1000ms).
|
|
876
|
+
* It sets up necessary variables to track byte rate over time.
|
|
877
|
+
*
|
|
878
|
+
* @param interval - Time interval in milliseconds to compute the byte rate.
|
|
879
|
+
*/
|
|
880
|
+
constructor(interval = 1000) {
|
|
881
|
+
this._interval = interval;
|
|
882
|
+
this.clear();
|
|
743
883
|
}
|
|
884
|
+
/**
|
|
885
|
+
* Returns the computed byte rate rounded to the nearest integer
|
|
886
|
+
*/
|
|
744
887
|
value() {
|
|
745
888
|
return Math.round(this.exact());
|
|
746
889
|
}
|
|
890
|
+
/**
|
|
891
|
+
* Computes the exact byte rate in bytes per second
|
|
892
|
+
*/
|
|
747
893
|
exact() {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
this._value = (this._bytes * 1000) / elapsed;
|
|
753
|
-
this._bytes = 0;
|
|
754
|
-
this._time = now;
|
|
755
|
-
}
|
|
756
|
-
return this._value;
|
|
894
|
+
// compute rate/s
|
|
895
|
+
this.updateSamples();
|
|
896
|
+
const duration = time() - this._time;
|
|
897
|
+
return duration ? (this._bytes / duration) * 1000 : 0;
|
|
757
898
|
}
|
|
899
|
+
/**
|
|
900
|
+
* Adds a new byte sample to the tracking system.
|
|
901
|
+
* Updates the list of samples and recomputes the byte rate
|
|
902
|
+
*
|
|
903
|
+
* @param bytes - Number of bytes added in this interval
|
|
904
|
+
*/
|
|
758
905
|
addBytes(bytes) {
|
|
906
|
+
var _a;
|
|
907
|
+
const time$1 = time();
|
|
908
|
+
const lastSample = this.updateSamples(time$1)[this._samples.length - 1];
|
|
909
|
+
const lastTime = (_a = lastSample === null || lastSample === void 0 ? void 0 : lastSample.time) !== null && _a !== void 0 ? _a : this._time;
|
|
910
|
+
if (time$1 > lastTime) {
|
|
911
|
+
this._samples.push({ bytes, time: time$1, clip: false });
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
// no new duration => attach byte to last-one
|
|
915
|
+
if (!lastSample) {
|
|
916
|
+
// Ignore, was before our ByteRate scope !
|
|
917
|
+
return this;
|
|
918
|
+
}
|
|
919
|
+
lastSample.bytes += bytes;
|
|
920
|
+
}
|
|
759
921
|
this._bytes += bytes;
|
|
760
922
|
this.onBytes(bytes);
|
|
761
923
|
return this;
|
|
762
924
|
}
|
|
925
|
+
/**
|
|
926
|
+
* Clears all recorded byte rate data.
|
|
927
|
+
*/
|
|
928
|
+
clear() {
|
|
929
|
+
this._bytes = 0;
|
|
930
|
+
this._time = time();
|
|
931
|
+
this._samples = [];
|
|
932
|
+
this._clip = false;
|
|
933
|
+
return this;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Clips the byte rate tracking by marking the last sample as clipped.
|
|
937
|
+
* If a previous clip exists, removes the clipped sample and all preceding samples.
|
|
938
|
+
* Allows to shrink the interval manually between two positions.
|
|
939
|
+
*/
|
|
940
|
+
clip() {
|
|
941
|
+
if (this._clip) {
|
|
942
|
+
this._clip = false;
|
|
943
|
+
let removes = 0;
|
|
944
|
+
for (const sample of this._samples) {
|
|
945
|
+
this._bytes -= sample.bytes;
|
|
946
|
+
++removes;
|
|
947
|
+
this._time = sample.time;
|
|
948
|
+
if (sample.clip) {
|
|
949
|
+
break;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
this._samples.splice(0, removes);
|
|
953
|
+
}
|
|
954
|
+
const lastSample = this._samples[this._samples.length - 1];
|
|
955
|
+
if (lastSample) {
|
|
956
|
+
lastSample.clip = true;
|
|
957
|
+
this._clip = true;
|
|
958
|
+
}
|
|
959
|
+
return this;
|
|
960
|
+
}
|
|
961
|
+
updateSamples(now = time()) {
|
|
962
|
+
// Remove obsolete sample
|
|
963
|
+
const timeOK = now - this._interval;
|
|
964
|
+
let removes = 0;
|
|
965
|
+
let sample;
|
|
966
|
+
while (this._time < timeOK && (sample = this._samples[removes])) {
|
|
967
|
+
this._bytes -= sample.bytes;
|
|
968
|
+
if (sample.clip) {
|
|
969
|
+
this._clip = sample.clip = false;
|
|
970
|
+
}
|
|
971
|
+
if (sample.time > timeOK) {
|
|
972
|
+
// only a part of the sample to delete !
|
|
973
|
+
sample.bytes *= (sample.time - timeOK) / (sample.time - this._time);
|
|
974
|
+
this._time = timeOK;
|
|
975
|
+
this._bytes += sample.bytes;
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
++removes;
|
|
979
|
+
this._time = sample.time;
|
|
980
|
+
}
|
|
981
|
+
this._samples.splice(0, removes);
|
|
982
|
+
return this._samples;
|
|
983
|
+
}
|
|
763
984
|
}/**
|
|
764
985
|
* Copyright 2024 Ceeblue B.V.
|
|
765
986
|
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
@@ -949,7 +1170,7 @@ function defineMediaExt(type, params) {
|
|
|
949
1170
|
break;
|
|
950
1171
|
}
|
|
951
1172
|
// Fix mediaExt in removing the possible '.' prefix
|
|
952
|
-
trimStart(params.mediaExt, '.');
|
|
1173
|
+
params.mediaExt = trimStart(params.mediaExt, '.');
|
|
953
1174
|
}
|
|
954
1175
|
/**
|
|
955
1176
|
* Build an URL from {@link Type | type} and {@link Params | params}
|
|
@@ -996,87 +1217,6 @@ function buildURL(type, params, protocol = 'wss') {
|
|
|
996
1217
|
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
997
1218
|
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
998
1219
|
*/
|
|
999
|
-
let _logging = 0;
|
|
1000
|
-
setInterval(() => {
|
|
1001
|
-
console.assert(_logging === 0, _logging.toFixed(), 'calls to log was useless');
|
|
1002
|
-
}, 10000);
|
|
1003
|
-
/**
|
|
1004
|
-
* Log types
|
|
1005
|
-
*/
|
|
1006
|
-
var LogType;
|
|
1007
|
-
(function (LogType) {
|
|
1008
|
-
LogType["ERROR"] = "error";
|
|
1009
|
-
LogType["WARN"] = "warn";
|
|
1010
|
-
LogType["INFO"] = "info";
|
|
1011
|
-
LogType["DEBUG"] = "debug";
|
|
1012
|
-
})(LogType || (LogType = {}));
|
|
1013
|
-
/**
|
|
1014
|
-
* Log instance
|
|
1015
|
-
*/
|
|
1016
|
-
class Log {
|
|
1017
|
-
get error() {
|
|
1018
|
-
return this._bind(LogType.ERROR);
|
|
1019
|
-
}
|
|
1020
|
-
get warn() {
|
|
1021
|
-
return this._bind(LogType.WARN);
|
|
1022
|
-
}
|
|
1023
|
-
get info() {
|
|
1024
|
-
return this._bind(LogType.INFO);
|
|
1025
|
-
}
|
|
1026
|
-
get debug() {
|
|
1027
|
-
return this._bind(LogType.DEBUG);
|
|
1028
|
-
}
|
|
1029
|
-
constructor(onLog, ...args) {
|
|
1030
|
-
if (!args.length) {
|
|
1031
|
-
// cannot have 0 args to be called correctly!
|
|
1032
|
-
args.push(undefined);
|
|
1033
|
-
}
|
|
1034
|
-
this._args = args;
|
|
1035
|
-
this._onLog = onLog;
|
|
1036
|
-
++_logging;
|
|
1037
|
-
}
|
|
1038
|
-
_bind(type) {
|
|
1039
|
-
if (!this._done) {
|
|
1040
|
-
this._done = true;
|
|
1041
|
-
--_logging;
|
|
1042
|
-
}
|
|
1043
|
-
// call the local onLog
|
|
1044
|
-
if (this._onLog) {
|
|
1045
|
-
this._onLog(type, this._args);
|
|
1046
|
-
}
|
|
1047
|
-
// call the global onLog
|
|
1048
|
-
if (this._args.length && log.on) {
|
|
1049
|
-
log.on(type, this._args);
|
|
1050
|
-
}
|
|
1051
|
-
// if not intercepted display the log
|
|
1052
|
-
return this._args.length ? console[type].bind(console, ...this._args) : EMPTY_FUNCTION;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
/**
|
|
1056
|
-
* Inherits from this class to use logs
|
|
1057
|
-
*/
|
|
1058
|
-
class Loggable {
|
|
1059
|
-
constructor() {
|
|
1060
|
-
/**
|
|
1061
|
-
* Start a log
|
|
1062
|
-
* @param args
|
|
1063
|
-
* @returns a Log object with the levels of log to call
|
|
1064
|
-
*/
|
|
1065
|
-
this.log = ((...args) => {
|
|
1066
|
-
return new Log(this.log.on, ...args);
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
/**
|
|
1071
|
-
* Global log
|
|
1072
|
-
*/
|
|
1073
|
-
const log = ((...args) => {
|
|
1074
|
-
return new Log(() => { }, ...args);
|
|
1075
|
-
});/**
|
|
1076
|
-
* Copyright 2024 Ceeblue B.V.
|
|
1077
|
-
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
1078
|
-
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
1079
|
-
*/
|
|
1080
1220
|
/**
|
|
1081
1221
|
* A advanced EventEmitter which allows to declare event as natural function in the inheriting children class,
|
|
1082
1222
|
* function must start by `on` prefix to be recognized as an event.
|
|
@@ -1164,25 +1304,25 @@ class EventEmitter extends Loggable {
|
|
|
1164
1304
|
* Event subscription
|
|
1165
1305
|
* @param name Name of event without the `on` prefix (ex: `log` to `onLog` event declared)
|
|
1166
1306
|
* @param event Subscriber Function
|
|
1167
|
-
* @param
|
|
1307
|
+
* @param options.signal Optional `AbortSignal` to stop this or multiple subscriptions in same time
|
|
1168
1308
|
*/
|
|
1169
|
-
on(name, event,
|
|
1309
|
+
on(name, event, options) {
|
|
1310
|
+
var _a;
|
|
1170
1311
|
if (!event) {
|
|
1171
1312
|
throw Error('event to subscribe cannot be null');
|
|
1172
1313
|
}
|
|
1173
1314
|
const events = this._event(name);
|
|
1174
1315
|
events.add(event);
|
|
1175
|
-
|
|
1176
|
-
abort.signal.addEventListener('abort', () => events.delete(event), { once: true });
|
|
1177
|
-
}
|
|
1316
|
+
(_a = options === null || options === void 0 ? void 0 : options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener('abort', () => events.delete(event), { once: true });
|
|
1178
1317
|
}
|
|
1179
1318
|
/**
|
|
1180
1319
|
* Event subscription only one time, once time fired it's automatically unsubscribe
|
|
1181
1320
|
* @param name Name of event without the `on` prefix (ex: `log` to `onLog` event declared)
|
|
1182
1321
|
* @param event Subscriber Function
|
|
1183
|
-
* @param
|
|
1322
|
+
* @param options.abortSignal Optional `AbortSignal` to stop this or multiple subscriptions in same time
|
|
1184
1323
|
*/
|
|
1185
|
-
once(name, event,
|
|
1324
|
+
once(name, event, options) {
|
|
1325
|
+
var _a;
|
|
1186
1326
|
if (!event) {
|
|
1187
1327
|
throw Error('event to subscribe cannot be null');
|
|
1188
1328
|
}
|
|
@@ -1191,9 +1331,7 @@ class EventEmitter extends Loggable {
|
|
|
1191
1331
|
events.delete(event); // delete from events
|
|
1192
1332
|
event(...args); // execute event
|
|
1193
1333
|
});
|
|
1194
|
-
|
|
1195
|
-
abort.signal.addEventListener('abort', () => events.delete(event), { once: true });
|
|
1196
|
-
}
|
|
1334
|
+
(_a = options === null || options === void 0 ? void 0 : options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener('abort', () => events.delete(event), { once: true });
|
|
1197
1335
|
}
|
|
1198
1336
|
/**
|
|
1199
1337
|
* Event unsubscription
|
|
@@ -2025,4 +2163,230 @@ function encodeTimestamp(context, lineWidth, blocksPerRow = 32, now = new Date()
|
|
|
2025
2163
|
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
2026
2164
|
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
2027
2165
|
*/
|
|
2028
|
-
|
|
2166
|
+
/**
|
|
2167
|
+
* An user-interface compoment to vizualize real-time metrics
|
|
2168
|
+
*/
|
|
2169
|
+
class UIMetrics {
|
|
2170
|
+
/**
|
|
2171
|
+
* get graph margin in pixels
|
|
2172
|
+
*/
|
|
2173
|
+
get graphMargin() {
|
|
2174
|
+
return this._graphMargin;
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* set graph margin in pixels
|
|
2178
|
+
*/
|
|
2179
|
+
set graphMargin(value) {
|
|
2180
|
+
this._graphMargin = value;
|
|
2181
|
+
}
|
|
2182
|
+
/**
|
|
2183
|
+
* get text margin in pixels
|
|
2184
|
+
*/
|
|
2185
|
+
get textMargin() {
|
|
2186
|
+
return this._textMargin;
|
|
2187
|
+
}
|
|
2188
|
+
/**
|
|
2189
|
+
* set text margin in pixels
|
|
2190
|
+
*/
|
|
2191
|
+
set textMargin(value) {
|
|
2192
|
+
this._textMargin = value;
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
2195
|
+
* get metric line height in pixels
|
|
2196
|
+
*/
|
|
2197
|
+
get lineHeight() {
|
|
2198
|
+
return this._lineHeight;
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* set metric line height in pixels
|
|
2202
|
+
*/
|
|
2203
|
+
set lineHeight(value) {
|
|
2204
|
+
this._lineHeight = value;
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* get label width in pixels
|
|
2208
|
+
*/
|
|
2209
|
+
get labelWidth() {
|
|
2210
|
+
return this._labelWidth;
|
|
2211
|
+
}
|
|
2212
|
+
/**
|
|
2213
|
+
* set label width in pixels
|
|
2214
|
+
*/
|
|
2215
|
+
set labelWidth(value) {
|
|
2216
|
+
this._labelWidth = value;
|
|
2217
|
+
}
|
|
2218
|
+
/**
|
|
2219
|
+
* get legend font size in pixels
|
|
2220
|
+
*/
|
|
2221
|
+
get legendFontSize() {
|
|
2222
|
+
return this._legendFontSize;
|
|
2223
|
+
}
|
|
2224
|
+
/**
|
|
2225
|
+
* set legend font size in pixels
|
|
2226
|
+
*/
|
|
2227
|
+
set legendFontSize(value) {
|
|
2228
|
+
this._legendFontSize = value;
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* get the metric unit-step in pixels
|
|
2232
|
+
*/
|
|
2233
|
+
get stepSize() {
|
|
2234
|
+
return this._stepSize;
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* set the metric unit-step in pixels
|
|
2238
|
+
*/
|
|
2239
|
+
set stepSize(value) {
|
|
2240
|
+
this._stepSize = value;
|
|
2241
|
+
}
|
|
2242
|
+
constructor(ui) {
|
|
2243
|
+
this._ui = ui;
|
|
2244
|
+
// default values in pixels
|
|
2245
|
+
this._lineHeight = 40;
|
|
2246
|
+
this._labelWidth = 170;
|
|
2247
|
+
this._graphMargin = 5;
|
|
2248
|
+
this._textMargin = 5;
|
|
2249
|
+
this._legendFontSize = 13;
|
|
2250
|
+
this._stepSize = 10;
|
|
2251
|
+
this._ranges = {};
|
|
2252
|
+
}
|
|
2253
|
+
/**
|
|
2254
|
+
* Reset metrics stats, essentially rescaling the metrics
|
|
2255
|
+
*/
|
|
2256
|
+
reset() {
|
|
2257
|
+
this._ranges = {};
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* build metric from stats
|
|
2261
|
+
* @param stats Map with stats per entry
|
|
2262
|
+
* @returns
|
|
2263
|
+
*/
|
|
2264
|
+
display(stats) {
|
|
2265
|
+
if (this._html != null) {
|
|
2266
|
+
// CPU processing, skip one stats!
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
this._html = '';
|
|
2270
|
+
const averageWidth = (this._legendFontSize / 2) * 7; // 7 chars (1 char width ≈ fontSize/2)
|
|
2271
|
+
const width = this._ui.clientWidth - averageWidth;
|
|
2272
|
+
const graphHeight = this._lineHeight - 2 * this._graphMargin;
|
|
2273
|
+
const graphMiddle = Math.round(this._lineHeight / 2);
|
|
2274
|
+
const textY = Math.round(this._lineHeight / 2 + this._textMargin);
|
|
2275
|
+
const titleWidth = this._labelWidth - 2 * this._textMargin;
|
|
2276
|
+
const averageCenter = averageWidth / 2;
|
|
2277
|
+
for (const [key, values] of stats) {
|
|
2278
|
+
let x = this._labelWidth + values.length * this._stepSize;
|
|
2279
|
+
if (x >= width) {
|
|
2280
|
+
x -= values.splice(0, Math.ceil((x - width) / this._stepSize)).length * this._stepSize;
|
|
2281
|
+
}
|
|
2282
|
+
if (!values.length) {
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
2285
|
+
/*
|
|
2286
|
+
<svg class="list-group-item p-0" style="height: 40px;" xmlns="http://www.w3.org/2000/svg">
|
|
2287
|
+
<text x="5" y="22">M text</text>
|
|
2288
|
+
<path fill="none" d="M100 0 110 22 120 0 130 20" stroke-width="1" stroke="brown"/>
|
|
2289
|
+
</svg>
|
|
2290
|
+
*/
|
|
2291
|
+
this._html +=
|
|
2292
|
+
'<svg class="list-group-item p-0" style="height: ' +
|
|
2293
|
+
this._lineHeight +
|
|
2294
|
+
'px" xmlns="http://www.w3.org/2000/svg">';
|
|
2295
|
+
this._html += '<text x="' + this._textMargin + '" y="' + textY + '">' + key + '</text>';
|
|
2296
|
+
this._html +=
|
|
2297
|
+
'<text x="' +
|
|
2298
|
+
titleWidth +
|
|
2299
|
+
'" y="' +
|
|
2300
|
+
textY +
|
|
2301
|
+
'" text-anchor="end">' +
|
|
2302
|
+
values[values.length - 1].toString() +
|
|
2303
|
+
'</text>';
|
|
2304
|
+
this._html += '<path fill="none" d="M' + this._labelWidth + ' ' + graphMiddle;
|
|
2305
|
+
this._html += 'H' + (width + averageCenter);
|
|
2306
|
+
this._html += '" stroke-width="1" stroke="lightgray" stroke-dasharray="10,10"/>';
|
|
2307
|
+
this._html += '<path fill="none" stroke-width="1" stroke="brown" d="M';
|
|
2308
|
+
let min = Number.POSITIVE_INFINITY;
|
|
2309
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
2310
|
+
for (let i = 0; i < values.length; ++i) {
|
|
2311
|
+
const value = parseFloat(values[i].toString());
|
|
2312
|
+
if (value < min) {
|
|
2313
|
+
min = value;
|
|
2314
|
+
}
|
|
2315
|
+
if (value > max) {
|
|
2316
|
+
max = value;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
let range = this._ranges[key];
|
|
2320
|
+
if (!range) {
|
|
2321
|
+
this._ranges[key] = range = { min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY };
|
|
2322
|
+
}
|
|
2323
|
+
range.min = Math.min(range.min, min);
|
|
2324
|
+
range.max = Math.max(range.max, max);
|
|
2325
|
+
const delta = range.max - range.min;
|
|
2326
|
+
let minCircle = '';
|
|
2327
|
+
let maxCircle = '';
|
|
2328
|
+
for (let i = 0; i < values.length; ++i) {
|
|
2329
|
+
x -= this._stepSize;
|
|
2330
|
+
const value = parseFloat(values[i].toString());
|
|
2331
|
+
const y = graphMiddle + (delta ? Math.round((0.5 - (value - range.min) / delta) * graphHeight) : 0);
|
|
2332
|
+
this._html += x + ' ' + y + ' ';
|
|
2333
|
+
if (value === min) {
|
|
2334
|
+
maxCircle = maxCircle || this._drawCircle(x, y, value);
|
|
2335
|
+
}
|
|
2336
|
+
else if (value === max) {
|
|
2337
|
+
minCircle = minCircle || this._drawCircle(x, y, value);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
this._html += '" />'; // end path
|
|
2341
|
+
// Average
|
|
2342
|
+
const average = Math.round((max - min) / 2);
|
|
2343
|
+
this._html += '<text text-anchor="middle" font-size="' + this._legendFontSize + '" y="' + textY + '">';
|
|
2344
|
+
this._html +=
|
|
2345
|
+
'<tspan x="' +
|
|
2346
|
+
(width + averageCenter) +
|
|
2347
|
+
'" dy="-0.5em">' +
|
|
2348
|
+
(min !== max ? '≈' : '=') +
|
|
2349
|
+
(min + average) +
|
|
2350
|
+
'</tspan>';
|
|
2351
|
+
this._html += '<tspan x="' + (width + averageCenter) + '" dy="1em">±' + average + '</tspan>';
|
|
2352
|
+
this._html += '</text>';
|
|
2353
|
+
this._html += minCircle + maxCircle;
|
|
2354
|
+
this._html += '</svg>';
|
|
2355
|
+
}
|
|
2356
|
+
requestAnimationFrame(() => {
|
|
2357
|
+
if (this._html != null) {
|
|
2358
|
+
this._ui.innerHTML = this._html;
|
|
2359
|
+
this._html = undefined;
|
|
2360
|
+
}
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
_drawCircle(x, y, value) {
|
|
2364
|
+
let circle = '<circle cx="' + x + '" cy="' + y + '" r="2" fill="green" />';
|
|
2365
|
+
const legendFontHeight = 0.7 * this._legendFontSize;
|
|
2366
|
+
const graphMiddle = Math.round(this._lineHeight / 2);
|
|
2367
|
+
if (y < graphMiddle) {
|
|
2368
|
+
// legend below
|
|
2369
|
+
y += this.textMargin + legendFontHeight;
|
|
2370
|
+
}
|
|
2371
|
+
else {
|
|
2372
|
+
// legend above
|
|
2373
|
+
y -= this.textMargin;
|
|
2374
|
+
}
|
|
2375
|
+
circle +=
|
|
2376
|
+
'<text font-style="italic" font-size="' +
|
|
2377
|
+
this._legendFontSize +
|
|
2378
|
+
'" x="' +
|
|
2379
|
+
(x - this._legendFontSize) +
|
|
2380
|
+
'" y="' +
|
|
2381
|
+
y +
|
|
2382
|
+
'">' +
|
|
2383
|
+
value +
|
|
2384
|
+
'</text>';
|
|
2385
|
+
return circle;
|
|
2386
|
+
}
|
|
2387
|
+
}/**
|
|
2388
|
+
* Copyright 2024 Ceeblue B.V.
|
|
2389
|
+
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
2390
|
+
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
2391
|
+
*/
|
|
2392
|
+
const VERSION = '3.1.0';export{BinaryReader,BinaryWriter,BitReader,ByteRate,Connect,EpochTime,EventEmitter,FixMap,Log,LogLevel,Loggable,NetAddress,Numbers,Queue,SDP,UIMetrics,Util,VERSION,WebSocketReliable,log};//# sourceMappingURL=web-utils.js.map
|