Add jitter stat

This commit is contained in:
redphx 2024-10-10 21:35:36 +07:00
parent 411e43ceb0
commit 728abced45
7 changed files with 102 additions and 42 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -91,6 +91,7 @@ div[class^=StreamMenu-module__container] .bx-badges {
&[data-stats*="[batt]"] > .bx-stat-batt, &[data-stats*="[batt]"] > .bx-stat-batt,
&[data-stats*="[fps]"] > .bx-stat-fps, &[data-stats*="[fps]"] > .bx-stat-fps,
&[data-stats*="[ping]"] > .bx-stat-ping, &[data-stats*="[ping]"] > .bx-stat-ping,
&[data-stats*="[jit]"] > .bx-stat-jit,
&[data-stats*="[btr]"] > .bx-stat-btr, &[data-stats*="[btr]"] > .bx-stat-btr,
&[data-stats*="[dt]"] > .bx-stat-dt, &[data-stats*="[dt]"] > .bx-stat-dt,
&[data-stats*="[pl]"] > .bx-stat-pl, &[data-stats*="[pl]"] > .bx-stat-pl,
@ -106,6 +107,7 @@ div[class^=StreamMenu-module__container] .bx-badges {
&[data-stats$="[batt]"] > .bx-stat-batt, &[data-stats$="[batt]"] > .bx-stat-batt,
&[data-stats$="[fps]"] > .bx-stat-fps, &[data-stats$="[fps]"] > .bx-stat-fps,
&[data-stats$="[ping]"] > .bx-stat-ping, &[data-stats$="[ping]"] > .bx-stat-ping,
&[data-stats$="[jit]"] > .bx-stat-jit,
&[data-stats$="[btr]"] > .bx-stat-btr, &[data-stats$="[btr]"] > .bx-stat-btr,
&[data-stats$="[dt]"] > .bx-stat-dt, &[data-stats$="[dt]"] > .bx-stat-dt,
&[data-stats$="[pl]"] > .bx-stat-pl, &[data-stats$="[pl]"] > .bx-stat-pl,

View File

@ -37,6 +37,10 @@ export class StreamStats {
name: t('stat-ping'), name: t('stat-ping'),
$element: CE('span'), $element: CE('span'),
}, },
[StreamStat.JITTER]: {
name: t('jitter'),
$element: CE('span'),
},
[StreamStat.FPS]: { [StreamStat.FPS]: {
name: t('stat-fps'), name: t('stat-fps'),
$element: CE('span'), $element: CE('span'),
@ -179,10 +183,8 @@ export class StreamStats {
$element.textContent = value.toString(); $element.textContent = value.toString();
// Get stat's grade // Get stat's grade
if (PREF_STATS_CONDITIONAL_FORMATTING) { if (PREF_STATS_CONDITIONAL_FORMATTING && 'grades' in value) {
if (statKey === StreamStat.PING || statKey === StreamStat.DECODE_TIME) { grade = statsCollector.calculateGrade(value.current, value.grades);
grade = (value as any).calculateGrade();
}
} }
if ($element.dataset.grade !== grade) { if ($element.dataset.grade !== grade) {

View File

@ -714,6 +714,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
[StreamStat.PLAYTIME]: `${StreamStat.PLAYTIME.toUpperCase()}: ${t('playtime')}`, [StreamStat.PLAYTIME]: `${StreamStat.PLAYTIME.toUpperCase()}: ${t('playtime')}`,
[StreamStat.BATTERY]: `${StreamStat.BATTERY.toUpperCase()}: ${t('battery')}`, [StreamStat.BATTERY]: `${StreamStat.BATTERY.toUpperCase()}: ${t('battery')}`,
[StreamStat.PING]: `${StreamStat.PING.toUpperCase()}: ${t('stat-ping')}`, [StreamStat.PING]: `${StreamStat.PING.toUpperCase()}: ${t('stat-ping')}`,
[StreamStat.JITTER]: `${StreamStat.JITTER.toUpperCase()}: ${t('jitter')}`,
[StreamStat.FPS]: `${StreamStat.FPS.toUpperCase()}: ${t('stat-fps')}`, [StreamStat.FPS]: `${StreamStat.FPS.toUpperCase()}: ${t('stat-fps')}`,
[StreamStat.BITRATE]: `${StreamStat.BITRATE.toUpperCase()}: ${t('stat-bitrate')}`, [StreamStat.BITRATE]: `${StreamStat.BITRATE.toUpperCase()}: ${t('stat-bitrate')}`,
[StreamStat.DECODE_TIME]: `${StreamStat.DECODE_TIME.toUpperCase()}: ${t('stat-decode-time')}`, [StreamStat.DECODE_TIME]: `${StreamStat.DECODE_TIME.toUpperCase()}: ${t('stat-decode-time')}`,

View File

@ -4,6 +4,7 @@ import { humanFileSize, secondsToHm } from "./html";
export enum StreamStat { export enum StreamStat {
PING = 'ping', PING = 'ping',
JITTER = 'jit',
FPS = 'fps', FPS = 'fps',
BITRATE = 'btr', BITRATE = 'btr',
DECODE_TIME = 'dt', DECODE_TIME = 'dt',
@ -21,7 +22,13 @@ export type StreamStatGrade = '' | 'bad' | 'ok' | 'good';
type CurrentStats = { type CurrentStats = {
[StreamStat.PING]: { [StreamStat.PING]: {
current: number; current: number;
calculateGrade: () => StreamStatGrade; grades: [number, number, number];
toString: () => string;
};
[StreamStat.JITTER]: {
current: number;
grades: [number, number, number];
toString: () => string; toString: () => string;
}; };
@ -50,7 +57,7 @@ type CurrentStats = {
[StreamStat.DECODE_TIME]: { [StreamStat.DECODE_TIME]: {
current: number; current: number;
total: number; total: number;
calculateGrade: () => StreamStatGrade; grades: [number, number, number];
toString: () => string; toString: () => string;
}; };
@ -96,17 +103,27 @@ export class StreamStatsCollector {
// Collect in background - 60 seconds // Collect in background - 60 seconds
static readonly INTERVAL_BACKGROUND = 60 * 1000; static readonly INTERVAL_BACKGROUND = 60 * 1000;
public calculateGrade(value: number, grades: [number, number, number]): StreamStatGrade {
return (value > grades[2]) ? 'bad' : (value > grades[1]) ? 'ok' : (value > grades[0]) ? 'good' : '';
}
private currentStats: CurrentStats = { private currentStats: CurrentStats = {
[StreamStat.PING]: { [StreamStat.PING]: {
current: -1, current: -1,
calculateGrade() { grades: [40, 75, 100],
return (this.current >= 100) ? 'bad' : (this.current > 75) ? 'ok' : (this.current > 40) ? 'good' : '';
},
toString() { toString() {
return this.current === -1 ? '???' : this.current.toString(); return this.current === -1 ? '???' : this.current.toString();
}, },
}, },
[StreamStat.JITTER]: {
current: 0,
grades: [30, 40, 60],
toString() {
return `${this.current.toFixed(2)}ms`;
},
},
[StreamStat.FPS]: { [StreamStat.FPS]: {
current: 0, current: 0,
toString() { toString() {
@ -142,9 +159,7 @@ export class StreamStatsCollector {
[StreamStat.DECODE_TIME]: { [StreamStat.DECODE_TIME]: {
current: 0, current: 0,
total: 0, total: 0,
calculateGrade() { grades: [6, 9, 12],
return (this.current > 12) ? 'bad' : (this.current > 9) ? 'ok' : (this.current > 6) ? 'good' : '';
},
toString() { toString() {
return isNaN(this.current) ? '??ms' : `${this.current.toFixed(2)}ms`; return isNaN(this.current) ? '??ms' : `${this.current.toFixed(2)}ms`;
}, },
@ -200,12 +215,15 @@ export class StreamStatsCollector {
}, },
}; };
private lastVideoStat?: RTCBasicStat | null; private lastVideoStat?: RTCInboundRtpStreamStats | null;
async collect() { async collect() {
const stats = await STATES.currentStream.peerConnection?.getStats(); const stats = await STATES.currentStream.peerConnection?.getStats();
if (!stats) {
return;
}
stats?.forEach(stat => { stats.forEach(stat => {
if (stat.type === 'inbound-rtp' && stat.kind === 'video') { if (stat.type === 'inbound-rtp' && stat.kind === 'video') {
// FPS // FPS
const fps = this.currentStats[StreamStat.FPS]; const fps = this.currentStats[StreamStat.FPS];
@ -229,15 +247,23 @@ export class StreamStatsCollector {
const lastStat = this.lastVideoStat; const lastStat = this.lastVideoStat;
// Jitter
const jit = this.currentStats[StreamStat.JITTER];
const bufferDelayDiff = (stat as RTCInboundRtpStreamStats).jitterBufferDelay! - lastStat.jitterBufferDelay!;
const emittedCountDiff = (stat as RTCInboundRtpStreamStats).jitterBufferEmittedCount! - lastStat.jitterBufferEmittedCount!;
if (emittedCountDiff > 0) {
jit.current = bufferDelayDiff / emittedCountDiff * 1000;
}
// Bitrate // Bitrate
const btr = this.currentStats[StreamStat.BITRATE]; const btr = this.currentStats[StreamStat.BITRATE];
const timeDiff = stat.timestamp - lastStat.timestamp; const timeDiff = stat.timestamp - lastStat.timestamp;
btr.current = 8 * (stat.bytesReceived - lastStat.bytesReceived) / timeDiff / 1000; btr.current = 8 * (stat.bytesReceived - lastStat.bytesReceived!) / timeDiff / 1000;
// Decode time // Decode time
const dt = this.currentStats[StreamStat.DECODE_TIME]; const dt = this.currentStats[StreamStat.DECODE_TIME];
dt.total = stat.totalDecodeTime - lastStat.totalDecodeTime; dt.total = stat.totalDecodeTime - lastStat.totalDecodeTime!;
const framesDecodedDiff = stat.framesDecoded - lastStat.framesDecoded; const framesDecodedDiff = stat.framesDecoded - lastStat.framesDecoded!;
dt.current = dt.total / framesDecodedDiff * 1000; dt.current = dt.total / framesDecodedDiff * 1000;
this.lastVideoStat = stat; this.lastVideoStat = stat;

View File

@ -131,6 +131,7 @@ const Texts = {
"increase": "Increase", "increase": "Increase",
"install-android": "Better xCloud app for Android", "install-android": "Better xCloud app for Android",
"japan": "Japan", "japan": "Japan",
"jitter": "Jitter",
"keyboard-shortcuts": "Keyboard shortcuts", "keyboard-shortcuts": "Keyboard shortcuts",
"korea": "Korea", "korea": "Korea",
"language": "Language", "language": "Language",