@ceeblue/web-utils 3.0.0 → 3.2.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/commitlint.config.js +9 -0
- package/dist/web-utils.d.ts +146 -12
- package/dist/web-utils.js +535 -189
- 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 -6
package/dist/web-utils.js
CHANGED
|
@@ -115,166 +115,6 @@ class BinaryReader {
|
|
|
115
115
|
const pos = this._position;
|
|
116
116
|
return this._data.subarray(pos, Math.max(pos, (this._position += size)));
|
|
117
117
|
}
|
|
118
|
-
}/**
|
|
119
|
-
* Copyright 2024 Ceeblue B.V.
|
|
120
|
-
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
121
|
-
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
122
|
-
*/
|
|
123
|
-
const _encoder$1 = new TextEncoder();
|
|
124
|
-
/**
|
|
125
|
-
* BinaryWriter allows to write data in its binary form
|
|
126
|
-
*/
|
|
127
|
-
class BinaryWriter {
|
|
128
|
-
get view() {
|
|
129
|
-
if (!this._view) {
|
|
130
|
-
this._view = new DataView(this._data.buffer, this._data.byteOffset, this._data.byteLength);
|
|
131
|
-
}
|
|
132
|
-
return this._view;
|
|
133
|
-
}
|
|
134
|
-
get capacity() {
|
|
135
|
-
return this._data.byteLength;
|
|
136
|
-
}
|
|
137
|
-
constructor(dataOrSize = 64, offset = 0, length) {
|
|
138
|
-
if (typeof dataOrSize == 'number') {
|
|
139
|
-
// allocate new buffer
|
|
140
|
-
this._data = new Uint8Array(dataOrSize);
|
|
141
|
-
this._size = 0;
|
|
142
|
-
}
|
|
143
|
-
else if ('buffer' in dataOrSize) {
|
|
144
|
-
// append to existing data!
|
|
145
|
-
this._data = new Uint8Array(dataOrSize.buffer, dataOrSize.byteOffset, dataOrSize.byteLength);
|
|
146
|
-
this._size = dataOrSize.byteLength;
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
// overrides data
|
|
150
|
-
this._isConst = true; // better than boolean for memory usage
|
|
151
|
-
if (length == null) {
|
|
152
|
-
// /!\ Safari does not support undefined length, so we need to use byteLength instead
|
|
153
|
-
length = dataOrSize.byteLength;
|
|
154
|
-
}
|
|
155
|
-
this._data = new Uint8Array(dataOrSize, offset, length);
|
|
156
|
-
this._size = 0;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
data() {
|
|
160
|
-
return new Uint8Array(this._data.buffer, this._data.byteOffset, this._size);
|
|
161
|
-
}
|
|
162
|
-
size() {
|
|
163
|
-
return this._size || 0;
|
|
164
|
-
}
|
|
165
|
-
next(count = 1) {
|
|
166
|
-
return this.reserve((this._size += count));
|
|
167
|
-
}
|
|
168
|
-
clear(size = 0) {
|
|
169
|
-
return this.reserve((this._size = size));
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Write binary data
|
|
173
|
-
* @param data
|
|
174
|
-
*/
|
|
175
|
-
write(data) {
|
|
176
|
-
this.reserve(this._size + data.length);
|
|
177
|
-
this._data.set(data, this._size);
|
|
178
|
-
this._size += data.length;
|
|
179
|
-
return this;
|
|
180
|
-
}
|
|
181
|
-
write8(value) {
|
|
182
|
-
if (value > 0xff) {
|
|
183
|
-
// cast to 8bits range
|
|
184
|
-
value = 0xff;
|
|
185
|
-
}
|
|
186
|
-
this.reserve(this._size + 1);
|
|
187
|
-
this._data[this._size++] = value;
|
|
188
|
-
return this;
|
|
189
|
-
}
|
|
190
|
-
write16(value) {
|
|
191
|
-
if (value > 0xffff) {
|
|
192
|
-
// cast to 16bits range
|
|
193
|
-
value = 0xffff;
|
|
194
|
-
}
|
|
195
|
-
this.reserve(this._size + 2);
|
|
196
|
-
this.view.setUint16(this._size, value);
|
|
197
|
-
this._size += 2;
|
|
198
|
-
return this;
|
|
199
|
-
}
|
|
200
|
-
write24(value) {
|
|
201
|
-
if (value > 0xffffff) {
|
|
202
|
-
// cast to 24bits range
|
|
203
|
-
value = 0xffffff;
|
|
204
|
-
}
|
|
205
|
-
this.reserve(this._size + 3);
|
|
206
|
-
this.view.setUint16(this._size, value >> 8);
|
|
207
|
-
this.view.setUint8((this._size += 2), value & 0xff);
|
|
208
|
-
++this._size;
|
|
209
|
-
return this;
|
|
210
|
-
}
|
|
211
|
-
write32(value) {
|
|
212
|
-
if (value > 0xffffffff) {
|
|
213
|
-
// cast to 32bits range
|
|
214
|
-
value = 0xffffffff;
|
|
215
|
-
}
|
|
216
|
-
this.reserve(this._size + 4);
|
|
217
|
-
this.view.setUint32(this._size, value);
|
|
218
|
-
this._size += 4;
|
|
219
|
-
return this;
|
|
220
|
-
}
|
|
221
|
-
write64(value) {
|
|
222
|
-
this.write32(value / 4294967296);
|
|
223
|
-
return this.write32(value & 0xffffffff);
|
|
224
|
-
}
|
|
225
|
-
writeFloat(value) {
|
|
226
|
-
this.reserve(this._size + 4);
|
|
227
|
-
this.view.setFloat32(this._size, value);
|
|
228
|
-
this._size += 4;
|
|
229
|
-
return this;
|
|
230
|
-
}
|
|
231
|
-
writeDouble(value) {
|
|
232
|
-
this.reserve(this._size + 8);
|
|
233
|
-
this.view.setFloat64(this._size, value);
|
|
234
|
-
this._size += 8;
|
|
235
|
-
return this;
|
|
236
|
-
}
|
|
237
|
-
write7Bit(value) {
|
|
238
|
-
let byte = value & 0x7f;
|
|
239
|
-
while ((value = Math.floor(value / 0x80))) {
|
|
240
|
-
// equivalent to >>=7 for JS!
|
|
241
|
-
this.write8(0x80 | byte);
|
|
242
|
-
byte = value & 0x7f;
|
|
243
|
-
}
|
|
244
|
-
return this.write8(byte);
|
|
245
|
-
}
|
|
246
|
-
writeString(value) {
|
|
247
|
-
return this.write(_encoder$1.encode(value)).write8(0);
|
|
248
|
-
}
|
|
249
|
-
writeHex(value) {
|
|
250
|
-
for (let i = 0; i < value.length; i += 2) {
|
|
251
|
-
this.write8(parseInt(value.substring(i, i + 2), 16));
|
|
252
|
-
}
|
|
253
|
-
return this;
|
|
254
|
-
}
|
|
255
|
-
reserve(size) {
|
|
256
|
-
if (!this._data) {
|
|
257
|
-
throw Error('buffer not writable');
|
|
258
|
-
}
|
|
259
|
-
if (size <= this._data.byteLength) {
|
|
260
|
-
return this;
|
|
261
|
-
}
|
|
262
|
-
if (this._isConst) {
|
|
263
|
-
throw Error('writing exceeds maximum ' + this._data.byteLength + ' bytes limit');
|
|
264
|
-
}
|
|
265
|
-
--size;
|
|
266
|
-
size |= size >> 1;
|
|
267
|
-
size |= size >> 2;
|
|
268
|
-
size |= size >> 4;
|
|
269
|
-
size |= size >> 8;
|
|
270
|
-
size |= size >> 16;
|
|
271
|
-
++size;
|
|
272
|
-
const data = new Uint8Array(size);
|
|
273
|
-
data.set(this._data); // copy old buffer!
|
|
274
|
-
this._data = data;
|
|
275
|
-
this._view = undefined; // release view
|
|
276
|
-
return this;
|
|
277
|
-
}
|
|
278
118
|
}/******************************************************************************
|
|
279
119
|
Copyright (c) Microsoft Corporation.
|
|
280
120
|
|
|
@@ -649,6 +489,180 @@ function trimEnd(value, chars = ' ') {
|
|
|
649
489
|
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
650
490
|
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
651
491
|
*/
|
|
492
|
+
/**
|
|
493
|
+
* BinaryWriter allows to write data in its binary form
|
|
494
|
+
*/
|
|
495
|
+
class BinaryWriter {
|
|
496
|
+
get view() {
|
|
497
|
+
if (!this._view) {
|
|
498
|
+
this._view = new DataView(this._data.buffer, this._data.byteOffset, this._data.byteLength);
|
|
499
|
+
}
|
|
500
|
+
return this._view;
|
|
501
|
+
}
|
|
502
|
+
get capacity() {
|
|
503
|
+
return this._data.byteLength;
|
|
504
|
+
}
|
|
505
|
+
constructor(dataOrSize = 64, offset = 0, length) {
|
|
506
|
+
if (typeof dataOrSize == 'number') {
|
|
507
|
+
// allocate new buffer
|
|
508
|
+
this._data = new Uint8Array(dataOrSize);
|
|
509
|
+
this._size = 0;
|
|
510
|
+
}
|
|
511
|
+
else if ('buffer' in dataOrSize) {
|
|
512
|
+
// append to existing data!
|
|
513
|
+
this._data = new Uint8Array(dataOrSize.buffer, dataOrSize.byteOffset, dataOrSize.byteLength);
|
|
514
|
+
this._size = dataOrSize.byteLength;
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
// overrides data
|
|
518
|
+
this._isConst = true; // better than boolean for memory usage
|
|
519
|
+
if (length == null) {
|
|
520
|
+
// /!\ Safari does not support undefined length, so we need to use byteLength instead
|
|
521
|
+
length = dataOrSize.byteLength;
|
|
522
|
+
}
|
|
523
|
+
this._data = new Uint8Array(dataOrSize, offset, length);
|
|
524
|
+
this._size = 0;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
data() {
|
|
528
|
+
return new Uint8Array(this._data.buffer, this._data.byteOffset, this._size);
|
|
529
|
+
}
|
|
530
|
+
size() {
|
|
531
|
+
return this._size || 0;
|
|
532
|
+
}
|
|
533
|
+
next(count = 1) {
|
|
534
|
+
return this.reserve((this._size += count));
|
|
535
|
+
}
|
|
536
|
+
clear(size = 0) {
|
|
537
|
+
return this.reserve((this._size = size));
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Write binary data
|
|
541
|
+
* @param data
|
|
542
|
+
*/
|
|
543
|
+
write(data) {
|
|
544
|
+
var _a;
|
|
545
|
+
let bin;
|
|
546
|
+
if (typeof data === 'string') {
|
|
547
|
+
// Convertit la chaîne en Uint8Array
|
|
548
|
+
bin = toBin(data);
|
|
549
|
+
}
|
|
550
|
+
else if (data instanceof ArrayBuffer) {
|
|
551
|
+
bin = new Uint8Array(data);
|
|
552
|
+
}
|
|
553
|
+
else if ('buffer' in data) {
|
|
554
|
+
bin = new Uint8Array(data.buffer, (_a = data.byteOffset) !== null && _a !== void 0 ? _a : 0, data.byteLength);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
bin = data;
|
|
558
|
+
}
|
|
559
|
+
this.reserve(this._size + bin.length);
|
|
560
|
+
this._data.set(bin, this._size);
|
|
561
|
+
this._size += bin.length;
|
|
562
|
+
return this;
|
|
563
|
+
}
|
|
564
|
+
write8(value) {
|
|
565
|
+
if (value > 0xff) {
|
|
566
|
+
// cast to 8bits range
|
|
567
|
+
value = 0xff;
|
|
568
|
+
}
|
|
569
|
+
this.reserve(this._size + 1);
|
|
570
|
+
this._data[this._size++] = value;
|
|
571
|
+
return this;
|
|
572
|
+
}
|
|
573
|
+
write16(value) {
|
|
574
|
+
if (value > 0xffff) {
|
|
575
|
+
// cast to 16bits range
|
|
576
|
+
value = 0xffff;
|
|
577
|
+
}
|
|
578
|
+
this.reserve(this._size + 2);
|
|
579
|
+
this.view.setUint16(this._size, value);
|
|
580
|
+
this._size += 2;
|
|
581
|
+
return this;
|
|
582
|
+
}
|
|
583
|
+
write24(value) {
|
|
584
|
+
if (value > 0xffffff) {
|
|
585
|
+
// cast to 24bits range
|
|
586
|
+
value = 0xffffff;
|
|
587
|
+
}
|
|
588
|
+
this.reserve(this._size + 3);
|
|
589
|
+
this.view.setUint16(this._size, value >> 8);
|
|
590
|
+
this.view.setUint8((this._size += 2), value & 0xff);
|
|
591
|
+
++this._size;
|
|
592
|
+
return this;
|
|
593
|
+
}
|
|
594
|
+
write32(value) {
|
|
595
|
+
if (value > 0xffffffff) {
|
|
596
|
+
// cast to 32bits range
|
|
597
|
+
value = 0xffffffff;
|
|
598
|
+
}
|
|
599
|
+
this.reserve(this._size + 4);
|
|
600
|
+
this.view.setUint32(this._size, value);
|
|
601
|
+
this._size += 4;
|
|
602
|
+
return this;
|
|
603
|
+
}
|
|
604
|
+
write64(value) {
|
|
605
|
+
this.write32(value / 4294967296);
|
|
606
|
+
return this.write32(value & 0xffffffff);
|
|
607
|
+
}
|
|
608
|
+
writeFloat(value) {
|
|
609
|
+
this.reserve(this._size + 4);
|
|
610
|
+
this.view.setFloat32(this._size, value);
|
|
611
|
+
this._size += 4;
|
|
612
|
+
return this;
|
|
613
|
+
}
|
|
614
|
+
writeDouble(value) {
|
|
615
|
+
this.reserve(this._size + 8);
|
|
616
|
+
this.view.setFloat64(this._size, value);
|
|
617
|
+
this._size += 8;
|
|
618
|
+
return this;
|
|
619
|
+
}
|
|
620
|
+
write7Bit(value) {
|
|
621
|
+
let byte = value & 0x7f;
|
|
622
|
+
while ((value = Math.floor(value / 0x80))) {
|
|
623
|
+
// equivalent to >>=7 for JS!
|
|
624
|
+
this.write8(0x80 | byte);
|
|
625
|
+
byte = value & 0x7f;
|
|
626
|
+
}
|
|
627
|
+
return this.write8(byte);
|
|
628
|
+
}
|
|
629
|
+
writeString(value) {
|
|
630
|
+
return this.write(toBin(value)).write8(0);
|
|
631
|
+
}
|
|
632
|
+
writeHex(value) {
|
|
633
|
+
for (let i = 0; i < value.length; i += 2) {
|
|
634
|
+
this.write8(parseInt(value.substring(i, i + 2), 16));
|
|
635
|
+
}
|
|
636
|
+
return this;
|
|
637
|
+
}
|
|
638
|
+
reserve(size) {
|
|
639
|
+
if (!this._data) {
|
|
640
|
+
throw Error('buffer not writable');
|
|
641
|
+
}
|
|
642
|
+
if (size <= this._data.byteLength) {
|
|
643
|
+
return this;
|
|
644
|
+
}
|
|
645
|
+
if (this._isConst) {
|
|
646
|
+
throw Error('writing exceeds maximum ' + this._data.byteLength + ' bytes limit');
|
|
647
|
+
}
|
|
648
|
+
--size;
|
|
649
|
+
size |= size >> 1;
|
|
650
|
+
size |= size >> 2;
|
|
651
|
+
size |= size >> 4;
|
|
652
|
+
size |= size >> 8;
|
|
653
|
+
size |= size >> 16;
|
|
654
|
+
++size;
|
|
655
|
+
const data = new Uint8Array(size);
|
|
656
|
+
data.set(this._data); // copy old buffer!
|
|
657
|
+
this._data = data;
|
|
658
|
+
this._view = undefined; // release view
|
|
659
|
+
return this;
|
|
660
|
+
}
|
|
661
|
+
}/**
|
|
662
|
+
* Copyright 2024 Ceeblue B.V.
|
|
663
|
+
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
664
|
+
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
665
|
+
*/
|
|
652
666
|
/**
|
|
653
667
|
* Log levels
|
|
654
668
|
*/
|
|
@@ -841,38 +855,146 @@ class BitReader extends Loggable {
|
|
|
841
855
|
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
842
856
|
*/
|
|
843
857
|
/**
|
|
844
|
-
*
|
|
858
|
+
* Class to compute a weighted average byte rate over a specified time interval.
|
|
859
|
+
*
|
|
860
|
+
* This class continuously tracks data transmission and computes the byte rate
|
|
861
|
+
* based on a weighted average, considering both the duration and the number of
|
|
862
|
+
* bytes in each sample. It allows for real-time monitoring of bandwidth usage
|
|
863
|
+
* and provides mechanisms to dynamically adjust the measurement interval.
|
|
864
|
+
*
|
|
865
|
+
* Features:
|
|
866
|
+
* - Computes the byte rate using a **weighted average** approach.
|
|
867
|
+
* - Allows setting a custom interval for tracking.
|
|
868
|
+
* - Supports dynamic clipping to manually shrink the observation window.
|
|
845
869
|
*/
|
|
846
870
|
class ByteRate {
|
|
871
|
+
/**
|
|
872
|
+
* Raised when new bytes are added
|
|
873
|
+
*/
|
|
847
874
|
onBytes(bytes) { }
|
|
848
|
-
|
|
849
|
-
|
|
875
|
+
/**
|
|
876
|
+
* Returns the interval used for computing the byte rate
|
|
877
|
+
*/
|
|
878
|
+
get interval() {
|
|
879
|
+
return this._interval;
|
|
850
880
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
this.
|
|
881
|
+
/**
|
|
882
|
+
* Sets a new interval for computing the average byte rate
|
|
883
|
+
*/
|
|
884
|
+
set interval(value) {
|
|
885
|
+
this._interval = value;
|
|
886
|
+
this.updateSamples();
|
|
856
887
|
}
|
|
888
|
+
/**
|
|
889
|
+
* Constructor initializes the ByteRate object with a specified interval (default: 1000ms).
|
|
890
|
+
* It sets up necessary variables to track byte rate over time.
|
|
891
|
+
*
|
|
892
|
+
* @param interval - Time interval in milliseconds to compute the byte rate.
|
|
893
|
+
*/
|
|
894
|
+
constructor(interval = 1000) {
|
|
895
|
+
this._interval = interval;
|
|
896
|
+
this.clear();
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Returns the computed byte rate rounded to the nearest integer
|
|
900
|
+
*/
|
|
857
901
|
value() {
|
|
858
902
|
return Math.round(this.exact());
|
|
859
903
|
}
|
|
904
|
+
/**
|
|
905
|
+
* Computes the exact byte rate in bytes per second
|
|
906
|
+
*/
|
|
860
907
|
exact() {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
this._value = (this._bytes * 1000) / elapsed;
|
|
866
|
-
this._bytes = 0;
|
|
867
|
-
this._time = now;
|
|
868
|
-
}
|
|
869
|
-
return this._value;
|
|
908
|
+
// compute rate/s
|
|
909
|
+
this.updateSamples();
|
|
910
|
+
const duration = time() - this._time;
|
|
911
|
+
return duration ? (this._bytes / duration) * 1000 : 0;
|
|
870
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* Adds a new byte sample to the tracking system.
|
|
915
|
+
* Updates the list of samples and recomputes the byte rate
|
|
916
|
+
*
|
|
917
|
+
* @param bytes - Number of bytes added in this interval
|
|
918
|
+
*/
|
|
871
919
|
addBytes(bytes) {
|
|
920
|
+
var _a;
|
|
921
|
+
const time$1 = time();
|
|
922
|
+
const lastSample = this.updateSamples(time$1)[this._samples.length - 1];
|
|
923
|
+
const lastTime = (_a = lastSample === null || lastSample === void 0 ? void 0 : lastSample.time) !== null && _a !== void 0 ? _a : this._time;
|
|
924
|
+
if (time$1 > lastTime) {
|
|
925
|
+
this._samples.push({ bytes, time: time$1, clip: false });
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
// no new duration => attach byte to last-one
|
|
929
|
+
if (!lastSample) {
|
|
930
|
+
// Ignore, was before our ByteRate scope !
|
|
931
|
+
return this;
|
|
932
|
+
}
|
|
933
|
+
lastSample.bytes += bytes;
|
|
934
|
+
}
|
|
872
935
|
this._bytes += bytes;
|
|
873
936
|
this.onBytes(bytes);
|
|
874
937
|
return this;
|
|
875
938
|
}
|
|
939
|
+
/**
|
|
940
|
+
* Clears all recorded byte rate data.
|
|
941
|
+
*/
|
|
942
|
+
clear() {
|
|
943
|
+
this._bytes = 0;
|
|
944
|
+
this._time = time();
|
|
945
|
+
this._samples = [];
|
|
946
|
+
this._clip = false;
|
|
947
|
+
return this;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Clips the byte rate tracking by marking the last sample as clipped.
|
|
951
|
+
* If a previous clip exists, removes the clipped sample and all preceding samples.
|
|
952
|
+
* Allows to shrink the interval manually between two positions.
|
|
953
|
+
*/
|
|
954
|
+
clip() {
|
|
955
|
+
if (this._clip) {
|
|
956
|
+
this._clip = false;
|
|
957
|
+
let removes = 0;
|
|
958
|
+
for (const sample of this._samples) {
|
|
959
|
+
this._bytes -= sample.bytes;
|
|
960
|
+
++removes;
|
|
961
|
+
this._time = sample.time;
|
|
962
|
+
if (sample.clip) {
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
this._samples.splice(0, removes);
|
|
967
|
+
}
|
|
968
|
+
const lastSample = this._samples[this._samples.length - 1];
|
|
969
|
+
if (lastSample) {
|
|
970
|
+
lastSample.clip = true;
|
|
971
|
+
this._clip = true;
|
|
972
|
+
}
|
|
973
|
+
return this;
|
|
974
|
+
}
|
|
975
|
+
updateSamples(now = time()) {
|
|
976
|
+
// Remove obsolete sample
|
|
977
|
+
const timeOK = now - this._interval;
|
|
978
|
+
let removes = 0;
|
|
979
|
+
let sample;
|
|
980
|
+
while (this._time < timeOK && (sample = this._samples[removes])) {
|
|
981
|
+
this._bytes -= sample.bytes;
|
|
982
|
+
if (sample.clip) {
|
|
983
|
+
this._clip = sample.clip = false;
|
|
984
|
+
}
|
|
985
|
+
if (sample.time > timeOK) {
|
|
986
|
+
// only a part of the sample to delete !
|
|
987
|
+
sample.bytes *= (sample.time - timeOK) / (sample.time - this._time);
|
|
988
|
+
this._time = timeOK;
|
|
989
|
+
this._bytes += sample.bytes;
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
++removes;
|
|
993
|
+
this._time = sample.time;
|
|
994
|
+
}
|
|
995
|
+
this._samples.splice(0, removes);
|
|
996
|
+
return this._samples;
|
|
997
|
+
}
|
|
876
998
|
}/**
|
|
877
999
|
* Copyright 2024 Ceeblue B.V.
|
|
878
1000
|
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
@@ -1062,7 +1184,7 @@ function defineMediaExt(type, params) {
|
|
|
1062
1184
|
break;
|
|
1063
1185
|
}
|
|
1064
1186
|
// Fix mediaExt in removing the possible '.' prefix
|
|
1065
|
-
trimStart(params.mediaExt, '.');
|
|
1187
|
+
params.mediaExt = trimStart(params.mediaExt, '.');
|
|
1066
1188
|
}
|
|
1067
1189
|
/**
|
|
1068
1190
|
* Build an URL from {@link Type | type} and {@link Params | params}
|
|
@@ -1196,25 +1318,25 @@ class EventEmitter extends Loggable {
|
|
|
1196
1318
|
* Event subscription
|
|
1197
1319
|
* @param name Name of event without the `on` prefix (ex: `log` to `onLog` event declared)
|
|
1198
1320
|
* @param event Subscriber Function
|
|
1199
|
-
* @param
|
|
1321
|
+
* @param options.signal Optional `AbortSignal` to stop this or multiple subscriptions in same time
|
|
1200
1322
|
*/
|
|
1201
|
-
on(name, event,
|
|
1323
|
+
on(name, event, options) {
|
|
1324
|
+
var _a;
|
|
1202
1325
|
if (!event) {
|
|
1203
1326
|
throw Error('event to subscribe cannot be null');
|
|
1204
1327
|
}
|
|
1205
1328
|
const events = this._event(name);
|
|
1206
1329
|
events.add(event);
|
|
1207
|
-
|
|
1208
|
-
abort.signal.addEventListener('abort', () => events.delete(event), { once: true });
|
|
1209
|
-
}
|
|
1330
|
+
(_a = options === null || options === void 0 ? void 0 : options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener('abort', () => events.delete(event), { once: true });
|
|
1210
1331
|
}
|
|
1211
1332
|
/**
|
|
1212
1333
|
* Event subscription only one time, once time fired it's automatically unsubscribe
|
|
1213
1334
|
* @param name Name of event without the `on` prefix (ex: `log` to `onLog` event declared)
|
|
1214
1335
|
* @param event Subscriber Function
|
|
1215
|
-
* @param
|
|
1336
|
+
* @param options.abortSignal Optional `AbortSignal` to stop this or multiple subscriptions in same time
|
|
1216
1337
|
*/
|
|
1217
|
-
once(name, event,
|
|
1338
|
+
once(name, event, options) {
|
|
1339
|
+
var _a;
|
|
1218
1340
|
if (!event) {
|
|
1219
1341
|
throw Error('event to subscribe cannot be null');
|
|
1220
1342
|
}
|
|
@@ -1223,9 +1345,7 @@ class EventEmitter extends Loggable {
|
|
|
1223
1345
|
events.delete(event); // delete from events
|
|
1224
1346
|
event(...args); // execute event
|
|
1225
1347
|
});
|
|
1226
|
-
|
|
1227
|
-
abort.signal.addEventListener('abort', () => events.delete(event), { once: true });
|
|
1228
|
-
}
|
|
1348
|
+
(_a = options === null || options === void 0 ? void 0 : options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener('abort', () => events.delete(event), { once: true });
|
|
1229
1349
|
}
|
|
1230
1350
|
/**
|
|
1231
1351
|
* Event unsubscription
|
|
@@ -2057,4 +2177,230 @@ function encodeTimestamp(context, lineWidth, blocksPerRow = 32, now = new Date()
|
|
|
2057
2177
|
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
2058
2178
|
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
2059
2179
|
*/
|
|
2060
|
-
|
|
2180
|
+
/**
|
|
2181
|
+
* An user-interface compoment to vizualize real-time metrics
|
|
2182
|
+
*/
|
|
2183
|
+
class UIMetrics {
|
|
2184
|
+
/**
|
|
2185
|
+
* get graph margin in pixels
|
|
2186
|
+
*/
|
|
2187
|
+
get graphMargin() {
|
|
2188
|
+
return this._graphMargin;
|
|
2189
|
+
}
|
|
2190
|
+
/**
|
|
2191
|
+
* set graph margin in pixels
|
|
2192
|
+
*/
|
|
2193
|
+
set graphMargin(value) {
|
|
2194
|
+
this._graphMargin = value;
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* get text margin in pixels
|
|
2198
|
+
*/
|
|
2199
|
+
get textMargin() {
|
|
2200
|
+
return this._textMargin;
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* set text margin in pixels
|
|
2204
|
+
*/
|
|
2205
|
+
set textMargin(value) {
|
|
2206
|
+
this._textMargin = value;
|
|
2207
|
+
}
|
|
2208
|
+
/**
|
|
2209
|
+
* get metric line height in pixels
|
|
2210
|
+
*/
|
|
2211
|
+
get lineHeight() {
|
|
2212
|
+
return this._lineHeight;
|
|
2213
|
+
}
|
|
2214
|
+
/**
|
|
2215
|
+
* set metric line height in pixels
|
|
2216
|
+
*/
|
|
2217
|
+
set lineHeight(value) {
|
|
2218
|
+
this._lineHeight = value;
|
|
2219
|
+
}
|
|
2220
|
+
/**
|
|
2221
|
+
* get label width in pixels
|
|
2222
|
+
*/
|
|
2223
|
+
get labelWidth() {
|
|
2224
|
+
return this._labelWidth;
|
|
2225
|
+
}
|
|
2226
|
+
/**
|
|
2227
|
+
* set label width in pixels
|
|
2228
|
+
*/
|
|
2229
|
+
set labelWidth(value) {
|
|
2230
|
+
this._labelWidth = value;
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* get legend font size in pixels
|
|
2234
|
+
*/
|
|
2235
|
+
get legendFontSize() {
|
|
2236
|
+
return this._legendFontSize;
|
|
2237
|
+
}
|
|
2238
|
+
/**
|
|
2239
|
+
* set legend font size in pixels
|
|
2240
|
+
*/
|
|
2241
|
+
set legendFontSize(value) {
|
|
2242
|
+
this._legendFontSize = value;
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* get the metric unit-step in pixels
|
|
2246
|
+
*/
|
|
2247
|
+
get stepSize() {
|
|
2248
|
+
return this._stepSize;
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* set the metric unit-step in pixels
|
|
2252
|
+
*/
|
|
2253
|
+
set stepSize(value) {
|
|
2254
|
+
this._stepSize = value;
|
|
2255
|
+
}
|
|
2256
|
+
constructor(ui) {
|
|
2257
|
+
this._ui = ui;
|
|
2258
|
+
// default values in pixels
|
|
2259
|
+
this._lineHeight = 40;
|
|
2260
|
+
this._labelWidth = 170;
|
|
2261
|
+
this._graphMargin = 5;
|
|
2262
|
+
this._textMargin = 5;
|
|
2263
|
+
this._legendFontSize = 13;
|
|
2264
|
+
this._stepSize = 10;
|
|
2265
|
+
this._ranges = {};
|
|
2266
|
+
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Reset metrics stats, essentially rescaling the metrics
|
|
2269
|
+
*/
|
|
2270
|
+
reset() {
|
|
2271
|
+
this._ranges = {};
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* build metric from stats
|
|
2275
|
+
* @param stats Map with stats per entry
|
|
2276
|
+
* @returns
|
|
2277
|
+
*/
|
|
2278
|
+
display(stats) {
|
|
2279
|
+
if (this._html != null) {
|
|
2280
|
+
// CPU processing, skip one stats!
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
this._html = '';
|
|
2284
|
+
const averageWidth = (this._legendFontSize / 2) * 7; // 7 chars (1 char width ≈ fontSize/2)
|
|
2285
|
+
const width = this._ui.clientWidth - averageWidth;
|
|
2286
|
+
const graphHeight = this._lineHeight - 2 * this._graphMargin;
|
|
2287
|
+
const graphMiddle = Math.round(this._lineHeight / 2);
|
|
2288
|
+
const textY = Math.round(this._lineHeight / 2 + this._textMargin);
|
|
2289
|
+
const titleWidth = this._labelWidth - 2 * this._textMargin;
|
|
2290
|
+
const averageCenter = averageWidth / 2;
|
|
2291
|
+
for (const [key, values] of stats) {
|
|
2292
|
+
let x = this._labelWidth + values.length * this._stepSize;
|
|
2293
|
+
if (x >= width) {
|
|
2294
|
+
x -= values.splice(0, Math.ceil((x - width) / this._stepSize)).length * this._stepSize;
|
|
2295
|
+
}
|
|
2296
|
+
if (!values.length) {
|
|
2297
|
+
continue;
|
|
2298
|
+
}
|
|
2299
|
+
/*
|
|
2300
|
+
<svg class="list-group-item p-0" style="height: 40px;" xmlns="http://www.w3.org/2000/svg">
|
|
2301
|
+
<text x="5" y="22">M text</text>
|
|
2302
|
+
<path fill="none" d="M100 0 110 22 120 0 130 20" stroke-width="1" stroke="brown"/>
|
|
2303
|
+
</svg>
|
|
2304
|
+
*/
|
|
2305
|
+
this._html +=
|
|
2306
|
+
'<svg class="list-group-item p-0" style="height: ' +
|
|
2307
|
+
this._lineHeight +
|
|
2308
|
+
'px" xmlns="http://www.w3.org/2000/svg">';
|
|
2309
|
+
this._html += '<text x="' + this._textMargin + '" y="' + textY + '">' + key + '</text>';
|
|
2310
|
+
this._html +=
|
|
2311
|
+
'<text x="' +
|
|
2312
|
+
titleWidth +
|
|
2313
|
+
'" y="' +
|
|
2314
|
+
textY +
|
|
2315
|
+
'" text-anchor="end">' +
|
|
2316
|
+
values[values.length - 1].toString() +
|
|
2317
|
+
'</text>';
|
|
2318
|
+
this._html += '<path fill="none" d="M' + this._labelWidth + ' ' + graphMiddle;
|
|
2319
|
+
this._html += 'H' + (width + averageCenter);
|
|
2320
|
+
this._html += '" stroke-width="1" stroke="lightgray" stroke-dasharray="10,10"/>';
|
|
2321
|
+
this._html += '<path fill="none" stroke-width="1" stroke="brown" d="M';
|
|
2322
|
+
let min = Number.POSITIVE_INFINITY;
|
|
2323
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
2324
|
+
for (let i = 0; i < values.length; ++i) {
|
|
2325
|
+
const value = parseFloat(values[i].toString());
|
|
2326
|
+
if (value < min) {
|
|
2327
|
+
min = value;
|
|
2328
|
+
}
|
|
2329
|
+
if (value > max) {
|
|
2330
|
+
max = value;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
let range = this._ranges[key];
|
|
2334
|
+
if (!range) {
|
|
2335
|
+
this._ranges[key] = range = { min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY };
|
|
2336
|
+
}
|
|
2337
|
+
range.min = Math.min(range.min, min);
|
|
2338
|
+
range.max = Math.max(range.max, max);
|
|
2339
|
+
const delta = range.max - range.min;
|
|
2340
|
+
let minCircle = '';
|
|
2341
|
+
let maxCircle = '';
|
|
2342
|
+
for (let i = 0; i < values.length; ++i) {
|
|
2343
|
+
x -= this._stepSize;
|
|
2344
|
+
const value = parseFloat(values[i].toString());
|
|
2345
|
+
const y = graphMiddle + (delta ? Math.round((0.5 - (value - range.min) / delta) * graphHeight) : 0);
|
|
2346
|
+
this._html += x + ' ' + y + ' ';
|
|
2347
|
+
if (value === min) {
|
|
2348
|
+
maxCircle = maxCircle || this._drawCircle(x, y, value);
|
|
2349
|
+
}
|
|
2350
|
+
else if (value === max) {
|
|
2351
|
+
minCircle = minCircle || this._drawCircle(x, y, value);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
this._html += '" />'; // end path
|
|
2355
|
+
// Average
|
|
2356
|
+
const average = Math.round((max - min) / 2);
|
|
2357
|
+
this._html += '<text text-anchor="middle" font-size="' + this._legendFontSize + '" y="' + textY + '">';
|
|
2358
|
+
this._html +=
|
|
2359
|
+
'<tspan x="' +
|
|
2360
|
+
(width + averageCenter) +
|
|
2361
|
+
'" dy="-0.5em">' +
|
|
2362
|
+
(min !== max ? '≈' : '=') +
|
|
2363
|
+
(min + average) +
|
|
2364
|
+
'</tspan>';
|
|
2365
|
+
this._html += '<tspan x="' + (width + averageCenter) + '" dy="1em">±' + average + '</tspan>';
|
|
2366
|
+
this._html += '</text>';
|
|
2367
|
+
this._html += minCircle + maxCircle;
|
|
2368
|
+
this._html += '</svg>';
|
|
2369
|
+
}
|
|
2370
|
+
requestAnimationFrame(() => {
|
|
2371
|
+
if (this._html != null) {
|
|
2372
|
+
this._ui.innerHTML = this._html;
|
|
2373
|
+
this._html = undefined;
|
|
2374
|
+
}
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
_drawCircle(x, y, value) {
|
|
2378
|
+
let circle = '<circle cx="' + x + '" cy="' + y + '" r="2" fill="green" />';
|
|
2379
|
+
const legendFontHeight = 0.7 * this._legendFontSize;
|
|
2380
|
+
const graphMiddle = Math.round(this._lineHeight / 2);
|
|
2381
|
+
if (y < graphMiddle) {
|
|
2382
|
+
// legend below
|
|
2383
|
+
y += this.textMargin + legendFontHeight;
|
|
2384
|
+
}
|
|
2385
|
+
else {
|
|
2386
|
+
// legend above
|
|
2387
|
+
y -= this.textMargin;
|
|
2388
|
+
}
|
|
2389
|
+
circle +=
|
|
2390
|
+
'<text font-style="italic" font-size="' +
|
|
2391
|
+
this._legendFontSize +
|
|
2392
|
+
'" x="' +
|
|
2393
|
+
(x - this._legendFontSize) +
|
|
2394
|
+
'" y="' +
|
|
2395
|
+
y +
|
|
2396
|
+
'">' +
|
|
2397
|
+
value +
|
|
2398
|
+
'</text>';
|
|
2399
|
+
return circle;
|
|
2400
|
+
}
|
|
2401
|
+
}/**
|
|
2402
|
+
* Copyright 2024 Ceeblue B.V.
|
|
2403
|
+
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
2404
|
+
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
2405
|
+
*/
|
|
2406
|
+
const VERSION = '3.2.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
|