@livepeer-frameworks/player-core 0.1.1 → 0.1.2

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.
Files changed (247) hide show
  1. package/dist/cjs/core/ABRController.js +456 -0
  2. package/dist/cjs/core/ABRController.js.map +1 -0
  3. package/dist/cjs/core/CodecUtils.js +195 -0
  4. package/dist/cjs/core/CodecUtils.js.map +1 -0
  5. package/dist/cjs/core/ErrorClassifier.js +410 -0
  6. package/dist/cjs/core/ErrorClassifier.js.map +1 -0
  7. package/dist/cjs/core/EventEmitter.js +108 -0
  8. package/dist/cjs/core/EventEmitter.js.map +1 -0
  9. package/dist/cjs/core/GatewayClient.js +342 -0
  10. package/dist/cjs/core/GatewayClient.js.map +1 -0
  11. package/dist/cjs/core/InteractionController.js +606 -0
  12. package/dist/cjs/core/InteractionController.js.map +1 -0
  13. package/dist/cjs/core/LiveDurationProxy.js +186 -0
  14. package/dist/cjs/core/LiveDurationProxy.js.map +1 -0
  15. package/dist/cjs/core/MetaTrackManager.js +624 -0
  16. package/dist/cjs/core/MetaTrackManager.js.map +1 -0
  17. package/dist/cjs/core/MistReporter.js +449 -0
  18. package/dist/cjs/core/MistReporter.js.map +1 -0
  19. package/dist/cjs/core/MistSignaling.js +264 -0
  20. package/dist/cjs/core/MistSignaling.js.map +1 -0
  21. package/dist/cjs/core/PlayerController.js +2658 -0
  22. package/dist/cjs/core/PlayerController.js.map +1 -0
  23. package/dist/cjs/core/PlayerInterface.js +269 -0
  24. package/dist/cjs/core/PlayerInterface.js.map +1 -0
  25. package/dist/cjs/core/PlayerManager.js +806 -0
  26. package/dist/cjs/core/PlayerManager.js.map +1 -0
  27. package/dist/cjs/core/PlayerRegistry.js +270 -0
  28. package/dist/cjs/core/PlayerRegistry.js.map +1 -0
  29. package/dist/cjs/core/QualityMonitor.js +474 -0
  30. package/dist/cjs/core/QualityMonitor.js.map +1 -0
  31. package/dist/cjs/core/SeekingUtils.js +292 -0
  32. package/dist/cjs/core/SeekingUtils.js.map +1 -0
  33. package/dist/cjs/core/StreamStateClient.js +381 -0
  34. package/dist/cjs/core/StreamStateClient.js.map +1 -0
  35. package/dist/cjs/core/SubtitleManager.js +227 -0
  36. package/dist/cjs/core/SubtitleManager.js.map +1 -0
  37. package/dist/cjs/core/TelemetryReporter.js +258 -0
  38. package/dist/cjs/core/TelemetryReporter.js.map +1 -0
  39. package/dist/cjs/core/TimeFormat.js +176 -0
  40. package/dist/cjs/core/TimeFormat.js.map +1 -0
  41. package/dist/cjs/core/TimerManager.js +176 -0
  42. package/dist/cjs/core/TimerManager.js.map +1 -0
  43. package/dist/cjs/core/UrlUtils.js +160 -0
  44. package/dist/cjs/core/UrlUtils.js.map +1 -0
  45. package/dist/cjs/core/detector.js +293 -0
  46. package/dist/cjs/core/detector.js.map +1 -0
  47. package/dist/cjs/core/scorer.js +443 -0
  48. package/dist/cjs/core/scorer.js.map +1 -0
  49. package/dist/cjs/index.js +121 -20134
  50. package/dist/cjs/index.js.map +1 -1
  51. package/dist/cjs/lib/utils.js +11 -0
  52. package/dist/cjs/lib/utils.js.map +1 -0
  53. package/dist/cjs/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js +6 -0
  54. package/dist/cjs/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +1 -0
  55. package/dist/cjs/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js +3042 -0
  56. package/dist/cjs/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  57. package/dist/cjs/players/DashJsPlayer.js +638 -0
  58. package/dist/cjs/players/DashJsPlayer.js.map +1 -0
  59. package/dist/cjs/players/HlsJsPlayer.js +482 -0
  60. package/dist/cjs/players/HlsJsPlayer.js.map +1 -0
  61. package/dist/cjs/players/MewsWsPlayer/SourceBufferManager.js +522 -0
  62. package/dist/cjs/players/MewsWsPlayer/SourceBufferManager.js.map +1 -0
  63. package/dist/cjs/players/MewsWsPlayer/WebSocketManager.js +215 -0
  64. package/dist/cjs/players/MewsWsPlayer/WebSocketManager.js.map +1 -0
  65. package/dist/cjs/players/MewsWsPlayer/index.js +987 -0
  66. package/dist/cjs/players/MewsWsPlayer/index.js.map +1 -0
  67. package/dist/cjs/players/MistPlayer.js +185 -0
  68. package/dist/cjs/players/MistPlayer.js.map +1 -0
  69. package/dist/cjs/players/MistWebRTCPlayer/index.js +635 -0
  70. package/dist/cjs/players/MistWebRTCPlayer/index.js.map +1 -0
  71. package/dist/cjs/players/NativePlayer.js +762 -0
  72. package/dist/cjs/players/NativePlayer.js.map +1 -0
  73. package/dist/cjs/players/VideoJsPlayer.js +585 -0
  74. package/dist/cjs/players/VideoJsPlayer.js.map +1 -0
  75. package/dist/cjs/players/WebCodecsPlayer/JitterBuffer.js +236 -0
  76. package/dist/cjs/players/WebCodecsPlayer/JitterBuffer.js.map +1 -0
  77. package/dist/cjs/players/WebCodecsPlayer/LatencyProfiles.js +143 -0
  78. package/dist/cjs/players/WebCodecsPlayer/LatencyProfiles.js.map +1 -0
  79. package/dist/cjs/players/WebCodecsPlayer/RawChunkParser.js +96 -0
  80. package/dist/cjs/players/WebCodecsPlayer/RawChunkParser.js.map +1 -0
  81. package/dist/cjs/players/WebCodecsPlayer/SyncController.js +359 -0
  82. package/dist/cjs/players/WebCodecsPlayer/SyncController.js.map +1 -0
  83. package/dist/cjs/players/WebCodecsPlayer/WebSocketController.js +460 -0
  84. package/dist/cjs/players/WebCodecsPlayer/WebSocketController.js.map +1 -0
  85. package/dist/cjs/players/WebCodecsPlayer/index.js +1467 -0
  86. package/dist/cjs/players/WebCodecsPlayer/index.js.map +1 -0
  87. package/dist/cjs/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js +320 -0
  88. package/dist/cjs/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js.map +1 -0
  89. package/dist/cjs/styles/index.js +57 -0
  90. package/dist/cjs/styles/index.js.map +1 -0
  91. package/dist/cjs/vanilla/FrameWorksPlayer.js +269 -0
  92. package/dist/cjs/vanilla/FrameWorksPlayer.js.map +1 -0
  93. package/dist/cjs/vanilla.js +11 -0
  94. package/dist/cjs/vanilla.js.map +1 -0
  95. package/dist/esm/core/ABRController.js +454 -0
  96. package/dist/esm/core/ABRController.js.map +1 -0
  97. package/dist/esm/core/CodecUtils.js +193 -0
  98. package/dist/esm/core/CodecUtils.js.map +1 -0
  99. package/dist/esm/core/ErrorClassifier.js +408 -0
  100. package/dist/esm/core/ErrorClassifier.js.map +1 -0
  101. package/dist/esm/core/EventEmitter.js +106 -0
  102. package/dist/esm/core/EventEmitter.js.map +1 -0
  103. package/dist/esm/core/GatewayClient.js +340 -0
  104. package/dist/esm/core/GatewayClient.js.map +1 -0
  105. package/dist/esm/core/InteractionController.js +604 -0
  106. package/dist/esm/core/InteractionController.js.map +1 -0
  107. package/dist/esm/core/LiveDurationProxy.js +184 -0
  108. package/dist/esm/core/LiveDurationProxy.js.map +1 -0
  109. package/dist/esm/core/MetaTrackManager.js +622 -0
  110. package/dist/esm/core/MetaTrackManager.js.map +1 -0
  111. package/dist/esm/core/MistReporter.js +447 -0
  112. package/dist/esm/core/MistReporter.js.map +1 -0
  113. package/dist/esm/core/MistSignaling.js +262 -0
  114. package/dist/esm/core/MistSignaling.js.map +1 -0
  115. package/dist/esm/core/PlayerController.js +2651 -0
  116. package/dist/esm/core/PlayerController.js.map +1 -0
  117. package/dist/esm/core/PlayerInterface.js +267 -0
  118. package/dist/esm/core/PlayerInterface.js.map +1 -0
  119. package/dist/esm/core/PlayerManager.js +804 -0
  120. package/dist/esm/core/PlayerManager.js.map +1 -0
  121. package/dist/esm/core/PlayerRegistry.js +264 -0
  122. package/dist/esm/core/PlayerRegistry.js.map +1 -0
  123. package/dist/esm/core/QualityMonitor.js +471 -0
  124. package/dist/esm/core/QualityMonitor.js.map +1 -0
  125. package/dist/esm/core/SeekingUtils.js +280 -0
  126. package/dist/esm/core/SeekingUtils.js.map +1 -0
  127. package/dist/esm/core/StreamStateClient.js +379 -0
  128. package/dist/esm/core/StreamStateClient.js.map +1 -0
  129. package/dist/esm/core/SubtitleManager.js +225 -0
  130. package/dist/esm/core/SubtitleManager.js.map +1 -0
  131. package/dist/esm/core/TelemetryReporter.js +256 -0
  132. package/dist/esm/core/TelemetryReporter.js.map +1 -0
  133. package/dist/esm/core/TimeFormat.js +169 -0
  134. package/dist/esm/core/TimeFormat.js.map +1 -0
  135. package/dist/esm/core/TimerManager.js +174 -0
  136. package/dist/esm/core/TimerManager.js.map +1 -0
  137. package/dist/esm/core/UrlUtils.js +151 -0
  138. package/dist/esm/core/UrlUtils.js.map +1 -0
  139. package/dist/esm/core/detector.js +279 -0
  140. package/dist/esm/core/detector.js.map +1 -0
  141. package/dist/esm/core/scorer.js +422 -0
  142. package/dist/esm/core/scorer.js.map +1 -0
  143. package/dist/esm/index.js +26 -20043
  144. package/dist/esm/index.js.map +1 -1
  145. package/dist/esm/lib/utils.js +9 -0
  146. package/dist/esm/lib/utils.js.map +1 -0
  147. package/dist/esm/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js +4 -0
  148. package/dist/esm/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +1 -0
  149. package/dist/esm/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js +3036 -0
  150. package/dist/esm/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  151. package/dist/esm/players/DashJsPlayer.js +636 -0
  152. package/dist/esm/players/DashJsPlayer.js.map +1 -0
  153. package/dist/esm/players/HlsJsPlayer.js +480 -0
  154. package/dist/esm/players/HlsJsPlayer.js.map +1 -0
  155. package/dist/esm/players/MewsWsPlayer/SourceBufferManager.js +520 -0
  156. package/dist/esm/players/MewsWsPlayer/SourceBufferManager.js.map +1 -0
  157. package/dist/esm/players/MewsWsPlayer/WebSocketManager.js +213 -0
  158. package/dist/esm/players/MewsWsPlayer/WebSocketManager.js.map +1 -0
  159. package/dist/esm/players/MewsWsPlayer/index.js +985 -0
  160. package/dist/esm/players/MewsWsPlayer/index.js.map +1 -0
  161. package/dist/esm/players/MistPlayer.js +183 -0
  162. package/dist/esm/players/MistPlayer.js.map +1 -0
  163. package/dist/esm/players/MistWebRTCPlayer/index.js +633 -0
  164. package/dist/esm/players/MistWebRTCPlayer/index.js.map +1 -0
  165. package/dist/esm/players/NativePlayer.js +759 -0
  166. package/dist/esm/players/NativePlayer.js.map +1 -0
  167. package/dist/esm/players/VideoJsPlayer.js +583 -0
  168. package/dist/esm/players/VideoJsPlayer.js.map +1 -0
  169. package/dist/esm/players/WebCodecsPlayer/JitterBuffer.js +233 -0
  170. package/dist/esm/players/WebCodecsPlayer/JitterBuffer.js.map +1 -0
  171. package/dist/esm/players/WebCodecsPlayer/LatencyProfiles.js +134 -0
  172. package/dist/esm/players/WebCodecsPlayer/LatencyProfiles.js.map +1 -0
  173. package/dist/esm/players/WebCodecsPlayer/RawChunkParser.js +91 -0
  174. package/dist/esm/players/WebCodecsPlayer/RawChunkParser.js.map +1 -0
  175. package/dist/esm/players/WebCodecsPlayer/SyncController.js +357 -0
  176. package/dist/esm/players/WebCodecsPlayer/SyncController.js.map +1 -0
  177. package/dist/esm/players/WebCodecsPlayer/WebSocketController.js +458 -0
  178. package/dist/esm/players/WebCodecsPlayer/WebSocketController.js.map +1 -0
  179. package/dist/esm/players/WebCodecsPlayer/index.js +1458 -0
  180. package/dist/esm/players/WebCodecsPlayer/index.js.map +1 -0
  181. package/dist/esm/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js +315 -0
  182. package/dist/esm/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js.map +1 -0
  183. package/dist/esm/styles/index.js +54 -0
  184. package/dist/esm/styles/index.js.map +1 -0
  185. package/dist/esm/vanilla/FrameWorksPlayer.js +264 -0
  186. package/dist/esm/vanilla/FrameWorksPlayer.js.map +1 -0
  187. package/dist/esm/vanilla.js +2 -0
  188. package/dist/esm/vanilla.js.map +1 -0
  189. package/dist/player.css +4 -1
  190. package/dist/types/core/ABRController.d.ts +4 -4
  191. package/dist/types/core/CodecUtils.d.ts +1 -1
  192. package/dist/types/core/ErrorClassifier.d.ts +77 -0
  193. package/dist/types/core/GatewayClient.d.ts +4 -4
  194. package/dist/types/core/MetaTrackManager.d.ts +2 -2
  195. package/dist/types/core/MistReporter.d.ts +3 -3
  196. package/dist/types/core/MistSignaling.d.ts +12 -12
  197. package/dist/types/core/PlayerController.d.ts +19 -14
  198. package/dist/types/core/PlayerInterface.d.ts +100 -2
  199. package/dist/types/core/PlayerManager.d.ts +36 -9
  200. package/dist/types/core/PlayerRegistry.d.ts +11 -11
  201. package/dist/types/core/QualityMonitor.d.ts +2 -2
  202. package/dist/types/core/SeekingUtils.d.ts +2 -2
  203. package/dist/types/core/StreamStateClient.d.ts +2 -2
  204. package/dist/types/core/TelemetryReporter.d.ts +1 -1
  205. package/dist/types/core/TimerManager.d.ts +1 -1
  206. package/dist/types/core/detector.d.ts +1 -1
  207. package/dist/types/core/index.d.ts +44 -44
  208. package/dist/types/core/scorer.d.ts +1 -1
  209. package/dist/types/core/selector.d.ts +2 -2
  210. package/dist/types/index.d.ts +35 -34
  211. package/dist/types/players/DashJsPlayer.d.ts +3 -3
  212. package/dist/types/players/HlsJsPlayer.d.ts +3 -3
  213. package/dist/types/players/MewsWsPlayer/SourceBufferManager.d.ts +1 -1
  214. package/dist/types/players/MewsWsPlayer/WebSocketManager.d.ts +1 -1
  215. package/dist/types/players/MewsWsPlayer/index.d.ts +2 -2
  216. package/dist/types/players/MewsWsPlayer/types.d.ts +15 -15
  217. package/dist/types/players/MistPlayer.d.ts +2 -2
  218. package/dist/types/players/MistWebRTCPlayer/index.d.ts +3 -3
  219. package/dist/types/players/NativePlayer.d.ts +3 -3
  220. package/dist/types/players/VideoJsPlayer.d.ts +3 -3
  221. package/dist/types/players/WebCodecsPlayer/JitterBuffer.d.ts +3 -3
  222. package/dist/types/players/WebCodecsPlayer/LatencyProfiles.d.ts +1 -1
  223. package/dist/types/players/WebCodecsPlayer/RawChunkParser.d.ts +2 -2
  224. package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +2 -2
  225. package/dist/types/players/WebCodecsPlayer/WebSocketController.d.ts +3 -3
  226. package/dist/types/players/WebCodecsPlayer/index.d.ts +9 -9
  227. package/dist/types/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.d.ts +1 -1
  228. package/dist/types/players/WebCodecsPlayer/types.d.ts +49 -49
  229. package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +31 -31
  230. package/dist/types/players/index.d.ts +5 -8
  231. package/dist/types/types.d.ts +15 -15
  232. package/dist/types/vanilla/FrameWorksPlayer.d.ts +2 -2
  233. package/dist/types/vanilla/index.d.ts +4 -4
  234. package/dist/workers/decoder.worker.js +129 -122
  235. package/dist/workers/decoder.worker.js.map +1 -1
  236. package/package.json +31 -15
  237. package/src/core/ErrorClassifier.ts +499 -0
  238. package/src/core/PlayerController.ts +17 -2
  239. package/src/core/PlayerInterface.ts +109 -0
  240. package/src/core/PlayerManager.ts +290 -46
  241. package/src/core/PlayerRegistry.ts +221 -87
  242. package/src/core/TelemetryReporter.ts +4 -1
  243. package/src/index.ts +13 -4
  244. package/src/players/WebCodecsPlayer/index.ts +2 -2
  245. package/src/players/index.ts +5 -16
  246. package/src/styles/player.css +4 -1
  247. package/src/vanilla/FrameWorksPlayer.ts +2 -5
