mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-06 07:37:19 +02:00
Add jitter stat
This commit is contained in:
parent
411e43ceb0
commit
728abced45
38
dist/better-xcloud.lite.user.js
vendored
38
dist/better-xcloud.lite.user.js
vendored
File diff suppressed because one or more lines are too long
40
dist/better-xcloud.user.js
vendored
40
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
@ -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,
|
||||||
|
@ -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) {
|
||||||
|
@ -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')}`,
|
||||||
|
@ -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;
|
||||||
|
@ -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",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user