@mebius-io/web 0.4.7 → 0.4.9
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 +171 -19
- 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 +171 -19
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +171 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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;
|
|
@@ -42884,6 +42922,9 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42884
42922
|
};
|
|
42885
42923
|
|
|
42886
42924
|
// src/internal/scale-view-transport.ts
|
|
42925
|
+
function retryWarmupNotFound(cfg, retryCount, res, retry) {
|
|
42926
|
+
return retry || retryCount < (cfg?.maxNumRetry ?? 0) && res?.code === 404;
|
|
42927
|
+
}
|
|
42887
42928
|
var HlsViewTransport = class {
|
|
42888
42929
|
/**
|
|
42889
42930
|
* deliveryPath, when given, is a gateway-relative path from the gateway's own
|
|
@@ -42894,6 +42935,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42894
42935
|
constructor(signaling, deliveryPath) {
|
|
42895
42936
|
this.signaling = signaling;
|
|
42896
42937
|
this.deliveryPath = deliveryPath;
|
|
42938
|
+
this.kind = "hls";
|
|
42897
42939
|
this.hls = null;
|
|
42898
42940
|
this.video = null;
|
|
42899
42941
|
this.endedCb = null;
|
|
@@ -42930,7 +42972,22 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42930
42972
|
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
42931
42973
|
return;
|
|
42932
42974
|
}
|
|
42933
|
-
const hls = new Hls2({
|
|
42975
|
+
const hls = new Hls2({
|
|
42976
|
+
maxLiveSyncPlaybackRate: 1.1,
|
|
42977
|
+
manifestLoadPolicy: {
|
|
42978
|
+
default: {
|
|
42979
|
+
maxTimeToFirstByteMs: 1e4,
|
|
42980
|
+
maxLoadTimeMs: 2e4,
|
|
42981
|
+
timeoutRetry: { maxNumRetry: 2, retryDelayMs: 0, maxRetryDelayMs: 0 },
|
|
42982
|
+
errorRetry: {
|
|
42983
|
+
maxNumRetry: 5,
|
|
42984
|
+
retryDelayMs: 500,
|
|
42985
|
+
maxRetryDelayMs: 2e3,
|
|
42986
|
+
shouldRetry: (cfg, retryCount, _isTimeout, res, retry) => retryWarmupNotFound(cfg, retryCount, res, retry)
|
|
42987
|
+
}
|
|
42988
|
+
}
|
|
42989
|
+
}
|
|
42990
|
+
});
|
|
42934
42991
|
this.hls = hls;
|
|
42935
42992
|
hls.on(Hls2.Events.ERROR, (_evt, data) => {
|
|
42936
42993
|
if (data.fatal) this.bufferingCb?.();
|
|
@@ -42999,6 +43056,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42999
43056
|
constructor(signaling, deliveryPath) {
|
|
43000
43057
|
this.signaling = signaling;
|
|
43001
43058
|
this.deliveryPath = deliveryPath;
|
|
43059
|
+
this.kind = "flv_js";
|
|
43002
43060
|
this.player = null;
|
|
43003
43061
|
this.video = null;
|
|
43004
43062
|
this.endedCb = null;
|
|
@@ -43007,6 +43065,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43007
43065
|
this.listeners = null;
|
|
43008
43066
|
/** True when playback only started because the element had to be muted. */
|
|
43009
43067
|
this.mutedByPolicy = false;
|
|
43068
|
+
/** Decoded-frame count and timestamp of the previous getStats() call. */
|
|
43069
|
+
this.lastFrames = null;
|
|
43010
43070
|
}
|
|
43011
43071
|
onEnded(cb) {
|
|
43012
43072
|
this.endedCb = cb;
|
|
@@ -43070,13 +43130,33 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43070
43130
|
}
|
|
43071
43131
|
this.video = null;
|
|
43072
43132
|
}
|
|
43133
|
+
/**
|
|
43134
|
+
* Real playback statistics for this route.
|
|
43135
|
+
*
|
|
43136
|
+
* Both numbers used to be hardcoded zeros, which is worse than reporting
|
|
43137
|
+
* nothing: the dashboard cannot tell a measured 0 kbps from an unmeasured
|
|
43138
|
+
* one, so every flv.js viewer in production showed a downlink of 0 and the
|
|
43139
|
+
* column read as a total outage. flv.js measures throughput itself
|
|
43140
|
+
* (`statisticsInfo.speed`, KB/s), and the element counts decoded frames, so
|
|
43141
|
+
* frame rate is the delta between two calls. Anything genuinely unavailable
|
|
43142
|
+
* is left undefined rather than zeroed.
|
|
43143
|
+
*/
|
|
43073
43144
|
async getStats() {
|
|
43074
43145
|
if (!this.video) return null;
|
|
43075
|
-
|
|
43076
|
-
|
|
43077
|
-
|
|
43078
|
-
|
|
43079
|
-
|
|
43146
|
+
const speedKBs = this.player?.statisticsInfo?.speed;
|
|
43147
|
+
const bitrateKbps = typeof speedKBs === "number" ? Math.round(speedKBs * 8) : void 0;
|
|
43148
|
+
let framesPerSecond;
|
|
43149
|
+
const q = this.video.getVideoPlaybackQuality?.();
|
|
43150
|
+
const count = q?.totalVideoFrames;
|
|
43151
|
+
const atMs = Date.now();
|
|
43152
|
+
if (typeof count === "number") {
|
|
43153
|
+
const prev = this.lastFrames;
|
|
43154
|
+
if (prev && atMs > prev.atMs && count >= prev.count) {
|
|
43155
|
+
framesPerSecond = Math.round((count - prev.count) * 1e3 / (atMs - prev.atMs));
|
|
43156
|
+
}
|
|
43157
|
+
this.lastFrames = { count, atMs };
|
|
43158
|
+
}
|
|
43159
|
+
return { bitrateKbps, framesPerSecond, latencyMs: void 0 };
|
|
43080
43160
|
}
|
|
43081
43161
|
};
|
|
43082
43162
|
|
|
@@ -43112,7 +43192,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43112
43192
|
}
|
|
43113
43193
|
|
|
43114
43194
|
// src/internal/telemetry.ts
|
|
43115
|
-
var SDK_VERSION = "web/0.4.
|
|
43195
|
+
var SDK_VERSION = "web/0.4.8";
|
|
43116
43196
|
var FLUSH_INTERVAL_MS = 15e3;
|
|
43117
43197
|
var MAX_BATCH = 64;
|
|
43118
43198
|
function describeDevice() {
|
|
@@ -43125,11 +43205,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43125
43205
|
return conn?.effectiveType ? { type: conn.effectiveType } : void 0;
|
|
43126
43206
|
}
|
|
43127
43207
|
var QoeReporter = class {
|
|
43128
|
-
constructor(target, role, streamId, userId) {
|
|
43208
|
+
constructor(target, role, streamId, userId, playerKind) {
|
|
43129
43209
|
this.target = target;
|
|
43130
43210
|
this.role = role;
|
|
43131
43211
|
this.streamId = streamId;
|
|
43132
43212
|
this.userId = userId;
|
|
43213
|
+
this.playerKind = playerKind;
|
|
43133
43214
|
this.sessionId = randomId();
|
|
43134
43215
|
this.buffer = [];
|
|
43135
43216
|
this.timer = null;
|
|
@@ -43165,6 +43246,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43165
43246
|
streamId: this.streamId,
|
|
43166
43247
|
role: this.role,
|
|
43167
43248
|
userId: this.userId,
|
|
43249
|
+
playerKind: this.playerKind,
|
|
43168
43250
|
samples,
|
|
43169
43251
|
device: describeDevice(),
|
|
43170
43252
|
network: describeNetwork()
|
|
@@ -43289,7 +43371,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43289
43371
|
ts: Math.floor(Date.now() / 1e3),
|
|
43290
43372
|
bitrateKbps: stats.bitrateKbps,
|
|
43291
43373
|
fps: stats.framesPerSecond,
|
|
43292
|
-
rttMs: stats.rttMs
|
|
43374
|
+
rttMs: stats.rttMs,
|
|
43375
|
+
packetLossPct: stats.packetLossPct
|
|
43293
43376
|
});
|
|
43294
43377
|
}, STATS_INTERVAL_MS);
|
|
43295
43378
|
}
|
|
@@ -43303,6 +43386,59 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43303
43386
|
return c;
|
|
43304
43387
|
}
|
|
43305
43388
|
|
|
43389
|
+
// src/internal/freeze-clock.ts
|
|
43390
|
+
var FreezeClock = class {
|
|
43391
|
+
constructor(now2 = Date.now) {
|
|
43392
|
+
this.now = now2;
|
|
43393
|
+
/** When the current stall began, or null when playback is running. */
|
|
43394
|
+
this.stalledSinceMs = null;
|
|
43395
|
+
/** Stall time that has ended but has not yet been shipped with a sample. */
|
|
43396
|
+
this.pendingMs = 0;
|
|
43397
|
+
}
|
|
43398
|
+
/** True while a stall is in progress. */
|
|
43399
|
+
get stalled() {
|
|
43400
|
+
return this.stalledSinceMs !== null;
|
|
43401
|
+
}
|
|
43402
|
+
/**
|
|
43403
|
+
* Begin a stall. Re-entering while already stalled is ignored rather than
|
|
43404
|
+
* restarting the clock: flv.js fires `waiting` repeatedly through a single
|
|
43405
|
+
* long stall, and resetting the start on each would report a fraction of the
|
|
43406
|
+
* freeze that actually happened.
|
|
43407
|
+
*/
|
|
43408
|
+
beginStall() {
|
|
43409
|
+
if (this.stalledSinceMs === null) this.stalledSinceMs = this.now();
|
|
43410
|
+
}
|
|
43411
|
+
/** End the current stall and bank its duration. No-op when not stalled. */
|
|
43412
|
+
endStall() {
|
|
43413
|
+
if (this.stalledSinceMs === null) return;
|
|
43414
|
+
this.pendingMs += Math.max(0, this.now() - this.stalledSinceMs);
|
|
43415
|
+
this.stalledSinceMs = null;
|
|
43416
|
+
}
|
|
43417
|
+
/**
|
|
43418
|
+
* Freeze milliseconds to report on this tick, resetting the counter.
|
|
43419
|
+
*
|
|
43420
|
+
* A stall still in progress is counted up to now and its clock restarted, so
|
|
43421
|
+
* a freeze longer than the sample interval is reported while it is happening
|
|
43422
|
+
* rather than landing whole in whichever sample eventually follows it. Every
|
|
43423
|
+
* millisecond is attributed exactly once — never dropped, never double-counted.
|
|
43424
|
+
*/
|
|
43425
|
+
take() {
|
|
43426
|
+
if (this.stalledSinceMs !== null) {
|
|
43427
|
+
const now2 = this.now();
|
|
43428
|
+
this.pendingMs += Math.max(0, now2 - this.stalledSinceMs);
|
|
43429
|
+
this.stalledSinceMs = now2;
|
|
43430
|
+
}
|
|
43431
|
+
const ms = this.pendingMs;
|
|
43432
|
+
this.pendingMs = 0;
|
|
43433
|
+
return ms;
|
|
43434
|
+
}
|
|
43435
|
+
/** Forget everything. Called when a session ends. */
|
|
43436
|
+
reset() {
|
|
43437
|
+
this.stalledSinceMs = null;
|
|
43438
|
+
this.pendingMs = 0;
|
|
43439
|
+
}
|
|
43440
|
+
};
|
|
43441
|
+
|
|
43306
43442
|
// src/player.ts
|
|
43307
43443
|
var STATS_INTERVAL_MS2 = 2e3;
|
|
43308
43444
|
var FIRST_FRAME_TIMEOUT_MS = 8e3;
|
|
@@ -43320,6 +43456,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43320
43456
|
this.reporter = null;
|
|
43321
43457
|
/** True between a `buffering` event and the element actually resuming. */
|
|
43322
43458
|
this.stalled = false;
|
|
43459
|
+
/** Measures how long playback was actually frozen; see FreezeClock. */
|
|
43460
|
+
this.freeze = new FreezeClock();
|
|
43323
43461
|
/** Cancels element listeners bound for the lifetime of one play(). */
|
|
43324
43462
|
this.elementListeners = null;
|
|
43325
43463
|
this.candidates = createViewCandidates(options.mode ?? "auto", signaling, deliveries);
|
|
@@ -43338,6 +43476,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43338
43476
|
() => {
|
|
43339
43477
|
if (!this.stalled || !this.playing) return;
|
|
43340
43478
|
this.stalled = false;
|
|
43479
|
+
this.freeze.endStall();
|
|
43341
43480
|
this.emit("playing", { streamId });
|
|
43342
43481
|
},
|
|
43343
43482
|
{ signal: this.elementListeners.signal }
|
|
@@ -43353,7 +43492,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43353
43492
|
this.transport = candidate;
|
|
43354
43493
|
this.playing = true;
|
|
43355
43494
|
if (this.telemetry) {
|
|
43356
|
-
this.reporter = new QoeReporter(
|
|
43495
|
+
this.reporter = new QoeReporter(
|
|
43496
|
+
this.telemetry,
|
|
43497
|
+
"play",
|
|
43498
|
+
streamId,
|
|
43499
|
+
this.userId,
|
|
43500
|
+
candidate.kind
|
|
43501
|
+
);
|
|
43357
43502
|
this.reporter.start();
|
|
43358
43503
|
this.reporter.add({ ts: Math.floor(Date.now() / 1e3), firstFrameMs: Date.now() - startedAtMs });
|
|
43359
43504
|
}
|
|
@@ -43381,6 +43526,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43381
43526
|
ELEMENT_OWNER.delete(this.video);
|
|
43382
43527
|
}
|
|
43383
43528
|
this.stalled = false;
|
|
43529
|
+
this.freeze.reset();
|
|
43384
43530
|
this.stopStats();
|
|
43385
43531
|
await this.reporter?.stop();
|
|
43386
43532
|
this.reporter = null;
|
|
@@ -43420,6 +43566,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43420
43566
|
});
|
|
43421
43567
|
transport.onBuffering(() => {
|
|
43422
43568
|
if (this.transport !== transport) return;
|
|
43569
|
+
this.freeze.beginStall();
|
|
43423
43570
|
this.stalled = true;
|
|
43424
43571
|
this.emit("buffering", void 0);
|
|
43425
43572
|
});
|
|
@@ -43427,12 +43574,17 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43427
43574
|
startStats() {
|
|
43428
43575
|
this.statsTimer = setInterval(async () => {
|
|
43429
43576
|
const stats = await this.transport?.getStats();
|
|
43430
|
-
|
|
43577
|
+
const freezeMs = this.freeze.take();
|
|
43578
|
+
if (!stats) {
|
|
43579
|
+
if (freezeMs > 0) this.reporter?.add({ ts: Math.floor(Date.now() / 1e3), freezeMs });
|
|
43580
|
+
return;
|
|
43581
|
+
}
|
|
43431
43582
|
this.emit("stats", stats);
|
|
43432
43583
|
this.reporter?.add({
|
|
43433
43584
|
ts: Math.floor(Date.now() / 1e3),
|
|
43434
43585
|
bitrateKbps: stats.bitrateKbps,
|
|
43435
|
-
fps: stats.framesPerSecond
|
|
43586
|
+
fps: stats.framesPerSecond,
|
|
43587
|
+
freezeMs
|
|
43436
43588
|
});
|
|
43437
43589
|
}, STATS_INTERVAL_MS2);
|
|
43438
43590
|
}
|