Fix starting StreamStats multiple times

This commit is contained in:
redphx 2025-02-07 18:21:07 +07:00
parent ac20cc51cc
commit 2fb2cfb004
3 changed files with 9 additions and 5 deletions

View File

@ -3039,6 +3039,7 @@ class StreamStats {
static instance;
static getInstance = () => StreamStats.instance ?? (StreamStats.instance = new StreamStats);
LOG_TAG = "StreamStats";
isRunning = !1;
intervalId;
REFRESH_INTERVAL = 1000;
stats = {
@ -3097,12 +3098,12 @@ class StreamStats {
BxLogger.info(this.LOG_TAG, "constructor()"), this.boundOnStreamHudStateChanged = this.onStreamHudStateChanged.bind(this), BxEventBus.Stream.on("ui.streamHud.rendered", this.boundOnStreamHudStateChanged), this.render();
}
async start(glancing = !1) {
if (!this.isHidden() || glancing && this.isGlancing()) return;
this.intervalId && clearInterval(this.intervalId), await this.update(!0), this.$container.classList.remove("bx-gone"), this.$container.dataset.display = glancing ? "glancing" : "fixed", this.intervalId = window.setInterval(this.update, this.REFRESH_INTERVAL);
if (this.isRunning || !this.isHidden() || glancing && this.isGlancing()) return;
this.isRunning = !0, this.intervalId && clearInterval(this.intervalId), await this.update(!0), this.$container.classList.remove("bx-gone"), this.$container.dataset.display = glancing ? "glancing" : "fixed", this.intervalId = window.setInterval(this.update, this.REFRESH_INTERVAL);
}
async stop(glancing = !1) {
if (glancing && !this.isGlancing()) return;
this.intervalId && clearInterval(this.intervalId), this.intervalId = null, this.$container.removeAttribute("data-display"), this.$container.classList.add("bx-gone");
this.isRunning = !1, this.intervalId && clearInterval(this.intervalId), this.intervalId = null, this.$container.removeAttribute("data-display"), this.$container.classList.add("bx-gone");
}
async toggle() {
if (this.isGlancing()) this.$container && (this.$container.dataset.display = "fixed");

View File

@ -162,7 +162,7 @@ function isPlainObject(input) {return typeof input === "object" && input !== nul
class SoundShortcut {static adjustGainNodeVolume(amount) {if (!getGlobalPref("audio.volume.booster.enabled")) return 0;let currentValue = getStreamPref("audio.volume"), nearestValue;if (amount > 0) nearestValue = ceilToNearest(currentValue, amount);else nearestValue = floorToNearest(currentValue, -1 * amount);let newValue;if (currentValue !== nearestValue) newValue = nearestValue;else newValue = currentValue + amount;return newValue = setStreamPref("audio.volume", newValue, "direct"), SoundShortcut.setGainNodeVolume(newValue), Toast.show(`${t("stream")} ${t("volume")}`, newValue + "%", { instant: !0 }), newValue;}static setGainNodeVolume(value) {STATES.currentStream.audioGainNode && (STATES.currentStream.audioGainNode.gain.value = value / 100);}static muteUnmute() {if (getGlobalPref("audio.volume.booster.enabled") && STATES.currentStream.audioGainNode) {let gainValue = STATES.currentStream.audioGainNode.gain.value, settingValue = getStreamPref("audio.volume"), targetValue;if (settingValue === 0) targetValue = 100, setStreamPref("audio.volume", targetValue, "direct");else if (gainValue === 0) targetValue = settingValue;else targetValue = 0;let status;if (targetValue === 0) status = t("muted");else status = targetValue + "%";SoundShortcut.setGainNodeVolume(targetValue), Toast.show(`${t("stream")} ${t("volume")}`, status, { instant: !0 }), BxEventBus.Stream.emit("speaker.state.changed", {state: targetValue === 0 ? 1 : 0});return;}let $media = document.querySelector("div[data-testid=media-container] audio") ?? document.querySelector("div[data-testid=media-container] video");if ($media) {$media.muted = !$media.muted;let status = $media.muted ? t("muted") : t("unmuted");Toast.show(`${t("stream")} ${t("volume")}`, status, { instant: !0 }), BxEventBus.Stream.emit("speaker.state.changed", {state: $media.muted ? 1 : 0});}}}
class StreamUiShortcut {static showHideStreamMenu() {window.BX_EXPOSED.showStreamMenu && window.BX_EXPOSED.showStreamMenu();}}
class StreamStatsCollector {static instance;static getInstance = () => StreamStatsCollector.instance ?? (StreamStatsCollector.instance = new StreamStatsCollector);LOG_TAG = "StreamStatsCollector";static INTERVAL_BACKGROUND = 60000;calculateGrade(value, grades) {return value > grades[2] ? "bad" : value > grades[1] ? "ok" : value > grades[0] ? "good" : "";}currentStats = {ping: {current: -1,grades: [40, 75, 100],toString() {return this.current === -1 ? "???" : this.current.toString().padStart(3);}},jit: {current: 0,grades: [30, 40, 60],toString() {return `${this.current.toFixed(1)}ms`.padStart(6);}},fps: {current: 0,toString() {let maxFps = getStreamPref("video.maxFps");return maxFps < 60 ? `${maxFps}/${this.current}`.padStart(5) : this.current.toString();}},btr: {current: 0,toString() {return `${this.current.toFixed(1)} Mbps`.padStart(9);}},fl: {received: 0,dropped: 0,toString() {let percentage = (this.dropped * 100 / (this.dropped + this.received || 1)).toFixed(1);return percentage.startsWith("0.") ? this.dropped.toString() : `${this.dropped} (${percentage}%)`;}},pl: {received: 0,dropped: 0,toString() {let percentage = (this.dropped * 100 / (this.dropped + this.received || 1)).toFixed(1);return percentage.startsWith("0.") ? this.dropped.toString() : `${this.dropped} (${percentage}%)`;}},dt: {current: 0,total: 0,grades: [6, 9, 12],toString() {return isNaN(this.current) ? "??ms" : `${this.current.toFixed(1)}ms`.padStart(6);}},dl: {total: 0,toString() {return humanFileSize(this.total).padStart(8);}},ul: {total: 0,toString() {return humanFileSize(this.total);}},play: {seconds: 0,startTime: 0,toString() {return secondsToHm(this.seconds);}},batt: {current: 100,start: 100,isCharging: !1,toString() {let text = `${this.current}%`;if (this.current !== this.start) {let diffLevel = Math.round(this.current - this.start), sign = diffLevel > 0 ? "+" : "";text += ` (${sign}${diffLevel}%)`;}return text;}},time: {toString() {return (new Date()).toLocaleTimeString([], {hour: "2-digit",minute: "2-digit",hour12: !1});}}};lastVideoStat;selectedCandidatePairId = null;constructor() {BxLogger.info(this.LOG_TAG, "constructor()");}async collect() {let stats = await STATES.currentStream.peerConnection?.getStats();if (!stats) return;if (!this.selectedCandidatePairId) {let found = !1;stats.forEach((stat) => {if (found || stat.type !== "transport") return;if (stat = stat, stat.iceState === "connected" && stat.selectedCandidatePairId) this.selectedCandidatePairId = stat.selectedCandidatePairId, found = !0;});}stats.forEach((stat) => {if (stat.type === "inbound-rtp" && stat.kind === "video") {let fps = this.currentStats["fps"];fps.current = stat.framesPerSecond || 0;let pl = this.currentStats["pl"];pl.dropped = Math.max(0, stat.packetsLost), pl.received = stat.packetsReceived;let fl = this.currentStats["fl"];if (fl.dropped = stat.framesDropped, fl.received = stat.framesReceived, !this.lastVideoStat) {this.lastVideoStat = stat;return;}let lastStat = this.lastVideoStat, jit = this.currentStats["jit"], bufferDelayDiff = stat.jitterBufferDelay - lastStat.jitterBufferDelay, emittedCountDiff = stat.jitterBufferEmittedCount - lastStat.jitterBufferEmittedCount;if (emittedCountDiff > 0) jit.current = bufferDelayDiff / emittedCountDiff * 1000;let btr = this.currentStats["btr"], timeDiff = stat.timestamp - lastStat.timestamp;btr.current = 8 * (stat.bytesReceived - lastStat.bytesReceived) / timeDiff / 1000;let dt = this.currentStats["dt"];dt.total = stat.totalDecodeTime - lastStat.totalDecodeTime;let framesDecodedDiff = stat.framesDecoded - lastStat.framesDecoded;dt.current = dt.total / framesDecodedDiff * 1000, this.lastVideoStat = stat;} else if (this.selectedCandidatePairId && stat.type === "candidate-pair" && stat.id === this.selectedCandidatePairId) {let ping = this.currentStats["ping"];ping.current = stat.currentRoundTripTime ? stat.currentRoundTripTime * 1000 : -1;let dl = this.currentStats["dl"];dl.total = stat.bytesReceived;let ul = this.currentStats["ul"];ul.total = stat.bytesSent;}});let batteryLevel = 100, isCharging = !1;if (STATES.browser.capabilities.batteryApi) try {let bm = await navigator.getBattery();isCharging = bm.charging, batteryLevel = Math.round(bm.level * 100);} catch (e) {}let battery = this.currentStats["batt"];battery.current = batteryLevel, battery.isCharging = isCharging;let playTime = this.currentStats["play"], now = +new Date;playTime.seconds = Math.ceil((now - playTime.startTime) / 1000);}getStat(kind) {return this.currentStats[kind];}reset() {let playTime = this.currentStats["play"];playTime.seconds = 0, playTime.startTime = +new Date;try {STATES.browser.capabilities.batteryApi && navigator.getBattery().then((bm) => {this.currentStats["batt"].start = Math.round(bm.level * 100);});} catch (e) {}}static setupEvents() {BxEventBus.Stream.on("state.playing", () => {StreamStatsCollector.getInstance().reset();});}}
class StreamStats {static instance;static getInstance = () => StreamStats.instance ?? (StreamStats.instance = new StreamStats);LOG_TAG = "StreamStats";intervalId;REFRESH_INTERVAL = 1000;stats = {time: {name: t("clock"),$element: CE("span")},play: {name: t("playtime"),$element: CE("span")},batt: {name: t("battery"),$element: CE("span")},ping: {name: t("stat-ping"),$element: CE("span")},jit: {name: t("jitter"),$element: CE("span")},fps: {name: t("stat-fps"),$element: CE("span")},btr: {name: t("stat-bitrate"),$element: CE("span")},dt: {name: t("stat-decode-time"),$element: CE("span")},pl: {name: t("stat-packets-lost"),$element: CE("span")},fl: {name: t("stat-frames-lost"),$element: CE("span")},dl: {name: t("downloaded"),$element: CE("span")},ul: {name: t("uploaded"),$element: CE("span")}};$container;boundOnStreamHudStateChanged;constructor() {BxLogger.info(this.LOG_TAG, "constructor()"), this.boundOnStreamHudStateChanged = this.onStreamHudStateChanged.bind(this), BxEventBus.Stream.on("ui.streamHud.rendered", this.boundOnStreamHudStateChanged), this.render();}async start(glancing = !1) {if (!this.isHidden() || glancing && this.isGlancing()) return;this.intervalId && clearInterval(this.intervalId), await this.update(!0), this.$container.classList.remove("bx-gone"), this.$container.dataset.display = glancing ? "glancing" : "fixed", this.intervalId = window.setInterval(this.update, this.REFRESH_INTERVAL);}async stop(glancing = !1) {if (glancing && !this.isGlancing()) return;this.intervalId && clearInterval(this.intervalId), this.intervalId = null, this.$container.removeAttribute("data-display"), this.$container.classList.add("bx-gone");}async toggle() {if (this.isGlancing()) this.$container && (this.$container.dataset.display = "fixed");else this.isHidden() ? await this.start() : await this.stop();}destroy() {this.stop(), this.hideSettingsUi();}isHidden = () => this.$container.classList.contains("bx-gone");isGlancing = () => this.$container.dataset.display === "glancing";onStreamHudStateChanged({ expanded }) {if (!getStreamPref("stats.quickGlance.enabled")) return;if (expanded) this.isHidden() && this.start(!0);else this.stop(!0);}update = async (forceUpdate = !1) => {if (!forceUpdate && this.isHidden() || !STATES.currentStream.peerConnection) {this.destroy();return;}let PREF_STATS_CONDITIONAL_FORMATTING = getStreamPref("stats.colors"), grade = "", statsCollector = StreamStatsCollector.getInstance();await statsCollector.collect();let statKey;for (statKey in this.stats) {grade = "";let stat = this.stats[statKey], value = statsCollector.getStat(statKey), $element = stat.$element;if ($element.textContent = value.toString(), PREF_STATS_CONDITIONAL_FORMATTING && "grades" in value) grade = statsCollector.calculateGrade(value.current, value.grades);if ($element.dataset.grade !== grade) $element.dataset.grade = grade;}};refreshStyles() {let PREF_ITEMS = getStreamPref("stats.items"), PREF_OPACITY_BG = getStreamPref("stats.opacity.background"), $container = this.$container;if ($container.dataset.stats = "[" + PREF_ITEMS.join("][") + "]", $container.dataset.position = getStreamPref("stats.position"), PREF_OPACITY_BG === 0) $container.style.removeProperty("background-color"), $container.dataset.shadow = "true";else delete $container.dataset.shadow, $container.style.backgroundColor = `rgba(0, 0, 0, ${PREF_OPACITY_BG}%)`;$container.style.opacity = getStreamPref("stats.opacity.all") + "%", $container.style.fontSize = getStreamPref("stats.textSize");}hideSettingsUi() {if (this.isGlancing() && !getStreamPref("stats.quickGlance.enabled")) this.stop();}async render() {this.$container = CE("div", { class: "bx-stats-bar bx-gone" });let statKey;for (statKey in this.stats) {let stat = this.stats[statKey], $div = CE("div", {class: `bx-stat-${statKey}`,title: stat.name}, CE("label", !1, statKey.toUpperCase()), stat.$element);this.$container.appendChild($div);}this.refreshStyles(), document.documentElement.appendChild(this.$container);}static setupEvents() {BxEventBus.Stream.on("state.playing", () => {let PREF_STATS_QUICK_GLANCE = getStreamPref("stats.quickGlance.enabled"), PREF_STATS_SHOW_WHEN_PLAYING = getStreamPref("stats.showWhenPlaying"), streamStats = StreamStats.getInstance();if (PREF_STATS_SHOW_WHEN_PLAYING) streamStats.start();else if (PREF_STATS_QUICK_GLANCE) !PREF_STATS_SHOW_WHEN_PLAYING && streamStats.start(!0);});}static refreshStyles() {StreamStats.getInstance().refreshStyles();}}
class StreamStats {static instance;static getInstance = () => StreamStats.instance ?? (StreamStats.instance = new StreamStats);LOG_TAG = "StreamStats";isRunning = !1;intervalId;REFRESH_INTERVAL = 1000;stats = {time: {name: t("clock"),$element: CE("span")},play: {name: t("playtime"),$element: CE("span")},batt: {name: t("battery"),$element: CE("span")},ping: {name: t("stat-ping"),$element: CE("span")},jit: {name: t("jitter"),$element: CE("span")},fps: {name: t("stat-fps"),$element: CE("span")},btr: {name: t("stat-bitrate"),$element: CE("span")},dt: {name: t("stat-decode-time"),$element: CE("span")},pl: {name: t("stat-packets-lost"),$element: CE("span")},fl: {name: t("stat-frames-lost"),$element: CE("span")},dl: {name: t("downloaded"),$element: CE("span")},ul: {name: t("uploaded"),$element: CE("span")}};$container;boundOnStreamHudStateChanged;constructor() {BxLogger.info(this.LOG_TAG, "constructor()"), this.boundOnStreamHudStateChanged = this.onStreamHudStateChanged.bind(this), BxEventBus.Stream.on("ui.streamHud.rendered", this.boundOnStreamHudStateChanged), this.render();}async start(glancing = !1) {if (this.isRunning || !this.isHidden() || glancing && this.isGlancing()) return;this.isRunning = !0, this.intervalId && clearInterval(this.intervalId), await this.update(!0), this.$container.classList.remove("bx-gone"), this.$container.dataset.display = glancing ? "glancing" : "fixed", this.intervalId = window.setInterval(this.update, this.REFRESH_INTERVAL);}async stop(glancing = !1) {if (glancing && !this.isGlancing()) return;this.isRunning = !1, this.intervalId && clearInterval(this.intervalId), this.intervalId = null, this.$container.removeAttribute("data-display"), this.$container.classList.add("bx-gone");}async toggle() {if (this.isGlancing()) this.$container && (this.$container.dataset.display = "fixed");else this.isHidden() ? await this.start() : await this.stop();}destroy() {this.stop(), this.hideSettingsUi();}isHidden = () => this.$container.classList.contains("bx-gone");isGlancing = () => this.$container.dataset.display === "glancing";onStreamHudStateChanged({ expanded }) {if (!getStreamPref("stats.quickGlance.enabled")) return;if (expanded) this.isHidden() && this.start(!0);else this.stop(!0);}update = async (forceUpdate = !1) => {if (!forceUpdate && this.isHidden() || !STATES.currentStream.peerConnection) {this.destroy();return;}let PREF_STATS_CONDITIONAL_FORMATTING = getStreamPref("stats.colors"), grade = "", statsCollector = StreamStatsCollector.getInstance();await statsCollector.collect();let statKey;for (statKey in this.stats) {grade = "";let stat = this.stats[statKey], value = statsCollector.getStat(statKey), $element = stat.$element;if ($element.textContent = value.toString(), PREF_STATS_CONDITIONAL_FORMATTING && "grades" in value) grade = statsCollector.calculateGrade(value.current, value.grades);if ($element.dataset.grade !== grade) $element.dataset.grade = grade;}};refreshStyles() {let PREF_ITEMS = getStreamPref("stats.items"), PREF_OPACITY_BG = getStreamPref("stats.opacity.background"), $container = this.$container;if ($container.dataset.stats = "[" + PREF_ITEMS.join("][") + "]", $container.dataset.position = getStreamPref("stats.position"), PREF_OPACITY_BG === 0) $container.style.removeProperty("background-color"), $container.dataset.shadow = "true";else delete $container.dataset.shadow, $container.style.backgroundColor = `rgba(0, 0, 0, ${PREF_OPACITY_BG}%)`;$container.style.opacity = getStreamPref("stats.opacity.all") + "%", $container.style.fontSize = getStreamPref("stats.textSize");}hideSettingsUi() {if (this.isGlancing() && !getStreamPref("stats.quickGlance.enabled")) this.stop();}async render() {this.$container = CE("div", { class: "bx-stats-bar bx-gone" });let statKey;for (statKey in this.stats) {let stat = this.stats[statKey], $div = CE("div", {class: `bx-stat-${statKey}`,title: stat.name}, CE("label", !1, statKey.toUpperCase()), stat.$element);this.$container.appendChild($div);}this.refreshStyles(), document.documentElement.appendChild(this.$container);}static setupEvents() {BxEventBus.Stream.on("state.playing", () => {let PREF_STATS_QUICK_GLANCE = getStreamPref("stats.quickGlance.enabled"), PREF_STATS_SHOW_WHEN_PLAYING = getStreamPref("stats.showWhenPlaying"), streamStats = StreamStats.getInstance();if (PREF_STATS_SHOW_WHEN_PLAYING) streamStats.start();else if (PREF_STATS_QUICK_GLANCE) !PREF_STATS_SHOW_WHEN_PLAYING && streamStats.start(!0);});}static refreshStyles() {StreamStats.getInstance().refreshStyles();}}
class KeyHelper {static NON_PRINTABLE_KEYS = {Backquote: "`",Minus: "-",Equal: "=",BracketLeft: "[",BracketRight: "]",Backslash: "\\",Semicolon: ";",Quote: "'",Comma: ",",Period: ".",Slash: "/",NumpadMultiply: "Numpad *",NumpadAdd: "Numpad +",NumpadSubtract: "Numpad -",NumpadDecimal: "Numpad .",NumpadDivide: "Numpad /",NumpadEqual: "Numpad =",Mouse0: "Left Click",Mouse2: "Right Click",Mouse1: "Middle Click",ScrollUp: "Scroll Up",ScrollDown: "Scroll Down",ScrollLeft: "Scroll Left",ScrollRight: "Scroll Right"};static getKeyFromEvent(e) {let code = null, modifiers;if (e instanceof KeyboardEvent) code = e.code || e.key, modifiers = 0, modifiers ^= e.ctrlKey ? 1 : 0, modifiers ^= e.shiftKey ? 2 : 0, modifiers ^= e.altKey ? 4 : 0;else if (e instanceof WheelEvent) {if (e.deltaY < 0) code = "ScrollUp";else if (e.deltaY > 0) code = "ScrollDown";else if (e.deltaX < 0) code = "ScrollLeft";else if (e.deltaX > 0) code = "ScrollRight";} else if (e instanceof MouseEvent) code = "Mouse" + e.button;if (code) {let results = { code };if (modifiers) results.modifiers = modifiers;return results;}return null;}static getFullKeyCodeFromEvent(e) {let key = KeyHelper.getKeyFromEvent(e);return key ? `${key.code}:${key.modifiers || 0}` : "";}static parseFullKeyCode(str) {if (!str) return null;let tmp = str.split(":"), code = tmp[0], modifiers = parseInt(tmp[1]);return {code,modifiers};}static codeToKeyName(key) {let { code, modifiers } = key, text = [KeyHelper.NON_PRINTABLE_KEYS[code] || code.startsWith("Key") && code.substring(3) || code.startsWith("Digit") && code.substring(5) || code.startsWith("Numpad") && "Numpad " + code.substring(6) || code.startsWith("Arrow") && "Arrow " + code.substring(5) || code.endsWith("Lock") && code.replace("Lock", " Lock") || code.endsWith("Left") && "Left " + code.replace("Left", "") || code.endsWith("Right") && "Right " + code.replace("Right", "") || code];if (modifiers && modifiers !== 0) {if (!code.startsWith("Control") && !code.startsWith("Shift") && !code.startsWith("Alt")) {if (modifiers & 2) text.unshift("Shift");if (modifiers & 4) text.unshift("Alt");if (modifiers & 1) text.unshift("Ctrl");}}return text.join(" + ");}}
class PointerClient {static instance;static getInstance = () => PointerClient.instance ?? (PointerClient.instance = new PointerClient);LOG_TAG = "PointerClient";REQUIRED_PROTOCOL_VERSION = 2;socket;mkbHandler;constructor() {BxLogger.info(this.LOG_TAG, "constructor()");}start(port, mkbHandler) {if (!port) throw new Error("PointerServer port is 0");this.mkbHandler = mkbHandler, this.socket = new WebSocket(`ws://localhost:${port}`), this.socket.binaryType = "arraybuffer", this.socket.addEventListener("open", (event) => {BxLogger.info(this.LOG_TAG, "connected");}), this.socket.addEventListener("error", (event) => {BxLogger.error(this.LOG_TAG, event), Toast.show("Cannot setup mouse: " + event);}), this.socket.addEventListener("close", (event) => {this.socket = null;}), this.socket.addEventListener("message", (event) => {let dataView = new DataView(event.data), messageType = dataView.getInt8(0), offset = Int8Array.BYTES_PER_ELEMENT;switch (messageType) {case 127:let protocolVersion = this.onProtocolVersion(dataView, offset);if (BxLogger.info(this.LOG_TAG, "Protocol version", protocolVersion), protocolVersion !== this.REQUIRED_PROTOCOL_VERSION) alert("Required MKB protocol: " + protocolVersion), this.stop();break;case 1:this.onMove(dataView, offset);break;case 2:case 3:this.onPress(messageType, dataView, offset);break;case 4:this.onScroll(dataView, offset);break;case 5:this.onPointerCaptureChanged(dataView, offset);}});}onProtocolVersion(dataView, offset) {return dataView.getUint16(offset);}onMove(dataView, offset) {let x = dataView.getInt16(offset);offset += Int16Array.BYTES_PER_ELEMENT;let y = dataView.getInt16(offset);this.mkbHandler?.handleMouseMove({movementX: x,movementY: y});}onPress(messageType, dataView, offset) {let button = dataView.getUint8(offset);this.mkbHandler?.handleMouseClick({pointerButton: button,pressed: messageType === 2});}onScroll(dataView, offset) {let vScroll = dataView.getInt16(offset);offset += Int16Array.BYTES_PER_ELEMENT;let hScroll = dataView.getInt16(offset);this.mkbHandler?.handleMouseWheel({vertical: vScroll,horizontal: hScroll});}onPointerCaptureChanged(dataView, offset) {dataView.getInt8(offset) !== 1 && this.mkbHandler?.stop();}stop() {try {this.socket?.close();} catch (e) {}this.socket = null;}}
class MouseDataProvider {mkbHandler;constructor(handler) {this.mkbHandler = handler;}init() {}destroy() {}}

View File

@ -14,6 +14,7 @@ export class StreamStats {
public static getInstance = () => StreamStats.instance ?? (StreamStats.instance = new StreamStats());
private readonly LOG_TAG = 'StreamStats';
private isRunning = false;
private intervalId?: number | null;
private readonly REFRESH_INTERVAL = 1 * 1000;
@ -81,10 +82,11 @@ export class StreamStats {
}
async start(glancing=false) {
if (!this.isHidden() || (glancing && this.isGlancing())) {
if (this.isRunning || !this.isHidden() || (glancing && this.isGlancing())) {
return;
}
this.isRunning = true;
this.intervalId && clearInterval(this.intervalId);
await this.update(true);
@ -99,6 +101,7 @@ export class StreamStats {
return;
}
this.isRunning = false;
this.intervalId && clearInterval(this.intervalId);
this.intervalId = null;