@mebius-io/web 0.4.7 → 0.4.8
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/dist/index.cjs +152 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -9
- package/dist/index.d.ts +26 -9
- package/dist/index.global.js +152 -18
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +152 -18
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -90,21 +90,36 @@ interface PlayerOptions {
|
|
|
90
90
|
* resolves to one.
|
|
91
91
|
*/
|
|
92
92
|
type ViewTarget = HTMLVideoElement | string;
|
|
93
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Live broadcast statistics, emitted periodically on the `"stats"` event.
|
|
95
|
+
*
|
|
96
|
+
* Every field is optional: the first tick of a session has no previous sample
|
|
97
|
+
* to difference against, and a value that was never measured must stay absent
|
|
98
|
+
* rather than be reported as a confident zero.
|
|
99
|
+
*/
|
|
94
100
|
interface BroadcastStats {
|
|
95
|
-
/** Outbound bitrate in kilobits per second. */
|
|
96
|
-
bitrateKbps
|
|
101
|
+
/** Outbound bitrate in kilobits per second — what is actually being sent. */
|
|
102
|
+
bitrateKbps?: number;
|
|
97
103
|
/** Frames per second currently being sent. */
|
|
98
|
-
framesPerSecond
|
|
104
|
+
framesPerSecond?: number;
|
|
99
105
|
/** Round-trip time to the gateway in milliseconds, if known. */
|
|
100
106
|
rttMs?: number;
|
|
107
|
+
/** Percentage of sent packets the receiver reported missing, if known. */
|
|
108
|
+
packetLossPct?: number;
|
|
101
109
|
}
|
|
102
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Live playback statistics, emitted periodically on the `"stats"` event.
|
|
112
|
+
*
|
|
113
|
+
* Fields are optional because not every route can measure every one, and a
|
|
114
|
+
* transport that reports 0 for something it never measured is indistinguishable
|
|
115
|
+
* from a stream that is genuinely delivering nothing — which is exactly how
|
|
116
|
+
* "0 kbps downlink" ended up on every viewer in the dashboard.
|
|
117
|
+
*/
|
|
103
118
|
interface PlaybackStats {
|
|
104
|
-
/** Inbound bitrate in kilobits per second. */
|
|
105
|
-
bitrateKbps
|
|
106
|
-
/** Frames per second currently being rendered. */
|
|
107
|
-
framesPerSecond
|
|
119
|
+
/** Inbound bitrate in kilobits per second, when the route can measure it. */
|
|
120
|
+
bitrateKbps?: number;
|
|
121
|
+
/** Frames per second currently being rendered, when known. */
|
|
122
|
+
framesPerSecond?: number;
|
|
108
123
|
/** Estimated end-to-end latency in milliseconds, if known. */
|
|
109
124
|
latencyMs?: number;
|
|
110
125
|
}
|
|
@@ -275,6 +290,8 @@ declare class MebiusPlayer extends TypedEmitter<PlayerEventMap> {
|
|
|
275
290
|
private reporter;
|
|
276
291
|
/** True between a `buffering` event and the element actually resuming. */
|
|
277
292
|
private stalled;
|
|
293
|
+
/** Measures how long playback was actually frozen; see FreezeClock. */
|
|
294
|
+
private readonly freeze;
|
|
278
295
|
/** Cancels element listeners bound for the lifetime of one play(). */
|
|
279
296
|
private elementListeners;
|
|
280
297
|
/** @internal */
|
package/dist/index.global.js
CHANGED
|
@@ -42724,6 +42724,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42724
42724
|
this.signaling = signaling;
|
|
42725
42725
|
this.pc = null;
|
|
42726
42726
|
this.resourceUrl = null;
|
|
42727
|
+
/** Bytes sent and packet counters at the previous getStats() call. */
|
|
42728
|
+
this.lastOutbound = null;
|
|
42727
42729
|
}
|
|
42728
42730
|
async start(streamId, stream) {
|
|
42729
42731
|
const pc = new RTCPeerConnection(DEFAULT_RTC_CONFIG);
|
|
@@ -42756,26 +42758,61 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42756
42758
|
this.pc?.close();
|
|
42757
42759
|
this.pc = null;
|
|
42758
42760
|
}
|
|
42761
|
+
/**
|
|
42762
|
+
* Live broadcast statistics.
|
|
42763
|
+
*
|
|
42764
|
+
* Two corrections over the obvious reading of RTCStats:
|
|
42765
|
+
*
|
|
42766
|
+
* `bitrateKbps` is the delta of `outbound-rtp.bytesSent`, not
|
|
42767
|
+
* `availableOutgoingBitrate`. The latter is the congestion controller's
|
|
42768
|
+
* ESTIMATE of headroom, so a broadcaster on a fast link reported several
|
|
42769
|
+
* megabits while actually sending a fraction of that — the dashboard's
|
|
42770
|
+
* "bitrate adherence" score was measuring the network, not the encoder.
|
|
42771
|
+
*
|
|
42772
|
+
* `packetLossPct` comes from the receiver's report (`remote-inbound-rtp`),
|
|
42773
|
+
* which is the only place that knows what did not arrive. It was never
|
|
42774
|
+
* reported at all, and publishQualityScore treats a missing value as zero
|
|
42775
|
+
* loss — so every publisher scored full marks on a fifth of the rubric no
|
|
42776
|
+
* matter how bad the uplink was.
|
|
42777
|
+
*/
|
|
42759
42778
|
async getStats() {
|
|
42760
42779
|
if (!this.pc) return null;
|
|
42761
42780
|
const report = await this.pc.getStats();
|
|
42762
|
-
let
|
|
42763
|
-
let framesPerSecond = 0;
|
|
42781
|
+
let framesPerSecond;
|
|
42764
42782
|
let rttMs;
|
|
42783
|
+
let packetLossPct;
|
|
42784
|
+
let bytesSent;
|
|
42785
|
+
let packetsSent;
|
|
42786
|
+
let packetsLost;
|
|
42765
42787
|
report.forEach((stat) => {
|
|
42766
42788
|
if (stat.type === "outbound-rtp" && !stat.isRemote) {
|
|
42767
42789
|
if (typeof stat.framesPerSecond === "number") framesPerSecond = stat.framesPerSecond;
|
|
42790
|
+
if (typeof stat.bytesSent === "number") bytesSent = (bytesSent ?? 0) + stat.bytesSent;
|
|
42791
|
+
if (typeof stat.packetsSent === "number") packetsSent = (packetsSent ?? 0) + stat.packetsSent;
|
|
42792
|
+
}
|
|
42793
|
+
if (stat.type === "remote-inbound-rtp") {
|
|
42794
|
+
if (typeof stat.packetsLost === "number") packetsLost = (packetsLost ?? 0) + stat.packetsLost;
|
|
42795
|
+
if (typeof stat.roundTripTime === "number") rttMs = Math.round(stat.roundTripTime * 1e3);
|
|
42768
42796
|
}
|
|
42769
42797
|
if (stat.type === "candidate-pair" && stat.state === "succeeded") {
|
|
42770
|
-
if (typeof stat.
|
|
42771
|
-
bitrateKbps = Math.round(stat.availableOutgoingBitrate / 1e3);
|
|
42772
|
-
}
|
|
42773
|
-
if (typeof stat.currentRoundTripTime === "number") {
|
|
42798
|
+
if (rttMs == null && typeof stat.currentRoundTripTime === "number") {
|
|
42774
42799
|
rttMs = Math.round(stat.currentRoundTripTime * 1e3);
|
|
42775
42800
|
}
|
|
42776
42801
|
}
|
|
42777
42802
|
});
|
|
42778
|
-
|
|
42803
|
+
let bitrateKbps;
|
|
42804
|
+
const atMs = Date.now();
|
|
42805
|
+
if (bytesSent != null) {
|
|
42806
|
+
const prev = this.lastOutbound;
|
|
42807
|
+
if (prev && atMs > prev.atMs && bytesSent >= prev.bytes) {
|
|
42808
|
+
bitrateKbps = Math.round((bytesSent - prev.bytes) * 8 / 1e3 / ((atMs - prev.atMs) / 1e3));
|
|
42809
|
+
}
|
|
42810
|
+
this.lastOutbound = { bytes: bytesSent, atMs };
|
|
42811
|
+
}
|
|
42812
|
+
if (packetsLost != null && packetsSent != null && packetsSent > 0) {
|
|
42813
|
+
packetLossPct = Math.max(0, Math.min(100, packetsLost / packetsSent * 100));
|
|
42814
|
+
}
|
|
42815
|
+
return { bitrateKbps, framesPerSecond, rttMs, packetLossPct };
|
|
42779
42816
|
}
|
|
42780
42817
|
};
|
|
42781
42818
|
|
|
@@ -42805,6 +42842,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42805
42842
|
var WhepViewTransport = class {
|
|
42806
42843
|
constructor(signaling) {
|
|
42807
42844
|
this.signaling = signaling;
|
|
42845
|
+
this.kind = "whep";
|
|
42808
42846
|
this.pc = null;
|
|
42809
42847
|
this.resourceUrl = null;
|
|
42810
42848
|
this.endedCb = null;
|
|
@@ -42894,6 +42932,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42894
42932
|
constructor(signaling, deliveryPath) {
|
|
42895
42933
|
this.signaling = signaling;
|
|
42896
42934
|
this.deliveryPath = deliveryPath;
|
|
42935
|
+
this.kind = "hls";
|
|
42897
42936
|
this.hls = null;
|
|
42898
42937
|
this.video = null;
|
|
42899
42938
|
this.endedCb = null;
|
|
@@ -42999,6 +43038,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42999
43038
|
constructor(signaling, deliveryPath) {
|
|
43000
43039
|
this.signaling = signaling;
|
|
43001
43040
|
this.deliveryPath = deliveryPath;
|
|
43041
|
+
this.kind = "flv_js";
|
|
43002
43042
|
this.player = null;
|
|
43003
43043
|
this.video = null;
|
|
43004
43044
|
this.endedCb = null;
|
|
@@ -43007,6 +43047,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43007
43047
|
this.listeners = null;
|
|
43008
43048
|
/** True when playback only started because the element had to be muted. */
|
|
43009
43049
|
this.mutedByPolicy = false;
|
|
43050
|
+
/** Decoded-frame count and timestamp of the previous getStats() call. */
|
|
43051
|
+
this.lastFrames = null;
|
|
43010
43052
|
}
|
|
43011
43053
|
onEnded(cb) {
|
|
43012
43054
|
this.endedCb = cb;
|
|
@@ -43070,13 +43112,33 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43070
43112
|
}
|
|
43071
43113
|
this.video = null;
|
|
43072
43114
|
}
|
|
43115
|
+
/**
|
|
43116
|
+
* Real playback statistics for this route.
|
|
43117
|
+
*
|
|
43118
|
+
* Both numbers used to be hardcoded zeros, which is worse than reporting
|
|
43119
|
+
* nothing: the dashboard cannot tell a measured 0 kbps from an unmeasured
|
|
43120
|
+
* one, so every flv.js viewer in production showed a downlink of 0 and the
|
|
43121
|
+
* column read as a total outage. flv.js measures throughput itself
|
|
43122
|
+
* (`statisticsInfo.speed`, KB/s), and the element counts decoded frames, so
|
|
43123
|
+
* frame rate is the delta between two calls. Anything genuinely unavailable
|
|
43124
|
+
* is left undefined rather than zeroed.
|
|
43125
|
+
*/
|
|
43073
43126
|
async getStats() {
|
|
43074
43127
|
if (!this.video) return null;
|
|
43075
|
-
|
|
43076
|
-
|
|
43077
|
-
|
|
43078
|
-
|
|
43079
|
-
|
|
43128
|
+
const speedKBs = this.player?.statisticsInfo?.speed;
|
|
43129
|
+
const bitrateKbps = typeof speedKBs === "number" ? Math.round(speedKBs * 8) : void 0;
|
|
43130
|
+
let framesPerSecond;
|
|
43131
|
+
const q = this.video.getVideoPlaybackQuality?.();
|
|
43132
|
+
const count = q?.totalVideoFrames;
|
|
43133
|
+
const atMs = Date.now();
|
|
43134
|
+
if (typeof count === "number") {
|
|
43135
|
+
const prev = this.lastFrames;
|
|
43136
|
+
if (prev && atMs > prev.atMs && count >= prev.count) {
|
|
43137
|
+
framesPerSecond = Math.round((count - prev.count) * 1e3 / (atMs - prev.atMs));
|
|
43138
|
+
}
|
|
43139
|
+
this.lastFrames = { count, atMs };
|
|
43140
|
+
}
|
|
43141
|
+
return { bitrateKbps, framesPerSecond, latencyMs: void 0 };
|
|
43080
43142
|
}
|
|
43081
43143
|
};
|
|
43082
43144
|
|
|
@@ -43112,7 +43174,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43112
43174
|
}
|
|
43113
43175
|
|
|
43114
43176
|
// src/internal/telemetry.ts
|
|
43115
|
-
var SDK_VERSION = "web/0.4.
|
|
43177
|
+
var SDK_VERSION = "web/0.4.8";
|
|
43116
43178
|
var FLUSH_INTERVAL_MS = 15e3;
|
|
43117
43179
|
var MAX_BATCH = 64;
|
|
43118
43180
|
function describeDevice() {
|
|
@@ -43125,11 +43187,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43125
43187
|
return conn?.effectiveType ? { type: conn.effectiveType } : void 0;
|
|
43126
43188
|
}
|
|
43127
43189
|
var QoeReporter = class {
|
|
43128
|
-
constructor(target, role, streamId, userId) {
|
|
43190
|
+
constructor(target, role, streamId, userId, playerKind) {
|
|
43129
43191
|
this.target = target;
|
|
43130
43192
|
this.role = role;
|
|
43131
43193
|
this.streamId = streamId;
|
|
43132
43194
|
this.userId = userId;
|
|
43195
|
+
this.playerKind = playerKind;
|
|
43133
43196
|
this.sessionId = randomId();
|
|
43134
43197
|
this.buffer = [];
|
|
43135
43198
|
this.timer = null;
|
|
@@ -43165,6 +43228,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43165
43228
|
streamId: this.streamId,
|
|
43166
43229
|
role: this.role,
|
|
43167
43230
|
userId: this.userId,
|
|
43231
|
+
playerKind: this.playerKind,
|
|
43168
43232
|
samples,
|
|
43169
43233
|
device: describeDevice(),
|
|
43170
43234
|
network: describeNetwork()
|
|
@@ -43289,7 +43353,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43289
43353
|
ts: Math.floor(Date.now() / 1e3),
|
|
43290
43354
|
bitrateKbps: stats.bitrateKbps,
|
|
43291
43355
|
fps: stats.framesPerSecond,
|
|
43292
|
-
rttMs: stats.rttMs
|
|
43356
|
+
rttMs: stats.rttMs,
|
|
43357
|
+
packetLossPct: stats.packetLossPct
|
|
43293
43358
|
});
|
|
43294
43359
|
}, STATS_INTERVAL_MS);
|
|
43295
43360
|
}
|
|
@@ -43303,6 +43368,59 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43303
43368
|
return c;
|
|
43304
43369
|
}
|
|
43305
43370
|
|
|
43371
|
+
// src/internal/freeze-clock.ts
|
|
43372
|
+
var FreezeClock = class {
|
|
43373
|
+
constructor(now2 = Date.now) {
|
|
43374
|
+
this.now = now2;
|
|
43375
|
+
/** When the current stall began, or null when playback is running. */
|
|
43376
|
+
this.stalledSinceMs = null;
|
|
43377
|
+
/** Stall time that has ended but has not yet been shipped with a sample. */
|
|
43378
|
+
this.pendingMs = 0;
|
|
43379
|
+
}
|
|
43380
|
+
/** True while a stall is in progress. */
|
|
43381
|
+
get stalled() {
|
|
43382
|
+
return this.stalledSinceMs !== null;
|
|
43383
|
+
}
|
|
43384
|
+
/**
|
|
43385
|
+
* Begin a stall. Re-entering while already stalled is ignored rather than
|
|
43386
|
+
* restarting the clock: flv.js fires `waiting` repeatedly through a single
|
|
43387
|
+
* long stall, and resetting the start on each would report a fraction of the
|
|
43388
|
+
* freeze that actually happened.
|
|
43389
|
+
*/
|
|
43390
|
+
beginStall() {
|
|
43391
|
+
if (this.stalledSinceMs === null) this.stalledSinceMs = this.now();
|
|
43392
|
+
}
|
|
43393
|
+
/** End the current stall and bank its duration. No-op when not stalled. */
|
|
43394
|
+
endStall() {
|
|
43395
|
+
if (this.stalledSinceMs === null) return;
|
|
43396
|
+
this.pendingMs += Math.max(0, this.now() - this.stalledSinceMs);
|
|
43397
|
+
this.stalledSinceMs = null;
|
|
43398
|
+
}
|
|
43399
|
+
/**
|
|
43400
|
+
* Freeze milliseconds to report on this tick, resetting the counter.
|
|
43401
|
+
*
|
|
43402
|
+
* A stall still in progress is counted up to now and its clock restarted, so
|
|
43403
|
+
* a freeze longer than the sample interval is reported while it is happening
|
|
43404
|
+
* rather than landing whole in whichever sample eventually follows it. Every
|
|
43405
|
+
* millisecond is attributed exactly once — never dropped, never double-counted.
|
|
43406
|
+
*/
|
|
43407
|
+
take() {
|
|
43408
|
+
if (this.stalledSinceMs !== null) {
|
|
43409
|
+
const now2 = this.now();
|
|
43410
|
+
this.pendingMs += Math.max(0, now2 - this.stalledSinceMs);
|
|
43411
|
+
this.stalledSinceMs = now2;
|
|
43412
|
+
}
|
|
43413
|
+
const ms = this.pendingMs;
|
|
43414
|
+
this.pendingMs = 0;
|
|
43415
|
+
return ms;
|
|
43416
|
+
}
|
|
43417
|
+
/** Forget everything. Called when a session ends. */
|
|
43418
|
+
reset() {
|
|
43419
|
+
this.stalledSinceMs = null;
|
|
43420
|
+
this.pendingMs = 0;
|
|
43421
|
+
}
|
|
43422
|
+
};
|
|
43423
|
+
|
|
43306
43424
|
// src/player.ts
|
|
43307
43425
|
var STATS_INTERVAL_MS2 = 2e3;
|
|
43308
43426
|
var FIRST_FRAME_TIMEOUT_MS = 8e3;
|
|
@@ -43320,6 +43438,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43320
43438
|
this.reporter = null;
|
|
43321
43439
|
/** True between a `buffering` event and the element actually resuming. */
|
|
43322
43440
|
this.stalled = false;
|
|
43441
|
+
/** Measures how long playback was actually frozen; see FreezeClock. */
|
|
43442
|
+
this.freeze = new FreezeClock();
|
|
43323
43443
|
/** Cancels element listeners bound for the lifetime of one play(). */
|
|
43324
43444
|
this.elementListeners = null;
|
|
43325
43445
|
this.candidates = createViewCandidates(options.mode ?? "auto", signaling, deliveries);
|
|
@@ -43338,6 +43458,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43338
43458
|
() => {
|
|
43339
43459
|
if (!this.stalled || !this.playing) return;
|
|
43340
43460
|
this.stalled = false;
|
|
43461
|
+
this.freeze.endStall();
|
|
43341
43462
|
this.emit("playing", { streamId });
|
|
43342
43463
|
},
|
|
43343
43464
|
{ signal: this.elementListeners.signal }
|
|
@@ -43353,7 +43474,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43353
43474
|
this.transport = candidate;
|
|
43354
43475
|
this.playing = true;
|
|
43355
43476
|
if (this.telemetry) {
|
|
43356
|
-
this.reporter = new QoeReporter(
|
|
43477
|
+
this.reporter = new QoeReporter(
|
|
43478
|
+
this.telemetry,
|
|
43479
|
+
"play",
|
|
43480
|
+
streamId,
|
|
43481
|
+
this.userId,
|
|
43482
|
+
candidate.kind
|
|
43483
|
+
);
|
|
43357
43484
|
this.reporter.start();
|
|
43358
43485
|
this.reporter.add({ ts: Math.floor(Date.now() / 1e3), firstFrameMs: Date.now() - startedAtMs });
|
|
43359
43486
|
}
|
|
@@ -43381,6 +43508,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43381
43508
|
ELEMENT_OWNER.delete(this.video);
|
|
43382
43509
|
}
|
|
43383
43510
|
this.stalled = false;
|
|
43511
|
+
this.freeze.reset();
|
|
43384
43512
|
this.stopStats();
|
|
43385
43513
|
await this.reporter?.stop();
|
|
43386
43514
|
this.reporter = null;
|
|
@@ -43420,6 +43548,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43420
43548
|
});
|
|
43421
43549
|
transport.onBuffering(() => {
|
|
43422
43550
|
if (this.transport !== transport) return;
|
|
43551
|
+
this.freeze.beginStall();
|
|
43423
43552
|
this.stalled = true;
|
|
43424
43553
|
this.emit("buffering", void 0);
|
|
43425
43554
|
});
|
|
@@ -43427,12 +43556,17 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43427
43556
|
startStats() {
|
|
43428
43557
|
this.statsTimer = setInterval(async () => {
|
|
43429
43558
|
const stats = await this.transport?.getStats();
|
|
43430
|
-
|
|
43559
|
+
const freezeMs = this.freeze.take();
|
|
43560
|
+
if (!stats) {
|
|
43561
|
+
if (freezeMs > 0) this.reporter?.add({ ts: Math.floor(Date.now() / 1e3), freezeMs });
|
|
43562
|
+
return;
|
|
43563
|
+
}
|
|
43431
43564
|
this.emit("stats", stats);
|
|
43432
43565
|
this.reporter?.add({
|
|
43433
43566
|
ts: Math.floor(Date.now() / 1e3),
|
|
43434
43567
|
bitrateKbps: stats.bitrateKbps,
|
|
43435
|
-
fps: stats.framesPerSecond
|
|
43568
|
+
fps: stats.framesPerSecond,
|
|
43569
|
+
freezeMs
|
|
43436
43570
|
});
|
|
43437
43571
|
}, STATS_INTERVAL_MS2);
|
|
43438
43572
|
}
|