@@ -0,0 +1,471 @@
1
+ import { TimerManager } from './TimerManager.js';
2
+
3
+ /**
4
+ * Default quality thresholds
5
+ */
6
+ const DEFAULT_THRESHOLDS = {
7
+ minScore: 60,
8
+ maxStalls: 3,
9
+ minBuffer: 2,
10
+ };
11
+ /**
12
+ * Rolling average window size
13
+ */
14
+ const ROLLING_WINDOW_SIZE = 20;
15
+ /** Protocol-specific playback score thresholds (MistMetaPlayer reference) */
16
+ const PROTOCOL_THRESHOLDS = {
17
+ webrtc: 0.95, // Very strict for low-latency
18
+ hls: 0.75, // More lenient for adaptive streaming
19
+ dash: 0.75, // More lenient for adaptive streaming
20
+ html5: 0.75, // Standard threshold
21
+ unknown: 0.75, // Default
22
+ };
23
+ /**
24
+ * QualityMonitor - Tracks playback quality metrics
25
+ *
26
+ * Monitors:
27
+ * - Buffer health (seconds ahead)
28
+ * - Stall count (waiting events)
29
+ * - Frame drop rate (via video.getVideoPlaybackQuality())
30
+ * - Estimated bitrate
31
+ * - Latency (for live streams)
32
+ *
33
+ * Calculates a composite quality score (0-100) and triggers
34
+ * callbacks when quality degrades below thresholds.
35
+ */
36
+ class QualityMonitor {
37
+ constructor(options = {}) {
38
+ this.videoElement = null;
39
+ this.timers = new TimerManager();
40
+ this.stallCount = 0;
41
+ this.lastStallTime = 0;
42
+ this.totalStallMs = 0;
43
+ this.history = [];
44
+ this.lastBytesLoaded = 0;
45
+ this.lastBytesTime = 0;
46
+ this.listeners = [];
47
+ // MistPlayer-style playback score (0-2.0 scale)
48
+ this.playbackScoreHistory = [];
49
+ this.playbackScore = 1.0;
50
+ this.PLAYBACK_SCORE_AVERAGING_STEPS = 10;
51
+ // Automatic fallback tracking
52
+ // Reference: player.js:654-665 - triggers "nextCombo" after sustained poor quality
53
+ this.consecutivePoorSamples = 0;
54
+ this.fallbackTriggered = false;
55
+ this.options = {
56
+ sampleInterval: options.sampleInterval ?? 500,
57
+ thresholds: options.thresholds ?? {},
58
+ onQualityDegraded: options.onQualityDegraded ?? (() => { }),
59
+ onSample: options.onSample ?? (() => { }),
60
+ protocol: options.protocol ?? "unknown",
61
+ playbackScoreThreshold: options.playbackScoreThreshold ?? null,
62
+ onFallbackRequest: options.onFallbackRequest ?? (() => { }),
63
+ poorSamplesBeforeFallback: options.poorSamplesBeforeFallback ?? 5,
64
+ };
65
+ this.thresholds = { ...DEFAULT_THRESHOLDS, ...options.thresholds };
66
+ }
67
+ /**
68
+ * Set the current player protocol for threshold selection
69
+ */
70
+ setProtocol(protocol) {
71
+ this.options.protocol = protocol;
72
+ }
73
+ /**
74
+ * Get the current player protocol
75
+ */
76
+ getProtocol() {
77
+ return this.options.protocol;
78
+ }
79
+ /**
80
+ * Get the playback score threshold for the current protocol
81
+ */
82
+ getPlaybackScoreThreshold() {
83
+ // Custom threshold takes precedence
84
+ if (this.options.playbackScoreThreshold !== null) {
85
+ return this.options.playbackScoreThreshold;
86
+ }
87
+ return PROTOCOL_THRESHOLDS[this.options.protocol];
88
+ }
89
+ /**
90
+ * Set a custom playback score threshold (overrides protocol default)
91
+ */
92
+ setPlaybackScoreThreshold(threshold) {
93
+ this.options.playbackScoreThreshold = threshold;
94
+ }
95
+ /**
96
+ * Start monitoring a video element
97
+ */
98
+ start(videoElement) {
99
+ this.stop();
100
+ this.videoElement = videoElement;
101
+ this.stallCount = 0;
102
+ this.totalStallMs = 0;
103
+ this.lastStallTime = 0;
104
+ this.history = [];
105
+ this.lastBytesLoaded = 0;
106
+ this.lastBytesTime = 0;
107
+ this.consecutivePoorSamples = 0;
108
+ this.fallbackTriggered = false;
109
+ this.playbackScoreHistory = [];
110
+ this.playbackScore = 1.0;
111
+ // Listen for stall events
112
+ const onWaiting = () => {
113
+ this.stallCount++;
114
+ this.lastStallTime = performance.now();
115
+ };
116
+ const onPlaying = () => {
117
+ if (this.lastStallTime > 0) {
118
+ this.totalStallMs += performance.now() - this.lastStallTime;
119
+ this.lastStallTime = 0;
120
+ }
121
+ };
122
+ const onCanPlay = () => {
123
+ if (this.lastStallTime > 0) {
124
+ this.totalStallMs += performance.now() - this.lastStallTime;
125
+ this.lastStallTime = 0;
126
+ }
127
+ };
128
+ videoElement.addEventListener("waiting", onWaiting);
129
+ videoElement.addEventListener("playing", onPlaying);
130
+ videoElement.addEventListener("canplay", onCanPlay);
131
+ this.listeners = [
132
+ () => videoElement.removeEventListener("waiting", onWaiting),
133
+ () => videoElement.removeEventListener("playing", onPlaying),
134
+ () => videoElement.removeEventListener("canplay", onCanPlay),
135
+ ];
136
+ // Start sampling interval
137
+ this.timers.startInterval(() => this.sample(), this.options.sampleInterval, "sampling");
138
+ // Take initial sample
139
+ this.sample();
140
+ }
141
+ /**
142
+ * Stop monitoring
143
+ */
144
+ stop() {
145
+ this.timers.destroy();
146
+ this.listeners.forEach((cleanup) => cleanup());
147
+ this.listeners = [];
148
+ this.videoElement = null;
149
+ }
150
+ /**
151
+ * Take a quality sample
152
+ */
153
+ sample() {
154
+ const video = this.videoElement;
155
+ if (!video)
156
+ return;
157
+ // Update MistPlayer-style playback score
158
+ this.updatePlaybackScore();
159
+ const quality = this.calculateQuality(video);
160
+ this.history.push(quality);
161
+ // Keep rolling window
162
+ if (this.history.length > ROLLING_WINDOW_SIZE) {
163
+ this.history.shift();
164
+ }
165
+ // Notify listeners
166
+ this.options.onSample(quality);
167
+ // Check for quality degradation
168
+ if (quality.score < this.thresholds.minScore ||
169
+ quality.stallCount > this.thresholds.maxStalls ||
170
+ quality.bufferedAhead < this.thresholds.minBuffer) {
171
+ this.options.onQualityDegraded(quality);
172
+ }
173
+ // Track sustained poor quality for automatic fallback
174
+ // Reference: player.js:654-665 - "nextCombo" after sustained poor playback
175
+ if (this.isPlaybackPoor()) {
176
+ this.consecutivePoorSamples++;
177
+ // Trigger fallback after sustained poor quality
178
+ // Only trigger once until quality improves or reset
179
+ if (!this.fallbackTriggered &&
180
+ this.consecutivePoorSamples >= this.options.poorSamplesBeforeFallback) {
181
+ this.fallbackTriggered = true;
182
+ console.warn(`[QualityMonitor] Poor playback detected: ${Math.round(this.playbackScore * 100)}% ` +
183
+ `(threshold: ${Math.round(this.getPlaybackScoreThreshold() * 100)}%, ` +
184
+ `protocol: ${this.options.protocol})`);
185
+ this.options.onFallbackRequest({
186
+ score: this.playbackScore,
187
+ consecutivePoorSamples: this.consecutivePoorSamples,
188
+ });
189
+ }
190
+ }
191
+ else {
192
+ // Quality recovered - reset counters
193
+ this.consecutivePoorSamples = 0;
194
+ this.fallbackTriggered = false;
195
+ }
196
+ }
197
+ /**
198
+ * Calculate current quality metrics
199
+ */
200
+ calculateQuality(video) {
201
+ const now = Date.now();
202
+ // Calculate buffered ahead
203
+ let bufferedAhead = 0;
204
+ if (video.buffered.length > 0) {
205
+ for (let i = 0; i < video.buffered.length; i++) {
206
+ if (video.buffered.start(i) <= video.currentTime &&
207
+ video.buffered.end(i) > video.currentTime) {
208
+ bufferedAhead = video.buffered.end(i) - video.currentTime;
209
+ break;
210
+ }
211
+ }
212
+ }
213
+ // Get frame stats if available
214
+ let framesDecoded = 0;
215
+ let framesDropped = 0;
216
+ let frameDropRate = 0;
217
+ if ("getVideoPlaybackQuality" in video) {
218
+ const stats = video.getVideoPlaybackQuality();
219
+ framesDecoded = stats.totalVideoFrames;
220
+ framesDropped = stats.droppedVideoFrames;
221
+ frameDropRate = framesDecoded > 0 ? (framesDropped / framesDecoded) * 100 : 0;
222
+ }
223
+ // Estimate bitrate from buffer loading
224
+ let bitrate = 0;
225
+ if (video.buffered.length > 0 && this.lastBytesTime > 0) {
226
+ const timeElapsed = (now - this.lastBytesTime) / 1000;
227
+ if (timeElapsed > 0) {
228
+ // Estimate from buffer growth
229
+ // This is a rough approximation - real bitrate tracking would use MSE
230
+ const bufferEnd = video.buffered.end(video.buffered.length - 1);
231
+ const bufferDuration = bufferEnd - video.currentTime;
232
+ // Assume average bitrate based on buffer size
233
+ bitrate = bufferDuration > 0 ? Math.round((bufferDuration * 1000000) / timeElapsed) : 0;
234
+ }
235
+ }
236
+ this.lastBytesTime = now;
237
+ // Calculate latency for live streams
238
+ let latency = 0;
239
+ if (video.duration === Infinity || !isFinite(video.duration)) {
240
+ // Live stream - estimate latency from buffer
241
+ if (video.buffered.length > 0) {
242
+ const liveEdge = video.buffered.end(video.buffered.length - 1);
243
+ latency = (liveEdge - video.currentTime) * 1000;
244
+ }
245
+ }
246
+ // Calculate composite quality score (0-100) with duration-weighted stalls
247
+ const score = this.calculateScore({
248
+ bufferedAhead,
249
+ stallCount: this.stallCount,
250
+ stallDurationMs: this.totalStallMs,
251
+ frameDropRate,
252
+ latency,
253
+ });
254
+ return {
255
+ score,
256
+ bitrate,
257
+ bufferedAhead,
258
+ stallCount: this.stallCount,
259
+ frameDropRate,
260
+ latency,
261
+ timestamp: now,
262
+ };
263
+ }
264
+ /**
265
+ * Calculate composite quality score
266
+ *
267
+ * D4: Duration-weighted stall tracking - stall penalty considers both
268
+ * count AND duration. 10x 0.1s stalls (1s total) weighs less than 1x 1s stall.
269
+ */
270
+ calculateScore(metrics) {
271
+ let score = 100;
272
+ // Buffer penalty (max -40 points)
273
+ if (metrics.bufferedAhead < this.thresholds.minBuffer) {
274
+ const bufferPenalty = Math.min(40, (this.thresholds.minBuffer - metrics.bufferedAhead) * 20);
275
+ score -= bufferPenalty;
276
+ }
277
+ // D4: Duration-weighted stall penalty (max -30 points)
278
+ // Base: 5 points per stall + 2 points per second of total stall time
279
+ // This weights duration: 1x 2s stall = 5 + 4 = 9 points
280
+ // 10x 0.2s stalls = 50 + 4 = 54 points (capped at 30)
281
+ // So many short stalls are penalized more than few long stalls of same duration
282
+ const countPenalty = metrics.stallCount * 5;
283
+ const durationPenalty = (metrics.stallDurationMs / 1000) * 2;
284
+ const stallPenalty = Math.min(30, countPenalty + durationPenalty);
285
+ score -= stallPenalty;
286
+ // Frame drop penalty (max -20 points)
287
+ const framePenalty = Math.min(20, metrics.frameDropRate * 2);
288
+ score -= framePenalty;
289
+ // Latency penalty for live streams (max -10 points)
290
+ if (metrics.latency > 5000) {
291
+ const latencyPenalty = Math.min(10, (metrics.latency - 5000) / 1000);
292
+ score -= latencyPenalty;
293
+ }
294
+ return Math.max(0, Math.round(score));
295
+ }
296
+ /**
297
+ * Get current quality metrics
298
+ */
299
+ getCurrentQuality() {
300
+ return this.history.length > 0 ? this.history[this.history.length - 1] : null;
301
+ }
302
+ /**
303
+ * Get rolling average quality
304
+ */
305
+ getAverageQuality() {
306
+ if (this.history.length === 0)
307
+ return null;
308
+ const avg = {
309
+ score: 0,
310
+ bitrate: 0,
311
+ bufferedAhead: 0,
312
+ stallCount: this.stallCount,
313
+ frameDropRate: 0,
314
+ latency: 0,
315
+ timestamp: Date.now(),
316
+ };
317
+ for (const q of this.history) {
318
+ avg.score += q.score;
319
+ avg.bitrate += q.bitrate;
320
+ avg.bufferedAhead += q.bufferedAhead;
321
+ avg.frameDropRate += q.frameDropRate;
322
+ avg.latency += q.latency;
323
+ }
324
+ const len = this.history.length;
325
+ avg.score = Math.round(avg.score / len);
326
+ avg.bitrate = Math.round(avg.bitrate / len);
327
+ avg.bufferedAhead = avg.bufferedAhead / len;
328
+ avg.frameDropRate = avg.frameDropRate / len;
329
+ avg.latency = avg.latency / len;
330
+ return avg;
331
+ }
332
+ /**
333
+ * Get quality history
334
+ */
335
+ getHistory() {
336
+ return [...this.history];
337
+ }
338
+ /**
339
+ * Reset stall counters
340
+ */
341
+ resetStallCounters() {
342
+ this.stallCount = 0;
343
+ this.totalStallMs = 0;
344
+ }
345
+ /**
346
+ * Get total stall time in ms
347
+ */
348
+ getTotalStallMs() {
349
+ return this.totalStallMs;
350
+ }
351
+ /**
352
+ * Check if currently monitoring
353
+ */
354
+ isMonitoring() {
355
+ return this.videoElement !== null && this.timers.activeCount > 0;
356
+ }
357
+ // ========================================
358
+ // MistPlayer-style Playback Score (0-2.0)
359
+ // ========================================
360
+ /**
361
+ * Calculate playback score entry value
362
+ * Compares video time progress vs wall clock time
363
+ */
364
+ getPlaybackScoreValue() {
365
+ const video = this.videoElement;
366
+ const clock = performance.now() / 1000;
367
+ const videoTime = video?.currentTime ?? 0;
368
+ const result = {
369
+ clock,
370
+ video: videoTime,
371
+ score: 1.0,
372
+ };
373
+ if (this.playbackScoreHistory.length > 0) {
374
+ const prev = this.playbackScoreHistory[this.playbackScoreHistory.length - 1];
375
+ result.score = this.calculatePlaybackScoreFromEntries(prev, result);
376
+ }
377
+ return result;
378
+ }
379
+ /**
380
+ * Calculate score between two entries
381
+ * Returns 1.0 for normal playback, >1.0 if faster, <1.0 if stalled, <0 if backwards
382
+ */
383
+ calculatePlaybackScoreFromEntries(a, b) {
384
+ const video = this.videoElement;
385
+ let rate = 1;
386
+ if (video) {
387
+ rate = video.playbackRate || 1;
388
+ }
389
+ const clockDelta = b.clock - a.clock;
390
+ const videoDelta = b.video - a.video;
391
+ if (clockDelta <= 0)
392
+ return 1.0;
393
+ return videoDelta / clockDelta / rate;
394
+ }
395
+ /**
396
+ * Calculate and update the playback score
397
+ * Like MistPlayer's calcScore function
398
+ */
399
+ updatePlaybackScore() {
400
+ const entry = this.getPlaybackScoreValue();
401
+ this.playbackScoreHistory.push(entry);
402
+ if (this.playbackScoreHistory.length <= 1) {
403
+ return 1.0;
404
+ }
405
+ // Calculate score from oldest to newest
406
+ const first = this.playbackScoreHistory[0];
407
+ const last = this.playbackScoreHistory[this.playbackScoreHistory.length - 1];
408
+ let score = this.calculatePlaybackScoreFromEntries(first, last);
409
+ // Trim history
410
+ if (this.playbackScoreHistory.length > this.PLAYBACK_SCORE_AVERAGING_STEPS) {
411
+ this.playbackScoreHistory.shift();
412
+ }
413
+ // Final score is max of averaged and current
414
+ score = Math.max(score, entry.score);
415
+ this.playbackScore = score;
416
+ return score;
417
+ }
418
+ /**
419
+ * Get current playback score (MistPlayer-style 0-2.0 scale)
420
+ *
421
+ * - 1.0 = normal playback (video progresses at expected rate)
422
+ * - > 1.0 = faster than expected (catching up)
423
+ * - < 1.0 = slower than expected (stalling/buffering)
424
+ * - < 0 = video went backwards
425
+ *
426
+ * Threshold recommendations:
427
+ * - WebRTC: warn below 0.95
428
+ * - HLS/DASH: warn below 0.75
429
+ */
430
+ getPlaybackScore() {
431
+ return this.playbackScore;
432
+ }
433
+ /**
434
+ * Check if playback quality is poor based on score
435
+ * Uses protocol-specific thresholds (MistPlayer-style)
436
+ * WebRTC: 0.95 (strict), HLS/DASH/HTML5: 0.75 (lenient)
437
+ */
438
+ isPlaybackPoor() {
439
+ return this.playbackScore < this.getPlaybackScoreThreshold();
440
+ }
441
+ /**
442
+ * Reset playback score tracking
443
+ */
444
+ resetPlaybackScore() {
445
+ this.playbackScoreHistory = [];
446
+ this.playbackScore = 1.0;
447
+ }
448
+ /**
449
+ * Reset fallback state
450
+ * Call after a player switch to allow fallback to trigger again
451
+ */
452
+ resetFallbackState() {
453
+ this.consecutivePoorSamples = 0;
454
+ this.fallbackTriggered = false;
455
+ }
456
+ /**
457
+ * Get consecutive poor sample count (for debugging)
458
+ */
459
+ getConsecutivePoorSamples() {
460
+ return this.consecutivePoorSamples;
461
+ }
462
+ /**
463
+ * Check if fallback has been triggered (for debugging)
464
+ */
465
+ hasFallbackTriggered() {
466
+ return this.fallbackTriggered;
467
+ }
468
+ }
469
+
470
+ export { PROTOCOL_THRESHOLDS, QualityMonitor };
471
+ //# sourceMappingURL=QualityMonitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QualityMonitor.js","sources":["../../../../src/core/QualityMonitor.ts"],"sourcesContent":["import type { PlaybackQuality, QualityThresholds } from \"../types\";\nimport { TimerManager } from \"./TimerManager\";\n\n/**\n * Default quality thresholds\n */\nconst DEFAULT_THRESHOLDS: QualityThresholds = {\n minScore: 60,\n maxStalls: 3,\n minBuffer: 2,\n};\n\n/**\n * Rolling average window size\n */\nconst ROLLING_WINDOW_SIZE = 20;\n\n/**\n * Playback score history entry (for MistPlayer-style 0-2.0 score)\n */\ninterface PlaybackScoreEntry {\n clock: number; // Wall clock time in seconds\n video: number; // Video currentTime\n score: number; // Calculated score for this sample\n}\n\n/** Protocol type for threshold selection */\nexport type PlayerProtocol = \"webrtc\" | \"hls\" | \"dash\" | \"html5\" | \"unknown\";\n\n/** Protocol-specific playback score thresholds (MistMetaPlayer reference) */\nexport const PROTOCOL_THRESHOLDS: Record<PlayerProtocol, number> = {\n webrtc: 0.95, // Very strict for low-latency\n hls: 0.75, // More lenient for adaptive streaming\n dash: 0.75, // More lenient for adaptive streaming\n html5: 0.75, // Standard threshold\n unknown: 0.75, // Default\n};\n\nexport interface QualityMonitorOptions {\n /** Sample interval in ms */\n sampleInterval?: number;\n /** Quality thresholds */\n thresholds?: Partial<QualityThresholds>;\n /** Callback when quality degrades */\n onQualityDegraded?: (quality: PlaybackQuality) => void;\n /** Callback on every sample */\n onSample?: (quality: PlaybackQuality) => void;\n /** Current player protocol for threshold selection */\n protocol?: PlayerProtocol;\n /** Custom playback score threshold (overrides protocol default) */\n playbackScoreThreshold?: number;\n /**\n * Callback when sustained poor quality triggers a fallback request\n * Reference: player.js:654-665 - \"nextCombo\" action\n */\n onFallbackRequest?: (reason: { score: number; consecutivePoorSamples: number }) => void;\n /**\n * Number of consecutive poor samples before requesting fallback\n * Default: 5 (2.5 seconds at 500ms sample interval)\n */\n poorSamplesBeforeFallback?: number;\n}\n\nexport interface QualityMonitorState {\n isMonitoring: boolean;\n quality: PlaybackQuality | null;\n history: PlaybackQuality[];\n}\n\n/**\n * QualityMonitor - Tracks playback quality metrics\n *\n * Monitors:\n * - Buffer health (seconds ahead)\n * - Stall count (waiting events)\n * - Frame drop rate (via video.getVideoPlaybackQuality())\n * - Estimated bitrate\n * - Latency (for live streams)\n *\n * Calculates a composite quality score (0-100) and triggers\n * callbacks when quality degrades below thresholds.\n */\nexport class QualityMonitor {\n private videoElement: HTMLVideoElement | null = null;\n private options: Required<Omit<QualityMonitorOptions, \"protocol\" | \"playbackScoreThreshold\">> & {\n protocol: PlayerProtocol;\n playbackScoreThreshold: number | null;\n };\n private thresholds: QualityThresholds;\n private timers = new TimerManager();\n private stallCount = 0;\n private lastStallTime = 0;\n private totalStallMs = 0;\n private history: PlaybackQuality[] = [];\n private lastBytesLoaded = 0;\n private lastBytesTime = 0;\n private listeners: Array<() => void> = [];\n\n // MistPlayer-style playback score (0-2.0 scale)\n private playbackScoreHistory: PlaybackScoreEntry[] = [];\n private playbackScore = 1.0;\n private readonly PLAYBACK_SCORE_AVERAGING_STEPS = 10;\n\n // Automatic fallback tracking\n // Reference: player.js:654-665 - triggers \"nextCombo\" after sustained poor quality\n private consecutivePoorSamples = 0;\n private fallbackTriggered = false;\n\n constructor(options: QualityMonitorOptions = {}) {\n this.options = {\n sampleInterval: options.sampleInterval ?? 500,\n thresholds: options.thresholds ?? {},\n onQualityDegraded: options.onQualityDegraded ?? (() => {}),\n onSample: options.onSample ?? (() => {}),\n protocol: options.protocol ?? \"unknown\",\n playbackScoreThreshold: options.playbackScoreThreshold ?? null,\n onFallbackRequest: options.onFallbackRequest ?? (() => {}),\n poorSamplesBeforeFallback: options.poorSamplesBeforeFallback ?? 5,\n };\n this.thresholds = { ...DEFAULT_THRESHOLDS, ...options.thresholds };\n }\n\n /**\n * Set the current player protocol for threshold selection\n */\n setProtocol(protocol: PlayerProtocol): void {\n this.options.protocol = protocol;\n }\n\n /**\n * Get the current player protocol\n */\n getProtocol(): PlayerProtocol {\n return this.options.protocol;\n }\n\n /**\n * Get the playback score threshold for the current protocol\n */\n getPlaybackScoreThreshold(): number {\n // Custom threshold takes precedence\n if (this.options.playbackScoreThreshold !== null) {\n return this.options.playbackScoreThreshold;\n }\n return PROTOCOL_THRESHOLDS[this.options.protocol];\n }\n\n /**\n * Set a custom playback score threshold (overrides protocol default)\n */\n setPlaybackScoreThreshold(threshold: number | null): void {\n this.options.playbackScoreThreshold = threshold;\n }\n\n /**\n * Start monitoring a video element\n */\n start(videoElement: HTMLVideoElement): void {\n this.stop();\n\n this.videoElement = videoElement;\n this.stallCount = 0;\n this.totalStallMs = 0;\n this.lastStallTime = 0;\n this.history = [];\n this.lastBytesLoaded = 0;\n this.lastBytesTime = 0;\n this.consecutivePoorSamples = 0;\n this.fallbackTriggered = false;\n this.playbackScoreHistory = [];\n this.playbackScore = 1.0;\n\n // Listen for stall events\n const onWaiting = () => {\n this.stallCount++;\n this.lastStallTime = performance.now();\n };\n\n const onPlaying = () => {\n if (this.lastStallTime > 0) {\n this.totalStallMs += performance.now() - this.lastStallTime;\n this.lastStallTime = 0;\n }\n };\n\n const onCanPlay = () => {\n if (this.lastStallTime > 0) {\n this.totalStallMs += performance.now() - this.lastStallTime;\n this.lastStallTime = 0;\n }\n };\n\n videoElement.addEventListener(\"waiting\", onWaiting);\n videoElement.addEventListener(\"playing\", onPlaying);\n videoElement.addEventListener(\"canplay\", onCanPlay);\n\n this.listeners = [\n () => videoElement.removeEventListener(\"waiting\", onWaiting),\n () => videoElement.removeEventListener(\"playing\", onPlaying),\n () => videoElement.removeEventListener(\"canplay\", onCanPlay),\n ];\n\n // Start sampling interval\n this.timers.startInterval(() => this.sample(), this.options.sampleInterval, \"sampling\");\n\n // Take initial sample\n this.sample();\n }\n\n /**\n * Stop monitoring\n */\n stop(): void {\n this.timers.destroy();\n\n this.listeners.forEach((cleanup) => cleanup());\n this.listeners = [];\n\n this.videoElement = null;\n }\n\n /**\n * Take a quality sample\n */\n private sample(): void {\n const video = this.videoElement;\n if (!video) return;\n\n // Update MistPlayer-style playback score\n this.updatePlaybackScore();\n\n const quality = this.calculateQuality(video);\n this.history.push(quality);\n\n // Keep rolling window\n if (this.history.length > ROLLING_WINDOW_SIZE) {\n this.history.shift();\n }\n\n // Notify listeners\n this.options.onSample(quality);\n\n // Check for quality degradation\n if (\n quality.score < this.thresholds.minScore ||\n quality.stallCount > this.thresholds.maxStalls ||\n quality.bufferedAhead < this.thresholds.minBuffer\n ) {\n this.options.onQualityDegraded(quality);\n }\n\n // Track sustained poor quality for automatic fallback\n // Reference: player.js:654-665 - \"nextCombo\" after sustained poor playback\n if (this.isPlaybackPoor()) {\n this.consecutivePoorSamples++;\n\n // Trigger fallback after sustained poor quality\n // Only trigger once until quality improves or reset\n if (\n !this.fallbackTriggered &&\n this.consecutivePoorSamples >= this.options.poorSamplesBeforeFallback\n ) {\n this.fallbackTriggered = true;\n console.warn(\n `[QualityMonitor] Poor playback detected: ${Math.round(this.playbackScore * 100)}% ` +\n `(threshold: ${Math.round(this.getPlaybackScoreThreshold() * 100)}%, ` +\n `protocol: ${this.options.protocol})`\n );\n this.options.onFallbackRequest({\n score: this.playbackScore,\n consecutivePoorSamples: this.consecutivePoorSamples,\n });\n }\n } else {\n // Quality recovered - reset counters\n this.consecutivePoorSamples = 0;\n this.fallbackTriggered = false;\n }\n }\n\n /**\n * Calculate current quality metrics\n */\n private calculateQuality(video: HTMLVideoElement): PlaybackQuality {\n const now = Date.now();\n\n // Calculate buffered ahead\n let bufferedAhead = 0;\n if (video.buffered.length > 0) {\n for (let i = 0; i < video.buffered.length; i++) {\n if (\n video.buffered.start(i) <= video.currentTime &&\n video.buffered.end(i) > video.currentTime\n ) {\n bufferedAhead = video.buffered.end(i) - video.currentTime;\n break;\n }\n }\n }\n\n // Get frame stats if available\n let framesDecoded = 0;\n let framesDropped = 0;\n let frameDropRate = 0;\n\n if (\"getVideoPlaybackQuality\" in video) {\n const stats = video.getVideoPlaybackQuality();\n framesDecoded = stats.totalVideoFrames;\n framesDropped = stats.droppedVideoFrames;\n frameDropRate = framesDecoded > 0 ? (framesDropped / framesDecoded) * 100 : 0;\n }\n\n // Estimate bitrate from buffer loading\n let bitrate = 0;\n if (video.buffered.length > 0 && this.lastBytesTime > 0) {\n const timeElapsed = (now - this.lastBytesTime) / 1000;\n if (timeElapsed > 0) {\n // Estimate from buffer growth\n // This is a rough approximation - real bitrate tracking would use MSE\n const bufferEnd = video.buffered.end(video.buffered.length - 1);\n const bufferDuration = bufferEnd - video.currentTime;\n // Assume average bitrate based on buffer size\n bitrate = bufferDuration > 0 ? Math.round((bufferDuration * 1000000) / timeElapsed) : 0;\n }\n }\n this.lastBytesTime = now;\n\n // Calculate latency for live streams\n let latency = 0;\n if (video.duration === Infinity || !isFinite(video.duration)) {\n // Live stream - estimate latency from buffer\n if (video.buffered.length > 0) {\n const liveEdge = video.buffered.end(video.buffered.length - 1);\n latency = (liveEdge - video.currentTime) * 1000;\n }\n }\n\n // Calculate composite quality score (0-100) with duration-weighted stalls\n const score = this.calculateScore({\n bufferedAhead,\n stallCount: this.stallCount,\n stallDurationMs: this.totalStallMs,\n frameDropRate,\n latency,\n });\n\n return {\n score,\n bitrate,\n bufferedAhead,\n stallCount: this.stallCount,\n frameDropRate,\n latency,\n timestamp: now,\n };\n }\n\n /**\n * Calculate composite quality score\n *\n * D4: Duration-weighted stall tracking - stall penalty considers both\n * count AND duration. 10x 0.1s stalls (1s total) weighs less than 1x 1s stall.\n */\n private calculateScore(metrics: {\n bufferedAhead: number;\n stallCount: number;\n stallDurationMs: number;\n frameDropRate: number;\n latency: number;\n }): number {\n let score = 100;\n\n // Buffer penalty (max -40 points)\n if (metrics.bufferedAhead < this.thresholds.minBuffer) {\n const bufferPenalty = Math.min(40, (this.thresholds.minBuffer - metrics.bufferedAhead) * 20);\n score -= bufferPenalty;\n }\n\n // D4: Duration-weighted stall penalty (max -30 points)\n // Base: 5 points per stall + 2 points per second of total stall time\n // This weights duration: 1x 2s stall = 5 + 4 = 9 points\n // 10x 0.2s stalls = 50 + 4 = 54 points (capped at 30)\n // So many short stalls are penalized more than few long stalls of same duration\n const countPenalty = metrics.stallCount * 5;\n const durationPenalty = (metrics.stallDurationMs / 1000) * 2;\n const stallPenalty = Math.min(30, countPenalty + durationPenalty);\n score -= stallPenalty;\n\n // Frame drop penalty (max -20 points)\n const framePenalty = Math.min(20, metrics.frameDropRate * 2);\n score -= framePenalty;\n\n // Latency penalty for live streams (max -10 points)\n if (metrics.latency > 5000) {\n const latencyPenalty = Math.min(10, (metrics.latency - 5000) / 1000);\n score -= latencyPenalty;\n }\n\n return Math.max(0, Math.round(score));\n }\n\n /**\n * Get current quality metrics\n */\n getCurrentQuality(): PlaybackQuality | null {\n return this.history.length > 0 ? this.history[this.history.length - 1] : null;\n }\n\n /**\n * Get rolling average quality\n */\n getAverageQuality(): PlaybackQuality | null {\n if (this.history.length === 0) return null;\n\n const avg: PlaybackQuality = {\n score: 0,\n bitrate: 0,\n bufferedAhead: 0,\n stallCount: this.stallCount,\n frameDropRate: 0,\n latency: 0,\n timestamp: Date.now(),\n };\n\n for (const q of this.history) {\n avg.score += q.score;\n avg.bitrate += q.bitrate;\n avg.bufferedAhead += q.bufferedAhead;\n avg.frameDropRate += q.frameDropRate;\n avg.latency += q.latency;\n }\n\n const len = this.history.length;\n avg.score = Math.round(avg.score / len);\n avg.bitrate = Math.round(avg.bitrate / len);\n avg.bufferedAhead = avg.bufferedAhead / len;\n avg.frameDropRate = avg.frameDropRate / len;\n avg.latency = avg.latency / len;\n\n return avg;\n }\n\n /**\n * Get quality history\n */\n getHistory(): PlaybackQuality[] {\n return [...this.history];\n }\n\n /**\n * Reset stall counters\n */\n resetStallCounters(): void {\n this.stallCount = 0;\n this.totalStallMs = 0;\n }\n\n /**\n * Get total stall time in ms\n */\n getTotalStallMs(): number {\n return this.totalStallMs;\n }\n\n /**\n * Check if currently monitoring\n */\n isMonitoring(): boolean {\n return this.videoElement !== null && this.timers.activeCount > 0;\n }\n\n // ========================================\n // MistPlayer-style Playback Score (0-2.0)\n // ========================================\n\n /**\n * Calculate playback score entry value\n * Compares video time progress vs wall clock time\n */\n private getPlaybackScoreValue(): PlaybackScoreEntry {\n const video = this.videoElement;\n const clock = performance.now() / 1000;\n const videoTime = video?.currentTime ?? 0;\n\n const result: PlaybackScoreEntry = {\n clock,\n video: videoTime,\n score: 1.0,\n };\n\n if (this.playbackScoreHistory.length > 0) {\n const prev = this.playbackScoreHistory[this.playbackScoreHistory.length - 1];\n result.score = this.calculatePlaybackScoreFromEntries(prev, result);\n }\n\n return result;\n }\n\n /**\n * Calculate score between two entries\n * Returns 1.0 for normal playback, >1.0 if faster, <1.0 if stalled, <0 if backwards\n */\n private calculatePlaybackScoreFromEntries(a: PlaybackScoreEntry, b: PlaybackScoreEntry): number {\n const video = this.videoElement;\n let rate = 1;\n if (video) {\n rate = video.playbackRate || 1;\n }\n\n const clockDelta = b.clock - a.clock;\n const videoDelta = b.video - a.video;\n\n if (clockDelta <= 0) return 1.0;\n\n return videoDelta / clockDelta / rate;\n }\n\n /**\n * Calculate and update the playback score\n * Like MistPlayer's calcScore function\n */\n private updatePlaybackScore(): number {\n const entry = this.getPlaybackScoreValue();\n this.playbackScoreHistory.push(entry);\n\n if (this.playbackScoreHistory.length <= 1) {\n return 1.0;\n }\n\n // Calculate score from oldest to newest\n const first = this.playbackScoreHistory[0];\n const last = this.playbackScoreHistory[this.playbackScoreHistory.length - 1];\n let score = this.calculatePlaybackScoreFromEntries(first, last);\n\n // Trim history\n if (this.playbackScoreHistory.length > this.PLAYBACK_SCORE_AVERAGING_STEPS) {\n this.playbackScoreHistory.shift();\n }\n\n // Final score is max of averaged and current\n score = Math.max(score, entry.score);\n this.playbackScore = score;\n\n return score;\n }\n\n /**\n * Get current playback score (MistPlayer-style 0-2.0 scale)\n *\n * - 1.0 = normal playback (video progresses at expected rate)\n * - > 1.0 = faster than expected (catching up)\n * - < 1.0 = slower than expected (stalling/buffering)\n * - < 0 = video went backwards\n *\n * Threshold recommendations:\n * - WebRTC: warn below 0.95\n * - HLS/DASH: warn below 0.75\n */\n getPlaybackScore(): number {\n return this.playbackScore;\n }\n\n /**\n * Check if playback quality is poor based on score\n * Uses protocol-specific thresholds (MistPlayer-style)\n * WebRTC: 0.95 (strict), HLS/DASH/HTML5: 0.75 (lenient)\n */\n isPlaybackPoor(): boolean {\n return this.playbackScore < this.getPlaybackScoreThreshold();\n }\n\n /**\n * Reset playback score tracking\n */\n resetPlaybackScore(): void {\n this.playbackScoreHistory = [];\n this.playbackScore = 1.0;\n }\n\n /**\n * Reset fallback state\n * Call after a player switch to allow fallback to trigger again\n */\n resetFallbackState(): void {\n this.consecutivePoorSamples = 0;\n this.fallbackTriggered = false;\n }\n\n /**\n * Get consecutive poor sample count (for debugging)\n */\n getConsecutivePoorSamples(): number {\n return this.consecutivePoorSamples;\n }\n\n /**\n * Check if fallback has been triggered (for debugging)\n */\n hasFallbackTriggered(): boolean {\n return this.fallbackTriggered;\n }\n}\n\nexport default QualityMonitor;\n"],"names":[],"mappings":"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"}