@livepeer-frameworks/player-wc 0.1.2 → 0.1.4
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/cjs/components/fw-dev-mode-panel.js +845 -212
- package/dist/cjs/components/fw-dev-mode-panel.js.map +1 -1
- package/dist/cjs/components/fw-dvd-logo.js +211 -0
- package/dist/cjs/components/fw-dvd-logo.js.map +1 -0
- package/dist/cjs/components/fw-idle-screen.js +641 -97
- package/dist/cjs/components/fw-idle-screen.js.map +1 -1
- package/dist/cjs/components/fw-loading-screen.js +513 -0
- package/dist/cjs/components/fw-loading-screen.js.map +1 -0
- package/dist/cjs/components/fw-player-controls.js +390 -173
- package/dist/cjs/components/fw-player-controls.js.map +1 -1
- package/dist/cjs/components/fw-player.js +506 -63
- package/dist/cjs/components/fw-player.js.map +1 -1
- package/dist/cjs/components/fw-seek-bar.js +292 -142
- package/dist/cjs/components/fw-seek-bar.js.map +1 -1
- package/dist/cjs/components/fw-settings-menu.js +208 -81
- package/dist/cjs/components/fw-settings-menu.js.map +1 -1
- package/dist/cjs/components/fw-stats-panel.js +134 -70
- package/dist/cjs/components/fw-stats-panel.js.map +1 -1
- package/dist/cjs/components/fw-stream-state-overlay.js +338 -0
- package/dist/cjs/components/fw-stream-state-overlay.js.map +1 -0
- package/dist/cjs/components/fw-subtitle-renderer.js +174 -27
- package/dist/cjs/components/fw-subtitle-renderer.js.map +1 -1
- package/dist/cjs/components/fw-thumbnail-overlay.js +161 -0
- package/dist/cjs/components/fw-thumbnail-overlay.js.map +1 -0
- package/dist/cjs/components/fw-volume-control.js +150 -69
- package/dist/cjs/components/fw-volume-control.js.map +1 -1
- package/dist/cjs/components/shared/hitmarker-audio.js +76 -0
- package/dist/cjs/components/shared/hitmarker-audio.js.map +1 -0
- package/dist/cjs/constants/media-assets.js +11 -0
- package/dist/cjs/constants/media-assets.js.map +1 -0
- package/dist/cjs/controllers/player-controller-host.js +51 -2
- package/dist/cjs/controllers/player-controller-host.js.map +1 -1
- package/dist/cjs/define.js +8 -0
- package/dist/cjs/define.js.map +1 -1
- package/dist/cjs/icons/index.js +27 -0
- package/dist/cjs/icons/index.js.map +1 -1
- package/dist/cjs/index.js +20 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/components/fw-dev-mode-panel.js +846 -213
- package/dist/esm/components/fw-dev-mode-panel.js.map +1 -1
- package/dist/esm/components/fw-dvd-logo.js +211 -0
- package/dist/esm/components/fw-dvd-logo.js.map +1 -0
- package/dist/esm/components/fw-idle-screen.js +643 -99
- package/dist/esm/components/fw-idle-screen.js.map +1 -1
- package/dist/esm/components/fw-loading-screen.js +513 -0
- package/dist/esm/components/fw-loading-screen.js.map +1 -0
- package/dist/esm/components/fw-player-controls.js +391 -174
- package/dist/esm/components/fw-player-controls.js.map +1 -1
- package/dist/esm/components/fw-player.js +506 -63
- package/dist/esm/components/fw-player.js.map +1 -1
- package/dist/esm/components/fw-seek-bar.js +293 -143
- package/dist/esm/components/fw-seek-bar.js.map +1 -1
- package/dist/esm/components/fw-settings-menu.js +209 -82
- package/dist/esm/components/fw-settings-menu.js.map +1 -1
- package/dist/esm/components/fw-stats-panel.js +135 -71
- package/dist/esm/components/fw-stats-panel.js.map +1 -1
- package/dist/esm/components/fw-stream-state-overlay.js +338 -0
- package/dist/esm/components/fw-stream-state-overlay.js.map +1 -0
- package/dist/esm/components/fw-subtitle-renderer.js +175 -28
- package/dist/esm/components/fw-subtitle-renderer.js.map +1 -1
- package/dist/esm/components/fw-thumbnail-overlay.js +161 -0
- package/dist/esm/components/fw-thumbnail-overlay.js.map +1 -0
- package/dist/esm/components/fw-volume-control.js +150 -69
- package/dist/esm/components/fw-volume-control.js.map +1 -1
- package/dist/esm/components/shared/hitmarker-audio.js +74 -0
- package/dist/esm/components/shared/hitmarker-audio.js.map +1 -0
- package/dist/esm/constants/media-assets.js +8 -0
- package/dist/esm/constants/media-assets.js.map +1 -0
- package/dist/esm/controllers/player-controller-host.js +51 -2
- package/dist/esm/controllers/player-controller-host.js.map +1 -1
- package/dist/esm/define.js +8 -0
- package/dist/esm/define.js.map +1 -1
- package/dist/esm/icons/index.js +26 -2
- package/dist/esm/icons/index.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/fw-player.iife.js +2097 -883
- package/dist/types/components/fw-dev-mode-panel.d.ts +36 -9
- package/dist/types/components/fw-dvd-logo.d.ts +29 -0
- package/dist/types/components/fw-idle-screen.d.ts +36 -0
- package/dist/types/components/fw-loading-screen.d.ts +36 -0
- package/dist/types/components/fw-player-controls.d.ts +23 -6
- package/dist/types/components/fw-player.d.ts +32 -1
- package/dist/types/components/fw-seek-bar.d.ts +31 -14
- package/dist/types/components/fw-settings-menu.d.ts +16 -1
- package/dist/types/components/fw-stats-panel.d.ts +4 -4
- package/dist/types/components/fw-stream-state-overlay.d.ts +20 -0
- package/dist/types/components/fw-subtitle-renderer.d.ts +33 -2
- package/dist/types/components/fw-thumbnail-overlay.d.ts +17 -0
- package/dist/types/components/fw-volume-control.d.ts +11 -4
- package/dist/types/components/shared/hitmarker-audio.d.ts +1 -0
- package/dist/types/constants/media-assets.d.ts +5 -0
- package/dist/types/controllers/player-controller-host.d.ts +14 -1
- package/dist/types/iife-entry.d.ts +4 -0
- package/dist/types/index.d.ts +4 -0
- package/package.json +2 -2
- package/src/components/fw-dev-mode-panel.ts +929 -228
- package/src/components/fw-dvd-logo.ts +233 -0
- package/src/components/fw-idle-screen.ts +680 -100
- package/src/components/fw-loading-screen.ts +540 -0
- package/src/components/fw-player-controls.ts +475 -175
- package/src/components/fw-player.ts +551 -60
- package/src/components/fw-seek-bar.ts +336 -143
- package/src/components/fw-settings-menu.ts +248 -85
- package/src/components/fw-stats-panel.ts +150 -77
- package/src/components/fw-stream-state-overlay.ts +331 -0
- package/src/components/fw-subtitle-renderer.ts +216 -28
- package/src/components/fw-thumbnail-overlay.ts +148 -0
- package/src/components/fw-volume-control.ts +166 -66
- package/src/components/shared/hitmarker-audio.ts +92 -0
- package/src/constants/media-assets.ts +7 -0
- package/src/controllers/player-controller-host.ts +52 -3
- package/src/define.ts +8 -0
- package/src/iife-entry.ts +4 -0
- package/src/index.ts +4 -0
- package/dist/fw-player.iife.js.map +0 -1
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* <fw-dev-mode-panel> — Developer mode panel
|
|
3
|
-
*
|
|
2
|
+
* <fw-dev-mode-panel> — Developer mode side panel.
|
|
3
|
+
* Feature parity with React/Svelte advanced panel.
|
|
4
4
|
*/
|
|
5
|
-
import { LitElement, html, css, nothing } from "lit";
|
|
5
|
+
import { LitElement, html, css, nothing, type PropertyValues } from "lit";
|
|
6
6
|
import { customElement, property, state } from "lit/decorators.js";
|
|
7
7
|
import { classMap } from "lit/directives/class-map.js";
|
|
8
8
|
import { sharedStyles } from "../styles/shared-styles.js";
|
|
9
9
|
import { utilityStyles } from "../styles/utility-styles.js";
|
|
10
10
|
import { closeIcon } from "../icons/index.js";
|
|
11
|
+
import {
|
|
12
|
+
QualityMonitor,
|
|
13
|
+
globalPlayerManager,
|
|
14
|
+
type MistStreamInfo,
|
|
15
|
+
type PlaybackMode,
|
|
16
|
+
type PlayerCombination,
|
|
17
|
+
type StreamInfo,
|
|
18
|
+
} from "@livepeer-frameworks/player-core";
|
|
11
19
|
import type { PlayerControllerHost } from "../controllers/player-controller-host.js";
|
|
12
|
-
|
|
20
|
+
|
|
21
|
+
const SOURCE_TYPE_LABELS: Record<string, string> = {
|
|
22
|
+
"html5/application/vnd.apple.mpegurl": "HLS",
|
|
23
|
+
"dash/video/mp4": "DASH",
|
|
24
|
+
"html5/video/mp4": "MP4",
|
|
25
|
+
"html5/video/webm": "WebM",
|
|
26
|
+
whep: "WHEP",
|
|
27
|
+
"mist/html": "Mist",
|
|
28
|
+
"mist/legacy": "Auto",
|
|
29
|
+
"ws/video/mp4": "MEWS",
|
|
30
|
+
};
|
|
13
31
|
|
|
14
32
|
@customElement("fw-dev-mode-panel")
|
|
15
33
|
export class FwDevModePanel extends LitElement {
|
|
@@ -17,6 +35,32 @@ export class FwDevModePanel extends LitElement {
|
|
|
17
35
|
@property({ type: String }) playbackMode: PlaybackMode = "auto";
|
|
18
36
|
|
|
19
37
|
@state() private _activeTab: "config" | "stats" = "config";
|
|
38
|
+
@state() private _hoveredComboIndex: number | null = null;
|
|
39
|
+
@state() private _tooltipAbove = false;
|
|
40
|
+
@state() private _showDisabledPlayers = false;
|
|
41
|
+
|
|
42
|
+
@state() private _playbackScore = 1;
|
|
43
|
+
@state() private _qualityScore = 100;
|
|
44
|
+
@state() private _stallCount = 0;
|
|
45
|
+
@state() private _frameDropRate = 0;
|
|
46
|
+
|
|
47
|
+
@state()
|
|
48
|
+
private _videoStats: {
|
|
49
|
+
resolution: string;
|
|
50
|
+
buffered: string;
|
|
51
|
+
playbackRate: string;
|
|
52
|
+
currentTime: string;
|
|
53
|
+
duration: string;
|
|
54
|
+
readyState: number;
|
|
55
|
+
networkState: number;
|
|
56
|
+
} | null = null;
|
|
57
|
+
|
|
58
|
+
@state() private _playerStats: unknown = null;
|
|
59
|
+
|
|
60
|
+
private _qualityMonitor: QualityMonitor | null = null;
|
|
61
|
+
private _qualityMonitorVideo: HTMLVideoElement | null = null;
|
|
62
|
+
private _videoStatsInterval: ReturnType<typeof setInterval> | null = null;
|
|
63
|
+
private _playerStatsInterval: ReturnType<typeof setInterval> | null = null;
|
|
20
64
|
|
|
21
65
|
static styles = [
|
|
22
66
|
sharedStyles,
|
|
@@ -24,258 +68,915 @@ export class FwDevModePanel extends LitElement {
|
|
|
24
68
|
css`
|
|
25
69
|
:host {
|
|
26
70
|
display: block;
|
|
27
|
-
}
|
|
28
|
-
.panel {
|
|
29
|
-
width: 320px;
|
|
30
71
|
height: 100%;
|
|
31
|
-
border-left: 1px solid rgb(255 255 255 / 0.1);
|
|
32
|
-
background: rgb(15 23 42);
|
|
33
|
-
overflow: auto;
|
|
34
|
-
font-size: 0.75rem;
|
|
35
|
-
color: rgb(255 255 255 / 0.7);
|
|
36
|
-
}
|
|
37
|
-
.header {
|
|
38
|
-
display: flex;
|
|
39
|
-
align-items: center;
|
|
40
|
-
justify-content: space-between;
|
|
41
|
-
padding: 0.5rem 0.75rem;
|
|
42
|
-
border-bottom: 1px solid rgb(255 255 255 / 0.1);
|
|
43
|
-
}
|
|
44
|
-
.tabs {
|
|
45
|
-
display: flex;
|
|
46
|
-
gap: 0.5rem;
|
|
47
|
-
}
|
|
48
|
-
.tab {
|
|
49
|
-
padding: 0.25rem 0.5rem;
|
|
50
|
-
border: none;
|
|
51
|
-
background: none;
|
|
52
|
-
color: rgb(255 255 255 / 0.5);
|
|
53
|
-
font-size: 0.6875rem;
|
|
54
|
-
font-weight: 600;
|
|
55
|
-
cursor: pointer;
|
|
56
|
-
border-radius: 0.25rem;
|
|
57
|
-
}
|
|
58
|
-
.tab--active {
|
|
59
|
-
color: white;
|
|
60
|
-
background: rgb(255 255 255 / 0.1);
|
|
61
|
-
}
|
|
62
|
-
.close {
|
|
63
|
-
display: flex;
|
|
64
|
-
background: none;
|
|
65
|
-
border: none;
|
|
66
|
-
color: rgb(255 255 255 / 0.5);
|
|
67
|
-
cursor: pointer;
|
|
68
|
-
padding: 0;
|
|
69
|
-
}
|
|
70
|
-
.close:hover {
|
|
71
|
-
color: white;
|
|
72
|
-
}
|
|
73
|
-
.body {
|
|
74
|
-
padding: 0.75rem;
|
|
75
|
-
}
|
|
76
|
-
.section {
|
|
77
|
-
margin-bottom: 0.75rem;
|
|
78
|
-
}
|
|
79
|
-
.label {
|
|
80
|
-
font-size: 0.625rem;
|
|
81
|
-
font-weight: 600;
|
|
82
|
-
text-transform: uppercase;
|
|
83
|
-
letter-spacing: 0.05em;
|
|
84
|
-
color: rgb(255 255 255 / 0.4);
|
|
85
|
-
margin-bottom: 0.375rem;
|
|
86
|
-
}
|
|
87
|
-
.value {
|
|
88
|
-
color: rgb(255 255 255 / 0.9);
|
|
89
|
-
font-family: ui-monospace, monospace;
|
|
90
|
-
}
|
|
91
|
-
.mode-group {
|
|
92
|
-
display: flex;
|
|
93
|
-
gap: 0.25rem;
|
|
94
|
-
flex-wrap: wrap;
|
|
95
|
-
}
|
|
96
|
-
.mode-btn {
|
|
97
|
-
padding: 0.25rem 0.5rem;
|
|
98
|
-
border: 1px solid rgb(255 255 255 / 0.15);
|
|
99
|
-
background: none;
|
|
100
|
-
color: rgb(255 255 255 / 0.6);
|
|
101
|
-
font-size: 0.6875rem;
|
|
102
|
-
cursor: pointer;
|
|
103
|
-
border-radius: 0.25rem;
|
|
104
|
-
transition: all 150ms;
|
|
105
|
-
}
|
|
106
|
-
.mode-btn:hover {
|
|
107
|
-
border-color: rgb(255 255 255 / 0.3);
|
|
108
|
-
color: white;
|
|
109
|
-
}
|
|
110
|
-
.mode-btn--active {
|
|
111
|
-
border-color: hsl(var(--tn-blue, 217 89% 61%));
|
|
112
|
-
color: hsl(var(--tn-blue, 217 89% 61%));
|
|
113
|
-
background: hsl(var(--tn-blue, 217 89% 61%) / 0.1);
|
|
114
|
-
}
|
|
115
|
-
.actions {
|
|
116
|
-
display: flex;
|
|
117
|
-
gap: 0.5rem;
|
|
118
|
-
margin-top: 0.5rem;
|
|
119
|
-
}
|
|
120
|
-
.action-btn {
|
|
121
|
-
padding: 0.375rem 0.75rem;
|
|
122
|
-
border: 1px solid rgb(255 255 255 / 0.15);
|
|
123
|
-
background: none;
|
|
124
|
-
color: rgb(255 255 255 / 0.7);
|
|
125
|
-
font-size: 0.6875rem;
|
|
126
|
-
cursor: pointer;
|
|
127
|
-
border-radius: 0.25rem;
|
|
128
|
-
}
|
|
129
|
-
.action-btn:hover {
|
|
130
|
-
border-color: rgb(255 255 255 / 0.3);
|
|
131
|
-
color: white;
|
|
132
|
-
}
|
|
133
|
-
.stat-row {
|
|
134
|
-
display: flex;
|
|
135
|
-
justify-content: space-between;
|
|
136
|
-
padding: 0.125rem 0;
|
|
137
|
-
}
|
|
138
|
-
.stat-label {
|
|
139
|
-
color: rgb(255 255 255 / 0.4);
|
|
140
|
-
}
|
|
141
|
-
.stat-value {
|
|
142
|
-
color: rgb(255 255 255 / 0.8);
|
|
143
|
-
font-family: ui-monospace, monospace;
|
|
144
|
-
font-variant-numeric: tabular-nums;
|
|
145
72
|
}
|
|
146
73
|
`,
|
|
147
74
|
];
|
|
148
75
|
|
|
149
|
-
|
|
76
|
+
disconnectedCallback(): void {
|
|
77
|
+
super.disconnectedCallback();
|
|
78
|
+
this._stopQualityMonitor();
|
|
79
|
+
this._stopStatsPolling();
|
|
80
|
+
}
|
|
150
81
|
|
|
151
|
-
protected
|
|
152
|
-
|
|
82
|
+
protected updated(_changed: PropertyValues<this>): void {
|
|
83
|
+
this._syncQualityMonitor();
|
|
84
|
+
this._syncStatsPolling();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private _getMistStreamInfo(): MistStreamInfo | undefined {
|
|
88
|
+
return this.pc.s.streamState?.streamInfo as MistStreamInfo | undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private _getAllCombinations(): PlayerCombination[] {
|
|
92
|
+
const streamInfo = this.pc.s.streamInfo as StreamInfo | null;
|
|
93
|
+
if (!streamInfo) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
return globalPlayerManager.getAllCombinations(streamInfo, this.playbackMode);
|
|
99
|
+
} catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private _getCompatibleCombinations(): PlayerCombination[] {
|
|
105
|
+
return this._getAllCombinations().filter((combo) => combo.compatible);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private _getActiveComboIndex(combinations: PlayerCombination[]): number {
|
|
109
|
+
const currentPlayer = this.pc.s.currentPlayerInfo;
|
|
110
|
+
const currentSource = this.pc.s.currentSourceInfo;
|
|
111
|
+
|
|
112
|
+
if (!currentPlayer || !currentSource || combinations.length === 0) {
|
|
113
|
+
return -1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return combinations.findIndex(
|
|
117
|
+
(combo) => combo.player === currentPlayer.shortname && combo.sourceType === currentSource.type
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private _syncQualityMonitor(): void {
|
|
122
|
+
const video = this.pc.s.videoElement;
|
|
123
|
+
|
|
124
|
+
if (!video) {
|
|
125
|
+
this._stopQualityMonitor();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!this._qualityMonitor) {
|
|
130
|
+
this._qualityMonitor = new QualityMonitor({
|
|
131
|
+
sampleInterval: 500,
|
|
132
|
+
onSample: (quality) => {
|
|
133
|
+
this._qualityScore = quality.score;
|
|
134
|
+
this._stallCount = quality.stallCount;
|
|
135
|
+
this._frameDropRate = quality.frameDropRate;
|
|
136
|
+
this._playbackScore = this._qualityMonitor?.getPlaybackScore() ?? 1;
|
|
137
|
+
this.requestUpdate();
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (this._qualityMonitorVideo !== video) {
|
|
143
|
+
this._qualityMonitor.stop();
|
|
144
|
+
this._qualityMonitor.start(video);
|
|
145
|
+
this._qualityMonitorVideo = video;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private _stopQualityMonitor(): void {
|
|
150
|
+
this._qualityMonitor?.stop();
|
|
151
|
+
this._qualityMonitorVideo = null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private _syncStatsPolling(): void {
|
|
155
|
+
if (this._activeTab !== "stats") {
|
|
156
|
+
this._stopStatsPolling();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!this._videoStatsInterval) {
|
|
161
|
+
this._updateVideoStats();
|
|
162
|
+
this._videoStatsInterval = setInterval(() => {
|
|
163
|
+
this._updateVideoStats();
|
|
164
|
+
}, 500);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!this._playerStatsInterval) {
|
|
168
|
+
void this._pollPlayerStats();
|
|
169
|
+
this._playerStatsInterval = setInterval(() => {
|
|
170
|
+
void this._pollPlayerStats();
|
|
171
|
+
}, 500);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private _stopStatsPolling(): void {
|
|
176
|
+
if (this._videoStatsInterval) {
|
|
177
|
+
clearInterval(this._videoStatsInterval);
|
|
178
|
+
this._videoStatsInterval = null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (this._playerStatsInterval) {
|
|
182
|
+
clearInterval(this._playerStatsInterval);
|
|
183
|
+
this._playerStatsInterval = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private _updateVideoStats(): void {
|
|
188
|
+
const video = this.pc.s.videoElement;
|
|
189
|
+
if (!video) {
|
|
190
|
+
this._videoStats = null;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this._videoStats = {
|
|
195
|
+
resolution: `${video.videoWidth}x${video.videoHeight}`,
|
|
196
|
+
buffered:
|
|
197
|
+
video.buffered.length > 0
|
|
198
|
+
? (video.buffered.end(video.buffered.length - 1) - video.currentTime).toFixed(1)
|
|
199
|
+
: "0",
|
|
200
|
+
playbackRate: video.playbackRate.toFixed(2),
|
|
201
|
+
currentTime: video.currentTime.toFixed(1),
|
|
202
|
+
duration: Number.isFinite(video.duration) ? video.duration.toFixed(1) : "live",
|
|
203
|
+
readyState: video.readyState,
|
|
204
|
+
networkState: video.networkState,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async _pollPlayerStats(): Promise<void> {
|
|
209
|
+
try {
|
|
210
|
+
const stats = await this.pc.getStats();
|
|
211
|
+
if (stats) {
|
|
212
|
+
this._playerStats = stats;
|
|
213
|
+
}
|
|
214
|
+
} catch {
|
|
215
|
+
// No-op for optional stats backends.
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private _handleComboMouseEnter(index: number, event: MouseEvent): void {
|
|
220
|
+
this._hoveredComboIndex = index;
|
|
221
|
+
|
|
222
|
+
const container = this.renderRoot.querySelector(".fw-dev-body") as HTMLElement | null;
|
|
223
|
+
if (!container) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const row = event.currentTarget as HTMLElement;
|
|
228
|
+
const containerRect = container.getBoundingClientRect();
|
|
229
|
+
const rowRect = row.getBoundingClientRect();
|
|
230
|
+
const relativePosition = (rowRect.top - containerRect.top) / containerRect.height;
|
|
231
|
+
|
|
232
|
+
this._tooltipAbove = relativePosition > 0.6;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private _handleModeChange(mode: "auto" | "low-latency" | "quality"): void {
|
|
236
|
+
this.playbackMode = mode;
|
|
237
|
+
void this.pc.setDevModeOptions({ playbackMode: mode });
|
|
238
|
+
this.dispatchEvent(
|
|
239
|
+
new CustomEvent("fw-playback-mode-change", {
|
|
240
|
+
detail: { mode },
|
|
241
|
+
bubbles: true,
|
|
242
|
+
composed: true,
|
|
243
|
+
})
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private _handleReload(): void {
|
|
248
|
+
this.pc.clearError();
|
|
249
|
+
void this.pc.reload();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private _handleNextCombo(): void {
|
|
253
|
+
const compatible = this._getCompatibleCombinations();
|
|
254
|
+
if (compatible.length === 0) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const activeCompatibleIndex = this._getActiveComboIndex(compatible);
|
|
259
|
+
const startIndex = activeCompatibleIndex >= 0 ? activeCompatibleIndex : -1;
|
|
260
|
+
const nextIndex = (startIndex + 1) % compatible.length;
|
|
261
|
+
const next = compatible[nextIndex];
|
|
262
|
+
|
|
263
|
+
void this.pc.setDevModeOptions({
|
|
264
|
+
forcePlayer: next.player,
|
|
265
|
+
forceType: next.sourceType,
|
|
266
|
+
forceSource: next.sourceIndex,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private _handleSelectCombo(index: number): void {
|
|
271
|
+
const allCombinations = this._getAllCombinations();
|
|
272
|
+
const combo = allCombinations[index];
|
|
273
|
+
if (!combo) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
void this.pc.setDevModeOptions({
|
|
278
|
+
forcePlayer: combo.player,
|
|
279
|
+
forceType: combo.sourceType,
|
|
280
|
+
forceSource: combo.sourceIndex,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private _renderStatsTab(): unknown {
|
|
285
|
+
const primaryEndpoint = (this.pc.s.endpoints?.primary ?? null) as {
|
|
286
|
+
protocol?: string;
|
|
287
|
+
nodeId?: string;
|
|
288
|
+
} | null;
|
|
289
|
+
|
|
290
|
+
const stats = this._videoStats;
|
|
291
|
+
const playerStats = this._playerStats as any;
|
|
292
|
+
const mistStreamInfo = this._getMistStreamInfo();
|
|
293
|
+
const trackEntries = Object.entries(mistStreamInfo?.meta?.tracks ?? {});
|
|
153
294
|
|
|
154
295
|
return html`
|
|
155
|
-
<div class="
|
|
156
|
-
<div class="
|
|
157
|
-
<div class="
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
296
|
+
<div class="fw-dev-body">
|
|
297
|
+
<div class="fw-dev-section">
|
|
298
|
+
<div class="fw-dev-label">Playback Rate</div>
|
|
299
|
+
<div class="fw-dev-rate">
|
|
300
|
+
<div
|
|
301
|
+
class=${classMap({
|
|
302
|
+
"fw-dev-rate-value": true,
|
|
303
|
+
"fw-dev-stat-value--good":
|
|
304
|
+
this._playbackScore >= 0.95 && this._playbackScore <= 1.05,
|
|
305
|
+
"fw-dev-stat-value--accent": this._playbackScore > 1.05,
|
|
306
|
+
"fw-dev-stat-value--warn":
|
|
307
|
+
this._playbackScore >= 0.75 && this._playbackScore < 0.95,
|
|
308
|
+
"fw-dev-stat-value--bad": this._playbackScore < 0.75,
|
|
309
|
+
})}
|
|
310
|
+
>
|
|
311
|
+
${this._playbackScore.toFixed(2)}x
|
|
312
|
+
</div>
|
|
313
|
+
<div class="fw-dev-rate-status">
|
|
314
|
+
${this._playbackScore >= 0.95 && this._playbackScore <= 1.05
|
|
315
|
+
? "realtime"
|
|
316
|
+
: this._playbackScore > 1.05
|
|
317
|
+
? "catching up"
|
|
318
|
+
: this._playbackScore >= 0.75
|
|
319
|
+
? "slightly slow"
|
|
320
|
+
: "stalling"}
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
<div class="fw-dev-rate-stats">
|
|
324
|
+
<span
|
|
325
|
+
class=${classMap({
|
|
326
|
+
"fw-dev-stat-value--good": this._qualityScore >= 75,
|
|
327
|
+
"fw-dev-stat-value--bad": this._qualityScore < 75,
|
|
328
|
+
})}
|
|
163
329
|
>
|
|
164
|
-
|
|
165
|
-
</
|
|
166
|
-
<
|
|
167
|
-
class=${classMap({
|
|
168
|
-
|
|
169
|
-
this.
|
|
170
|
-
}}
|
|
330
|
+
Quality: ${this._qualityScore}/100
|
|
331
|
+
</span>
|
|
332
|
+
<span
|
|
333
|
+
class=${classMap({
|
|
334
|
+
"fw-dev-stat-value--good": this._stallCount === 0,
|
|
335
|
+
"fw-dev-stat-value--warn": this._stallCount > 0,
|
|
336
|
+
})}
|
|
171
337
|
>
|
|
172
|
-
|
|
173
|
-
</
|
|
338
|
+
Stalls: ${this._stallCount}
|
|
339
|
+
</span>
|
|
340
|
+
<span
|
|
341
|
+
class=${classMap({
|
|
342
|
+
"fw-dev-stat-value--good": this._frameDropRate < 1,
|
|
343
|
+
"fw-dev-stat-value--bad": this._frameDropRate >= 1,
|
|
344
|
+
})}
|
|
345
|
+
>
|
|
346
|
+
Drops: ${this._frameDropRate.toFixed(1)}%
|
|
347
|
+
</span>
|
|
174
348
|
</div>
|
|
175
|
-
<button
|
|
176
|
-
class="close"
|
|
177
|
-
@click=${() =>
|
|
178
|
-
this.dispatchEvent(new CustomEvent("fw-close", { bubbles: true, composed: true }))}
|
|
179
|
-
aria-label="Close panel"
|
|
180
|
-
>
|
|
181
|
-
${closeIcon()}
|
|
182
|
-
</button>
|
|
183
349
|
</div>
|
|
184
350
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
351
|
+
${stats
|
|
352
|
+
? html`
|
|
353
|
+
<div class="fw-dev-stat">
|
|
354
|
+
<span class="fw-dev-stat-label">Resolution</span>
|
|
355
|
+
<span class="fw-dev-stat-value">${stats.resolution}</span>
|
|
356
|
+
</div>
|
|
357
|
+
<div class="fw-dev-stat">
|
|
358
|
+
<span class="fw-dev-stat-label">Buffer</span>
|
|
359
|
+
<span class="fw-dev-stat-value">${stats.buffered}s</span>
|
|
360
|
+
</div>
|
|
361
|
+
<div class="fw-dev-stat">
|
|
362
|
+
<span class="fw-dev-stat-label">Playback Rate</span>
|
|
363
|
+
<span class="fw-dev-stat-value">${stats.playbackRate}x</span>
|
|
364
|
+
</div>
|
|
365
|
+
<div class="fw-dev-stat">
|
|
366
|
+
<span class="fw-dev-stat-label">Time</span>
|
|
367
|
+
<span class="fw-dev-stat-value">${stats.currentTime} / ${stats.duration}</span>
|
|
368
|
+
</div>
|
|
369
|
+
<div class="fw-dev-stat">
|
|
370
|
+
<span class="fw-dev-stat-label">Ready State</span>
|
|
371
|
+
<span class="fw-dev-stat-value">${stats.readyState}</span>
|
|
372
|
+
</div>
|
|
373
|
+
<div class="fw-dev-stat">
|
|
374
|
+
<span class="fw-dev-stat-label">Network State</span>
|
|
375
|
+
<span class="fw-dev-stat-value">${stats.networkState}</span>
|
|
376
|
+
</div>
|
|
377
|
+
${primaryEndpoint?.protocol
|
|
378
|
+
? html`
|
|
379
|
+
<div class="fw-dev-stat">
|
|
380
|
+
<span class="fw-dev-stat-label">Protocol</span>
|
|
381
|
+
<span class="fw-dev-stat-value">${primaryEndpoint.protocol}</span>
|
|
382
|
+
</div>
|
|
383
|
+
`
|
|
384
|
+
: nothing}
|
|
385
|
+
${primaryEndpoint?.nodeId
|
|
386
|
+
? html`
|
|
387
|
+
<div class="fw-dev-stat">
|
|
388
|
+
<span class="fw-dev-stat-label">Node ID</span>
|
|
389
|
+
<span
|
|
390
|
+
class="fw-dev-stat-value"
|
|
391
|
+
style="max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"
|
|
392
|
+
>${primaryEndpoint.nodeId}</span
|
|
393
|
+
>
|
|
394
|
+
</div>
|
|
395
|
+
`
|
|
396
|
+
: nothing}
|
|
397
|
+
`
|
|
398
|
+
: html`<div class="fw-dev-list-empty">No video element available</div>`}
|
|
399
|
+
${playerStats
|
|
400
|
+
? html`
|
|
401
|
+
<div class="fw-dev-list-header fw-dev-section-header">
|
|
402
|
+
<span class="fw-dev-list-title"
|
|
403
|
+
>${playerStats.type === "hls"
|
|
404
|
+
? "HLS.js Stats"
|
|
405
|
+
: playerStats.type === "webrtc"
|
|
406
|
+
? "WebRTC Stats"
|
|
407
|
+
: "Player Stats"}</span
|
|
408
|
+
>
|
|
409
|
+
</div>
|
|
410
|
+
|
|
411
|
+
${playerStats.type === "hls"
|
|
412
|
+
? html`
|
|
413
|
+
<div class="fw-dev-stat">
|
|
414
|
+
<span class="fw-dev-stat-label">Bitrate</span>
|
|
415
|
+
<span class="fw-dev-stat-value--accent"
|
|
416
|
+
>${typeof playerStats.currentBitrate === "number" &&
|
|
417
|
+
playerStats.currentBitrate > 0
|
|
418
|
+
? `${Math.round(playerStats.currentBitrate / 1000)} kbps`
|
|
419
|
+
: "N/A"}</span
|
|
420
|
+
>
|
|
421
|
+
</div>
|
|
422
|
+
<div class="fw-dev-stat">
|
|
423
|
+
<span class="fw-dev-stat-label">Bandwidth Est.</span>
|
|
424
|
+
<span class="fw-dev-stat-value"
|
|
425
|
+
>${typeof playerStats.bandwidthEstimate === "number" &&
|
|
426
|
+
playerStats.bandwidthEstimate > 0
|
|
427
|
+
? `${Math.round(playerStats.bandwidthEstimate / 1000)} kbps`
|
|
428
|
+
: "N/A"}</span
|
|
429
|
+
>
|
|
430
|
+
</div>
|
|
431
|
+
<div class="fw-dev-stat">
|
|
432
|
+
<span class="fw-dev-stat-label">Level</span>
|
|
433
|
+
<span class="fw-dev-stat-value"
|
|
434
|
+
>${typeof playerStats.currentLevel === "number" &&
|
|
435
|
+
playerStats.currentLevel >= 0
|
|
436
|
+
? playerStats.currentLevel
|
|
437
|
+
: "Auto"}
|
|
438
|
+
/ ${Array.isArray(playerStats.levels) ? playerStats.levels.length : 0}</span
|
|
439
|
+
>
|
|
440
|
+
</div>
|
|
441
|
+
${typeof playerStats.latency === "number"
|
|
442
|
+
? html`
|
|
443
|
+
<div class="fw-dev-stat">
|
|
444
|
+
<span class="fw-dev-stat-label">Latency</span>
|
|
445
|
+
<span
|
|
446
|
+
class=${classMap({
|
|
447
|
+
"fw-dev-stat-value": true,
|
|
448
|
+
"fw-dev-stat-value--warn": playerStats.latency > 5000,
|
|
449
|
+
})}
|
|
450
|
+
>${Math.round(playerStats.latency)} ms</span
|
|
451
|
+
>
|
|
452
|
+
</div>
|
|
453
|
+
`
|
|
454
|
+
: nothing}
|
|
455
|
+
`
|
|
456
|
+
: nothing}
|
|
457
|
+
${playerStats.type === "webrtc"
|
|
458
|
+
? html`
|
|
459
|
+
${playerStats.video
|
|
460
|
+
? html`
|
|
461
|
+
<div class="fw-dev-stat">
|
|
462
|
+
<span class="fw-dev-stat-label">Video Bitrate</span>
|
|
463
|
+
<span class="fw-dev-stat-value--accent"
|
|
464
|
+
>${typeof playerStats.video.bitrate === "number" &&
|
|
465
|
+
playerStats.video.bitrate > 0
|
|
466
|
+
? `${Math.round(playerStats.video.bitrate / 1000)} kbps`
|
|
467
|
+
: "N/A"}</span
|
|
468
|
+
>
|
|
469
|
+
</div>
|
|
470
|
+
<div class="fw-dev-stat">
|
|
471
|
+
<span class="fw-dev-stat-label">FPS</span>
|
|
472
|
+
<span class="fw-dev-stat-value"
|
|
473
|
+
>${Math.round(
|
|
474
|
+
(playerStats.video.framesPerSecond as number) || 0
|
|
475
|
+
)}</span
|
|
476
|
+
>
|
|
477
|
+
</div>
|
|
478
|
+
<div class="fw-dev-stat">
|
|
479
|
+
<span class="fw-dev-stat-label">Frames</span>
|
|
480
|
+
<span class="fw-dev-stat-value"
|
|
481
|
+
>${playerStats.video.framesDecoded as number} decoded,
|
|
482
|
+
<span
|
|
483
|
+
class=${classMap({
|
|
484
|
+
"fw-dev-stat-value--bad":
|
|
485
|
+
((playerStats.video.frameDropRate as number) || 0) > 1,
|
|
486
|
+
"fw-dev-stat-value--good":
|
|
487
|
+
((playerStats.video.frameDropRate as number) || 0) <= 1,
|
|
488
|
+
})}
|
|
489
|
+
>${playerStats.video.framesDropped as number} dropped</span
|
|
490
|
+
></span
|
|
491
|
+
>
|
|
492
|
+
</div>
|
|
493
|
+
<div class="fw-dev-stat">
|
|
494
|
+
<span class="fw-dev-stat-label">Packet Loss</span>
|
|
495
|
+
<span
|
|
496
|
+
class=${classMap({
|
|
497
|
+
"fw-dev-stat-value--bad":
|
|
498
|
+
((playerStats.video.packetLossRate as number) || 0) > 1,
|
|
499
|
+
"fw-dev-stat-value--good":
|
|
500
|
+
((playerStats.video.packetLossRate as number) || 0) <= 1,
|
|
501
|
+
})}
|
|
502
|
+
>${(
|
|
503
|
+
((playerStats.video.packetLossRate as number) || 0) as number
|
|
504
|
+
).toFixed(2)}%</span
|
|
505
|
+
>
|
|
506
|
+
</div>
|
|
507
|
+
<div class="fw-dev-stat">
|
|
508
|
+
<span class="fw-dev-stat-label">Jitter</span>
|
|
509
|
+
<span
|
|
510
|
+
class=${classMap({
|
|
511
|
+
"fw-dev-stat-value": true,
|
|
512
|
+
"fw-dev-stat-value--warn":
|
|
513
|
+
((playerStats.video.jitter as number) || 0) > 30,
|
|
514
|
+
})}
|
|
515
|
+
>${(((playerStats.video.jitter as number) || 0) as number).toFixed(1)}
|
|
516
|
+
ms</span
|
|
517
|
+
>
|
|
518
|
+
</div>
|
|
519
|
+
<div class="fw-dev-stat">
|
|
520
|
+
<span class="fw-dev-stat-label">Jitter Buffer</span>
|
|
521
|
+
<span class="fw-dev-stat-value"
|
|
522
|
+
>${(
|
|
523
|
+
((playerStats.video.jitterBufferDelay as number) || 0) as number
|
|
524
|
+
).toFixed(1)}
|
|
525
|
+
ms</span
|
|
526
|
+
>
|
|
527
|
+
</div>
|
|
528
|
+
`
|
|
529
|
+
: nothing}
|
|
530
|
+
${playerStats.network
|
|
531
|
+
? html`
|
|
532
|
+
<div class="fw-dev-stat">
|
|
533
|
+
<span class="fw-dev-stat-label">RTT</span>
|
|
534
|
+
<span
|
|
535
|
+
class=${classMap({
|
|
536
|
+
"fw-dev-stat-value": true,
|
|
537
|
+
"fw-dev-stat-value--warn":
|
|
538
|
+
((playerStats.network.rtt as number) || 0) > 200,
|
|
539
|
+
})}
|
|
540
|
+
>${Math.round(((playerStats.network.rtt as number) || 0) as number)}
|
|
541
|
+
ms</span
|
|
542
|
+
>
|
|
543
|
+
</div>
|
|
544
|
+
`
|
|
545
|
+
: nothing}
|
|
546
|
+
`
|
|
547
|
+
: nothing}
|
|
548
|
+
`
|
|
549
|
+
: nothing}
|
|
550
|
+
${trackEntries.length > 0
|
|
551
|
+
? html`
|
|
552
|
+
<div class="fw-dev-list-header fw-dev-section-header">
|
|
553
|
+
<span class="fw-dev-list-title">Tracks (${trackEntries.length})</span>
|
|
554
|
+
</div>
|
|
555
|
+
${trackEntries.map(([id, track]) => {
|
|
556
|
+
const typedTrack = track as {
|
|
557
|
+
type: string;
|
|
558
|
+
codec: string;
|
|
559
|
+
width?: number;
|
|
560
|
+
height?: number;
|
|
561
|
+
bps?: number;
|
|
562
|
+
fpks?: number;
|
|
563
|
+
channels?: number;
|
|
564
|
+
rate?: number;
|
|
565
|
+
lang?: string;
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
return html`
|
|
569
|
+
<div class="fw-dev-track">
|
|
570
|
+
<div class="fw-dev-track-header">
|
|
571
|
+
<span
|
|
572
|
+
class=${classMap({
|
|
573
|
+
"fw-dev-track-badge": true,
|
|
574
|
+
"fw-dev-track-badge--video": typedTrack.type === "video",
|
|
575
|
+
"fw-dev-track-badge--audio": typedTrack.type === "audio",
|
|
576
|
+
"fw-dev-track-badge--other":
|
|
577
|
+
typedTrack.type !== "video" && typedTrack.type !== "audio",
|
|
578
|
+
})}
|
|
579
|
+
>${typedTrack.type}</span
|
|
580
|
+
>
|
|
581
|
+
<span class="fw-dev-track-codec">${typedTrack.codec}</span>
|
|
582
|
+
<span class="fw-dev-track-id">#${id}</span>
|
|
583
|
+
</div>
|
|
584
|
+
<div class="fw-dev-track-meta">
|
|
585
|
+
${typedTrack.type === "video" && typedTrack.width && typedTrack.height
|
|
586
|
+
? html`<span>${typedTrack.width}x${typedTrack.height}</span>`
|
|
587
|
+
: nothing}
|
|
588
|
+
${typedTrack.bps
|
|
589
|
+
? html`<span>${Math.round(typedTrack.bps / 1000)} kbps</span>`
|
|
590
|
+
: nothing}
|
|
591
|
+
${typedTrack.fpks
|
|
592
|
+
? html`<span>${Math.round(typedTrack.fpks / 1000)} fps</span>`
|
|
593
|
+
: nothing}
|
|
594
|
+
${typedTrack.type === "audio" && typedTrack.channels
|
|
595
|
+
? html`<span>${typedTrack.channels}ch</span>`
|
|
596
|
+
: nothing}
|
|
597
|
+
${typedTrack.type === "audio" && typedTrack.rate
|
|
598
|
+
? html`<span>${typedTrack.rate} Hz</span>`
|
|
599
|
+
: nothing}
|
|
600
|
+
${typedTrack.lang ? html`<span>${typedTrack.lang}</span>` : nothing}
|
|
601
|
+
</div>
|
|
602
|
+
</div>
|
|
603
|
+
`;
|
|
604
|
+
})}
|
|
605
|
+
`
|
|
606
|
+
: nothing}
|
|
607
|
+
${mistStreamInfo && trackEntries.length === 0
|
|
608
|
+
? html`
|
|
609
|
+
<div class="fw-dev-no-tracks">
|
|
610
|
+
<span class="fw-dev-no-tracks-text"
|
|
611
|
+
>No track data available
|
|
612
|
+
${mistStreamInfo.type
|
|
613
|
+
? html`<span class="fw-dev-no-tracks-type">(${mistStreamInfo.type})</span>`
|
|
614
|
+
: nothing}</span
|
|
615
|
+
>
|
|
616
|
+
</div>
|
|
617
|
+
`
|
|
618
|
+
: nothing}
|
|
188
619
|
</div>
|
|
189
620
|
`;
|
|
190
621
|
}
|
|
191
622
|
|
|
192
|
-
private
|
|
623
|
+
private _renderConfigTab(): unknown {
|
|
624
|
+
const allCombinations = this._getAllCombinations();
|
|
625
|
+
const compatibleCombinations = allCombinations.filter((combo) => combo.compatible);
|
|
626
|
+
const activeComboIndex = this._getActiveComboIndex(allCombinations);
|
|
627
|
+
|
|
628
|
+
const currentPlayer = this.pc.s.currentPlayerInfo;
|
|
629
|
+
const currentSource = this.pc.s.currentSourceInfo;
|
|
630
|
+
|
|
193
631
|
return html`
|
|
194
|
-
<div class="
|
|
195
|
-
<div class="
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
632
|
+
<div class="fw-dev-body">
|
|
633
|
+
<div class="fw-dev-section">
|
|
634
|
+
<div class="fw-dev-label">Active</div>
|
|
635
|
+
<div class="fw-dev-value">
|
|
636
|
+
${currentPlayer?.name || "None"}
|
|
637
|
+
<span class="fw-dev-value-arrow">${"\u2192"}</span>
|
|
638
|
+
${SOURCE_TYPE_LABELS[currentSource?.type || ""] || currentSource?.type || "-"}
|
|
639
|
+
</div>
|
|
640
|
+
${(this.pc.s.endpoints?.primary as { nodeId?: string } | undefined)?.nodeId
|
|
641
|
+
? html`
|
|
642
|
+
<div class="fw-dev-value-muted">
|
|
643
|
+
Node: ${(this.pc.s.endpoints?.primary as { nodeId?: string }).nodeId}
|
|
644
|
+
</div>
|
|
645
|
+
`
|
|
646
|
+
: nothing}
|
|
647
|
+
</div>
|
|
648
|
+
|
|
649
|
+
<div class="fw-dev-section">
|
|
650
|
+
<div class="fw-dev-label">Playback Mode</div>
|
|
651
|
+
<div class="fw-dev-mode-group">
|
|
652
|
+
${(["auto", "low-latency", "quality"] as const).map(
|
|
653
|
+
(mode) => html`
|
|
654
|
+
<button
|
|
655
|
+
type="button"
|
|
656
|
+
class=${classMap({
|
|
657
|
+
"fw-dev-mode-btn": true,
|
|
658
|
+
"fw-dev-mode-btn--active": this.playbackMode === mode,
|
|
659
|
+
})}
|
|
660
|
+
@click=${() => this._handleModeChange(mode)}
|
|
661
|
+
>
|
|
662
|
+
${mode === "low-latency"
|
|
663
|
+
? "Low Lat"
|
|
664
|
+
: `${mode.charAt(0).toUpperCase()}${mode.slice(1)}`}
|
|
665
|
+
</button>
|
|
666
|
+
`
|
|
667
|
+
)}
|
|
668
|
+
</div>
|
|
669
|
+
<div class="fw-dev-mode-desc">
|
|
670
|
+
${this.playbackMode === "auto"
|
|
671
|
+
? "Balanced: MP4/WS \u2192 WHEP \u2192 HLS"
|
|
672
|
+
: this.playbackMode === "low-latency"
|
|
673
|
+
? "WHEP/WebRTC first (<1s delay)"
|
|
674
|
+
: "MP4/WS first, HLS fallback"}
|
|
675
|
+
</div>
|
|
676
|
+
</div>
|
|
677
|
+
|
|
678
|
+
<div class="fw-dev-actions">
|
|
679
|
+
<button type="button" class="fw-dev-action-btn" @click=${this._handleReload}>
|
|
680
|
+
Reload
|
|
681
|
+
</button>
|
|
682
|
+
<button type="button" class="fw-dev-action-btn" @click=${this._handleNextCombo}>
|
|
683
|
+
Next Option
|
|
684
|
+
</button>
|
|
685
|
+
</div>
|
|
686
|
+
|
|
687
|
+
<div class="fw-dev-section" style="padding:0;border-bottom:0;">
|
|
688
|
+
<div class="fw-dev-list-header">
|
|
689
|
+
<span class="fw-dev-list-title">Player Options (${compatibleCombinations.length})</span>
|
|
690
|
+
${allCombinations.length > compatibleCombinations.length
|
|
691
|
+
? html`
|
|
692
|
+
<button
|
|
693
|
+
type="button"
|
|
694
|
+
class="fw-dev-list-toggle"
|
|
695
|
+
@click=${() => {
|
|
696
|
+
this._showDisabledPlayers = !this._showDisabledPlayers;
|
|
697
|
+
}}
|
|
698
|
+
>
|
|
699
|
+
<svg
|
|
700
|
+
width="10"
|
|
701
|
+
height="10"
|
|
702
|
+
viewBox="0 0 24 24"
|
|
703
|
+
fill="none"
|
|
704
|
+
stroke="currentColor"
|
|
705
|
+
stroke-width="2"
|
|
706
|
+
class=${classMap({
|
|
707
|
+
"fw-dev-chevron": true,
|
|
708
|
+
"fw-dev-chevron--open": this._showDisabledPlayers,
|
|
709
|
+
})}
|
|
710
|
+
>
|
|
711
|
+
<path d="M6 9l6 6 6-6"></path>
|
|
712
|
+
</svg>
|
|
713
|
+
${this._showDisabledPlayers ? "Hide" : "Show"} disabled
|
|
714
|
+
(${allCombinations.length - compatibleCombinations.length})
|
|
715
|
+
</button>
|
|
716
|
+
`
|
|
717
|
+
: nothing}
|
|
718
|
+
</div>
|
|
719
|
+
|
|
720
|
+
${allCombinations.length === 0
|
|
721
|
+
? html`<div class="fw-dev-list-empty">No stream info available</div>`
|
|
722
|
+
: html`
|
|
723
|
+
${allCombinations.map((combo, index) => {
|
|
724
|
+
const isCodecIncompatible = combo.codecIncompatible === true;
|
|
725
|
+
if (!combo.compatible && !isCodecIncompatible && !this._showDisabledPlayers) {
|
|
726
|
+
return nothing;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const isActive = activeComboIndex === index;
|
|
730
|
+
const typeLabel =
|
|
731
|
+
SOURCE_TYPE_LABELS[combo.sourceType] || combo.sourceType.split("/").pop();
|
|
732
|
+
|
|
733
|
+
const scoreClass =
|
|
734
|
+
!combo.compatible && !isCodecIncompatible
|
|
735
|
+
? "fw-dev-combo-score--disabled"
|
|
736
|
+
: isCodecIncompatible
|
|
737
|
+
? "fw-dev-combo-score--low"
|
|
738
|
+
: combo.score >= 2
|
|
739
|
+
? "fw-dev-combo-score--high"
|
|
740
|
+
: combo.score >= 1.5
|
|
741
|
+
? "fw-dev-combo-score--mid"
|
|
742
|
+
: "fw-dev-combo-score--low";
|
|
743
|
+
|
|
744
|
+
const rankClass = isActive
|
|
745
|
+
? "fw-dev-combo-rank--active"
|
|
746
|
+
: !combo.compatible && !isCodecIncompatible
|
|
747
|
+
? "fw-dev-combo-rank--disabled"
|
|
748
|
+
: isCodecIncompatible
|
|
749
|
+
? "fw-dev-combo-rank--warn"
|
|
750
|
+
: "";
|
|
751
|
+
|
|
752
|
+
const typeClass =
|
|
753
|
+
!combo.compatible && !isCodecIncompatible
|
|
754
|
+
? "fw-dev-combo-type--disabled"
|
|
755
|
+
: isCodecIncompatible
|
|
756
|
+
? "fw-dev-combo-type--warn"
|
|
757
|
+
: "";
|
|
758
|
+
|
|
759
|
+
return html`
|
|
760
|
+
<div
|
|
761
|
+
class="fw-dev-combo"
|
|
762
|
+
@mouseenter=${(event: MouseEvent) =>
|
|
763
|
+
this._handleComboMouseEnter(index, event)}
|
|
764
|
+
@mouseleave=${() => {
|
|
765
|
+
this._hoveredComboIndex = null;
|
|
766
|
+
}}
|
|
767
|
+
>
|
|
768
|
+
<button
|
|
769
|
+
type="button"
|
|
770
|
+
class=${classMap({
|
|
771
|
+
"fw-dev-combo-btn": true,
|
|
772
|
+
"fw-dev-combo-btn--active": isActive,
|
|
773
|
+
"fw-dev-combo-btn--disabled": !combo.compatible && !isCodecIncompatible,
|
|
774
|
+
"fw-dev-combo-btn--codec-warn": isCodecIncompatible,
|
|
775
|
+
})}
|
|
776
|
+
@click=${() => this._handleSelectCombo(index)}
|
|
777
|
+
>
|
|
778
|
+
<span
|
|
779
|
+
class=${classMap({
|
|
780
|
+
"fw-dev-combo-rank": true,
|
|
781
|
+
[rankClass]: rankClass.length > 0,
|
|
782
|
+
})}
|
|
783
|
+
>${combo.compatible
|
|
784
|
+
? index + 1
|
|
785
|
+
: isCodecIncompatible
|
|
786
|
+
? "\u26A0"
|
|
787
|
+
: "\u2014"}</span
|
|
788
|
+
>
|
|
789
|
+
|
|
790
|
+
<span class="fw-dev-combo-name"
|
|
791
|
+
>${combo.playerName} <span class="fw-dev-combo-arrow">${"\u2192"}</span>
|
|
792
|
+
<span
|
|
793
|
+
class=${classMap({
|
|
794
|
+
"fw-dev-combo-type": true,
|
|
795
|
+
[typeClass]: typeClass.length > 0,
|
|
796
|
+
})}
|
|
797
|
+
>${typeLabel}</span
|
|
798
|
+
></span
|
|
799
|
+
>
|
|
800
|
+
|
|
801
|
+
<span class=${classMap({ "fw-dev-combo-score": true, [scoreClass]: true })}
|
|
802
|
+
>${combo.score.toFixed(2)}</span
|
|
803
|
+
>
|
|
804
|
+
</button>
|
|
805
|
+
|
|
806
|
+
${this._hoveredComboIndex === index
|
|
807
|
+
? html`
|
|
808
|
+
<div
|
|
809
|
+
class=${classMap({
|
|
810
|
+
"fw-dev-tooltip": true,
|
|
811
|
+
"fw-dev-tooltip--above": this._tooltipAbove,
|
|
812
|
+
"fw-dev-tooltip--below": !this._tooltipAbove,
|
|
813
|
+
})}
|
|
814
|
+
>
|
|
815
|
+
<div class="fw-dev-tooltip-header">
|
|
816
|
+
<div class="fw-dev-tooltip-title">${combo.playerName}</div>
|
|
817
|
+
<div class="fw-dev-tooltip-subtitle">${combo.sourceType}</div>
|
|
818
|
+
${combo.scoreBreakdown?.trackTypes &&
|
|
819
|
+
combo.scoreBreakdown.trackTypes.length > 0
|
|
820
|
+
? html`
|
|
821
|
+
<div class="fw-dev-tooltip-tracks">
|
|
822
|
+
Tracks:
|
|
823
|
+
<span class="fw-dev-tooltip-value"
|
|
824
|
+
>${combo.scoreBreakdown.trackTypes.join(", ")}</span
|
|
825
|
+
>
|
|
826
|
+
</div>
|
|
827
|
+
`
|
|
828
|
+
: nothing}
|
|
829
|
+
</div>
|
|
830
|
+
|
|
831
|
+
${combo.compatible && combo.scoreBreakdown
|
|
832
|
+
? html`
|
|
833
|
+
<div class="fw-dev-tooltip-score">
|
|
834
|
+
Score: ${combo.score.toFixed(2)}
|
|
835
|
+
</div>
|
|
836
|
+
<div class="fw-dev-tooltip-row">
|
|
837
|
+
Tracks [${combo.scoreBreakdown.trackTypes.join(", ")}]:
|
|
838
|
+
<span class="fw-dev-tooltip-value"
|
|
839
|
+
>${combo.scoreBreakdown.trackScore.toFixed(2)}</span
|
|
840
|
+
>
|
|
841
|
+
<span class="fw-dev-tooltip-weight"
|
|
842
|
+
>x${combo.scoreBreakdown.weights.tracks}</span
|
|
843
|
+
>
|
|
844
|
+
</div>
|
|
845
|
+
<div class="fw-dev-tooltip-row">
|
|
846
|
+
Priority:
|
|
847
|
+
<span class="fw-dev-tooltip-value"
|
|
848
|
+
>${combo.scoreBreakdown.priorityScore.toFixed(2)}</span
|
|
849
|
+
>
|
|
850
|
+
<span class="fw-dev-tooltip-weight"
|
|
851
|
+
>x${combo.scoreBreakdown.weights.priority}</span
|
|
852
|
+
>
|
|
853
|
+
</div>
|
|
854
|
+
<div class="fw-dev-tooltip-row">
|
|
855
|
+
Source:
|
|
856
|
+
<span class="fw-dev-tooltip-value"
|
|
857
|
+
>${combo.scoreBreakdown.sourceScore.toFixed(2)}</span
|
|
858
|
+
>
|
|
859
|
+
<span class="fw-dev-tooltip-weight"
|
|
860
|
+
>x${combo.scoreBreakdown.weights.source}</span
|
|
861
|
+
>
|
|
862
|
+
</div>
|
|
863
|
+
|
|
864
|
+
${typeof combo.scoreBreakdown.reliabilityScore === "number"
|
|
865
|
+
? html`
|
|
866
|
+
<div class="fw-dev-tooltip-row">
|
|
867
|
+
Reliability:
|
|
868
|
+
<span class="fw-dev-tooltip-value"
|
|
869
|
+
>${combo.scoreBreakdown.reliabilityScore.toFixed(
|
|
870
|
+
2
|
|
871
|
+
)}</span
|
|
872
|
+
>
|
|
873
|
+
<span class="fw-dev-tooltip-weight"
|
|
874
|
+
>x${combo.scoreBreakdown.weights.reliability ??
|
|
875
|
+
0}</span
|
|
876
|
+
>
|
|
877
|
+
</div>
|
|
878
|
+
`
|
|
879
|
+
: nothing}
|
|
880
|
+
${typeof combo.scoreBreakdown.modeBonus === "number" &&
|
|
881
|
+
combo.scoreBreakdown.modeBonus !== 0
|
|
882
|
+
? html`
|
|
883
|
+
<div class="fw-dev-tooltip-row">
|
|
884
|
+
Mode (${this.playbackMode}):
|
|
885
|
+
<span class="fw-dev-tooltip-bonus"
|
|
886
|
+
>+${combo.scoreBreakdown.modeBonus.toFixed(2)}</span
|
|
887
|
+
>
|
|
888
|
+
<span class="fw-dev-tooltip-weight"
|
|
889
|
+
>x${combo.scoreBreakdown.weights.mode ?? 0}</span
|
|
890
|
+
>
|
|
891
|
+
</div>
|
|
892
|
+
`
|
|
893
|
+
: nothing}
|
|
894
|
+
${typeof combo.scoreBreakdown.routingBonus === "number" &&
|
|
895
|
+
combo.scoreBreakdown.routingBonus !== 0
|
|
896
|
+
? html`
|
|
897
|
+
<div class="fw-dev-tooltip-row">
|
|
898
|
+
Routing:
|
|
899
|
+
<span
|
|
900
|
+
class=${classMap({
|
|
901
|
+
"fw-dev-tooltip-bonus":
|
|
902
|
+
combo.scoreBreakdown.routingBonus > 0,
|
|
903
|
+
"fw-dev-tooltip-penalty":
|
|
904
|
+
combo.scoreBreakdown.routingBonus < 0,
|
|
905
|
+
})}
|
|
906
|
+
>${combo.scoreBreakdown.routingBonus > 0
|
|
907
|
+
? "+"
|
|
908
|
+
: ""}${combo.scoreBreakdown.routingBonus.toFixed(
|
|
909
|
+
2
|
|
910
|
+
)}</span
|
|
911
|
+
>
|
|
912
|
+
<span class="fw-dev-tooltip-weight"
|
|
913
|
+
>x${combo.scoreBreakdown.weights.routing ?? 0}</span
|
|
914
|
+
>
|
|
915
|
+
</div>
|
|
916
|
+
`
|
|
917
|
+
: nothing}
|
|
918
|
+
`
|
|
919
|
+
: html`
|
|
920
|
+
<div class="fw-dev-tooltip-error">
|
|
921
|
+
${combo.incompatibleReason || "Incompatible"}
|
|
922
|
+
</div>
|
|
923
|
+
`}
|
|
924
|
+
</div>
|
|
925
|
+
`
|
|
926
|
+
: nothing}
|
|
927
|
+
</div>
|
|
928
|
+
`;
|
|
213
929
|
})}
|
|
214
|
-
|
|
215
|
-
>
|
|
216
|
-
${mode}
|
|
217
|
-
</button>
|
|
218
|
-
`
|
|
219
|
-
)}
|
|
930
|
+
`}
|
|
220
931
|
</div>
|
|
221
932
|
</div>
|
|
222
|
-
<div class="actions fw-dev-actions">
|
|
223
|
-
<button
|
|
224
|
-
class="action-btn fw-dev-action-btn"
|
|
225
|
-
@click=${() => {
|
|
226
|
-
this.pc.clearError();
|
|
227
|
-
this.pc.reload();
|
|
228
|
-
}}
|
|
229
|
-
>
|
|
230
|
-
Reload
|
|
231
|
-
</button>
|
|
232
|
-
</div>
|
|
233
933
|
`;
|
|
234
934
|
}
|
|
235
935
|
|
|
236
|
-
|
|
237
|
-
const q = s.playbackQuality;
|
|
936
|
+
protected render() {
|
|
238
937
|
return html`
|
|
239
|
-
<div class="
|
|
240
|
-
<div class="
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
938
|
+
<div class="fw-dev-panel">
|
|
939
|
+
<div class="fw-dev-header">
|
|
940
|
+
<button
|
|
941
|
+
type="button"
|
|
942
|
+
class=${classMap({
|
|
943
|
+
"fw-dev-tab": true,
|
|
944
|
+
"fw-dev-tab--active": this._activeTab === "config",
|
|
945
|
+
})}
|
|
946
|
+
@click=${() => {
|
|
947
|
+
this._activeTab = "config";
|
|
948
|
+
}}
|
|
949
|
+
>
|
|
950
|
+
Config
|
|
951
|
+
</button>
|
|
952
|
+
<button
|
|
953
|
+
type="button"
|
|
954
|
+
class=${classMap({
|
|
955
|
+
"fw-dev-tab": true,
|
|
956
|
+
"fw-dev-tab--active": this._activeTab === "stats",
|
|
957
|
+
})}
|
|
958
|
+
@click=${() => {
|
|
959
|
+
this._activeTab = "stats";
|
|
960
|
+
}}
|
|
961
|
+
>
|
|
962
|
+
Stats
|
|
963
|
+
</button>
|
|
964
|
+
<div class="fw-dev-spacer"></div>
|
|
965
|
+
<button
|
|
966
|
+
type="button"
|
|
967
|
+
class="fw-dev-close"
|
|
968
|
+
aria-label="Close dev mode panel"
|
|
969
|
+
@click=${() =>
|
|
970
|
+
this.dispatchEvent(new CustomEvent("fw-close", { bubbles: true, composed: true }))}
|
|
971
|
+
>
|
|
972
|
+
${closeIcon()}
|
|
973
|
+
</button>
|
|
974
|
+
</div>
|
|
975
|
+
|
|
976
|
+
${this._activeTab === "config" ? this._renderConfigTab() : this._renderStatsTab()}
|
|
247
977
|
</div>
|
|
248
|
-
${q
|
|
249
|
-
? html`
|
|
250
|
-
<div class="section">
|
|
251
|
-
<div class="label">Quality</div>
|
|
252
|
-
${this._row("Resolution", this._resolution())}
|
|
253
|
-
${this._row("Bitrate", q.bitrate ? `${Math.round(q.bitrate / 1000)} kbps` : "—")}
|
|
254
|
-
${this._row("Latency", q.latency != null ? `${q.latency.toFixed(2)}s` : "—")}
|
|
255
|
-
${this._row(
|
|
256
|
-
"Buffer",
|
|
257
|
-
q.bufferedAhead != null ? `${q.bufferedAhead.toFixed(1)}s` : "—"
|
|
258
|
-
)}
|
|
259
|
-
${this._row("Score", q.score != null ? `${q.score.toFixed(0)}` : "—")}
|
|
260
|
-
${this._row("Drops", `${q.frameDropRate?.toFixed(1) ?? "0"}%`)}
|
|
261
|
-
${this._row("Stalls", `${q.stallCount ?? 0}`)}
|
|
262
|
-
</div>
|
|
263
|
-
`
|
|
264
|
-
: nothing}
|
|
265
978
|
`;
|
|
266
979
|
}
|
|
267
|
-
|
|
268
|
-
private _resolution(): string {
|
|
269
|
-
const video = this.pc.s.videoElement;
|
|
270
|
-
if (!video || !video.videoWidth || !video.videoHeight) return "—";
|
|
271
|
-
return `${video.videoWidth}×${video.videoHeight}`;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
private _row(label: string, value: string) {
|
|
275
|
-
return html`<div class="stat-row">
|
|
276
|
-
<span class="stat-label">${label}</span><span class="stat-value">${value}</span>
|
|
277
|
-
</div>`;
|
|
278
|
-
}
|
|
279
980
|
}
|
|
280
981
|
|
|
281
982
|
declare global {
|