@livepeer-frameworks/player-core 0.1.0 → 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.
- package/README.md +11 -9
- package/dist/cjs/core/ABRController.js +456 -0
- package/dist/cjs/core/ABRController.js.map +1 -0
- package/dist/cjs/core/CodecUtils.js +195 -0
- package/dist/cjs/core/CodecUtils.js.map +1 -0
- package/dist/cjs/core/ErrorClassifier.js +410 -0
- package/dist/cjs/core/ErrorClassifier.js.map +1 -0
- package/dist/cjs/core/EventEmitter.js +108 -0
- package/dist/cjs/core/EventEmitter.js.map +1 -0
- package/dist/cjs/core/GatewayClient.js +342 -0
- package/dist/cjs/core/GatewayClient.js.map +1 -0
- package/dist/cjs/core/InteractionController.js +606 -0
- package/dist/cjs/core/InteractionController.js.map +1 -0
- package/dist/cjs/core/LiveDurationProxy.js +186 -0
- package/dist/cjs/core/LiveDurationProxy.js.map +1 -0
- package/dist/cjs/core/MetaTrackManager.js +624 -0
- package/dist/cjs/core/MetaTrackManager.js.map +1 -0
- package/dist/cjs/core/MistReporter.js +449 -0
- package/dist/cjs/core/MistReporter.js.map +1 -0
- package/dist/cjs/core/MistSignaling.js +264 -0
- package/dist/cjs/core/MistSignaling.js.map +1 -0
- package/dist/cjs/core/PlayerController.js +2658 -0
- package/dist/cjs/core/PlayerController.js.map +1 -0
- package/dist/cjs/core/PlayerInterface.js +269 -0
- package/dist/cjs/core/PlayerInterface.js.map +1 -0
- package/dist/cjs/core/PlayerManager.js +806 -0
- package/dist/cjs/core/PlayerManager.js.map +1 -0
- package/dist/cjs/core/PlayerRegistry.js +270 -0
- package/dist/cjs/core/PlayerRegistry.js.map +1 -0
- package/dist/cjs/core/QualityMonitor.js +474 -0
- package/dist/cjs/core/QualityMonitor.js.map +1 -0
- package/dist/cjs/core/SeekingUtils.js +292 -0
- package/dist/cjs/core/SeekingUtils.js.map +1 -0
- package/dist/cjs/core/StreamStateClient.js +381 -0
- package/dist/cjs/core/StreamStateClient.js.map +1 -0
- package/dist/cjs/core/SubtitleManager.js +227 -0
- package/dist/cjs/core/SubtitleManager.js.map +1 -0
- package/dist/cjs/core/TelemetryReporter.js +258 -0
- package/dist/cjs/core/TelemetryReporter.js.map +1 -0
- package/dist/cjs/core/TimeFormat.js +176 -0
- package/dist/cjs/core/TimeFormat.js.map +1 -0
- package/dist/cjs/core/TimerManager.js +176 -0
- package/dist/cjs/core/TimerManager.js.map +1 -0
- package/dist/cjs/core/UrlUtils.js +160 -0
- package/dist/cjs/core/UrlUtils.js.map +1 -0
- package/dist/cjs/core/detector.js +293 -0
- package/dist/cjs/core/detector.js.map +1 -0
- package/dist/cjs/core/scorer.js +443 -0
- package/dist/cjs/core/scorer.js.map +1 -0
- package/dist/cjs/index.js +121 -20134
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/lib/utils.js +11 -0
- package/dist/cjs/lib/utils.js.map +1 -0
- package/dist/cjs/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js +6 -0
- package/dist/cjs/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/cjs/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js +3042 -0
- package/dist/cjs/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/dist/cjs/players/DashJsPlayer.js +638 -0
- package/dist/cjs/players/DashJsPlayer.js.map +1 -0
- package/dist/cjs/players/HlsJsPlayer.js +482 -0
- package/dist/cjs/players/HlsJsPlayer.js.map +1 -0
- package/dist/cjs/players/MewsWsPlayer/SourceBufferManager.js +522 -0
- package/dist/cjs/players/MewsWsPlayer/SourceBufferManager.js.map +1 -0
- package/dist/cjs/players/MewsWsPlayer/WebSocketManager.js +215 -0
- package/dist/cjs/players/MewsWsPlayer/WebSocketManager.js.map +1 -0
- package/dist/cjs/players/MewsWsPlayer/index.js +987 -0
- package/dist/cjs/players/MewsWsPlayer/index.js.map +1 -0
- package/dist/cjs/players/MistPlayer.js +185 -0
- package/dist/cjs/players/MistPlayer.js.map +1 -0
- package/dist/cjs/players/MistWebRTCPlayer/index.js +635 -0
- package/dist/cjs/players/MistWebRTCPlayer/index.js.map +1 -0
- package/dist/cjs/players/NativePlayer.js +762 -0
- package/dist/cjs/players/NativePlayer.js.map +1 -0
- package/dist/cjs/players/VideoJsPlayer.js +585 -0
- package/dist/cjs/players/VideoJsPlayer.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/JitterBuffer.js +236 -0
- package/dist/cjs/players/WebCodecsPlayer/JitterBuffer.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/LatencyProfiles.js +143 -0
- package/dist/cjs/players/WebCodecsPlayer/LatencyProfiles.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/RawChunkParser.js +96 -0
- package/dist/cjs/players/WebCodecsPlayer/RawChunkParser.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/SyncController.js +359 -0
- package/dist/cjs/players/WebCodecsPlayer/SyncController.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/WebSocketController.js +460 -0
- package/dist/cjs/players/WebCodecsPlayer/WebSocketController.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/index.js +1467 -0
- package/dist/cjs/players/WebCodecsPlayer/index.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js +320 -0
- package/dist/cjs/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js.map +1 -0
- package/dist/cjs/styles/index.js +57 -0
- package/dist/cjs/styles/index.js.map +1 -0
- package/dist/cjs/vanilla/FrameWorksPlayer.js +269 -0
- package/dist/cjs/vanilla/FrameWorksPlayer.js.map +1 -0
- package/dist/cjs/vanilla.js +11 -0
- package/dist/cjs/vanilla.js.map +1 -0
- package/dist/esm/core/ABRController.js +454 -0
- package/dist/esm/core/ABRController.js.map +1 -0
- package/dist/esm/core/CodecUtils.js +193 -0
- package/dist/esm/core/CodecUtils.js.map +1 -0
- package/dist/esm/core/ErrorClassifier.js +408 -0
- package/dist/esm/core/ErrorClassifier.js.map +1 -0
- package/dist/esm/core/EventEmitter.js +106 -0
- package/dist/esm/core/EventEmitter.js.map +1 -0
- package/dist/esm/core/GatewayClient.js +340 -0
- package/dist/esm/core/GatewayClient.js.map +1 -0
- package/dist/esm/core/InteractionController.js +604 -0
- package/dist/esm/core/InteractionController.js.map +1 -0
- package/dist/esm/core/LiveDurationProxy.js +184 -0
- package/dist/esm/core/LiveDurationProxy.js.map +1 -0
- package/dist/esm/core/MetaTrackManager.js +622 -0
- package/dist/esm/core/MetaTrackManager.js.map +1 -0
- package/dist/esm/core/MistReporter.js +447 -0
- package/dist/esm/core/MistReporter.js.map +1 -0
- package/dist/esm/core/MistSignaling.js +262 -0
- package/dist/esm/core/MistSignaling.js.map +1 -0
- package/dist/esm/core/PlayerController.js +2651 -0
- package/dist/esm/core/PlayerController.js.map +1 -0
- package/dist/esm/core/PlayerInterface.js +267 -0
- package/dist/esm/core/PlayerInterface.js.map +1 -0
- package/dist/esm/core/PlayerManager.js +804 -0
- package/dist/esm/core/PlayerManager.js.map +1 -0
- package/dist/esm/core/PlayerRegistry.js +264 -0
- package/dist/esm/core/PlayerRegistry.js.map +1 -0
- package/dist/esm/core/QualityMonitor.js +471 -0
- package/dist/esm/core/QualityMonitor.js.map +1 -0
- package/dist/esm/core/SeekingUtils.js +280 -0
- package/dist/esm/core/SeekingUtils.js.map +1 -0
- package/dist/esm/core/StreamStateClient.js +379 -0
- package/dist/esm/core/StreamStateClient.js.map +1 -0
- package/dist/esm/core/SubtitleManager.js +225 -0
- package/dist/esm/core/SubtitleManager.js.map +1 -0
- package/dist/esm/core/TelemetryReporter.js +256 -0
- package/dist/esm/core/TelemetryReporter.js.map +1 -0
- package/dist/esm/core/TimeFormat.js +169 -0
- package/dist/esm/core/TimeFormat.js.map +1 -0
- package/dist/esm/core/TimerManager.js +174 -0
- package/dist/esm/core/TimerManager.js.map +1 -0
- package/dist/esm/core/UrlUtils.js +151 -0
- package/dist/esm/core/UrlUtils.js.map +1 -0
- package/dist/esm/core/detector.js +279 -0
- package/dist/esm/core/detector.js.map +1 -0
- package/dist/esm/core/scorer.js +422 -0
- package/dist/esm/core/scorer.js.map +1 -0
- package/dist/esm/index.js +26 -20043
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/utils.js +9 -0
- package/dist/esm/lib/utils.js.map +1 -0
- package/dist/esm/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js +4 -0
- package/dist/esm/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/esm/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js +3036 -0
- package/dist/esm/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/dist/esm/players/DashJsPlayer.js +636 -0
- package/dist/esm/players/DashJsPlayer.js.map +1 -0
- package/dist/esm/players/HlsJsPlayer.js +480 -0
- package/dist/esm/players/HlsJsPlayer.js.map +1 -0
- package/dist/esm/players/MewsWsPlayer/SourceBufferManager.js +520 -0
- package/dist/esm/players/MewsWsPlayer/SourceBufferManager.js.map +1 -0
- package/dist/esm/players/MewsWsPlayer/WebSocketManager.js +213 -0
- package/dist/esm/players/MewsWsPlayer/WebSocketManager.js.map +1 -0
- package/dist/esm/players/MewsWsPlayer/index.js +985 -0
- package/dist/esm/players/MewsWsPlayer/index.js.map +1 -0
- package/dist/esm/players/MistPlayer.js +183 -0
- package/dist/esm/players/MistPlayer.js.map +1 -0
- package/dist/esm/players/MistWebRTCPlayer/index.js +633 -0
- package/dist/esm/players/MistWebRTCPlayer/index.js.map +1 -0
- package/dist/esm/players/NativePlayer.js +759 -0
- package/dist/esm/players/NativePlayer.js.map +1 -0
- package/dist/esm/players/VideoJsPlayer.js +583 -0
- package/dist/esm/players/VideoJsPlayer.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/JitterBuffer.js +233 -0
- package/dist/esm/players/WebCodecsPlayer/JitterBuffer.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/LatencyProfiles.js +134 -0
- package/dist/esm/players/WebCodecsPlayer/LatencyProfiles.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/RawChunkParser.js +91 -0
- package/dist/esm/players/WebCodecsPlayer/RawChunkParser.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/SyncController.js +357 -0
- package/dist/esm/players/WebCodecsPlayer/SyncController.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/WebSocketController.js +458 -0
- package/dist/esm/players/WebCodecsPlayer/WebSocketController.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/index.js +1458 -0
- package/dist/esm/players/WebCodecsPlayer/index.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js +315 -0
- package/dist/esm/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js.map +1 -0
- package/dist/esm/styles/index.js +54 -0
- package/dist/esm/styles/index.js.map +1 -0
- package/dist/esm/vanilla/FrameWorksPlayer.js +264 -0
- package/dist/esm/vanilla/FrameWorksPlayer.js.map +1 -0
- package/dist/esm/vanilla.js +2 -0
- package/dist/esm/vanilla.js.map +1 -0
- package/dist/player.css +185 -42
- package/dist/types/core/ABRController.d.ts +4 -4
- package/dist/types/core/CodecUtils.d.ts +1 -1
- package/dist/types/core/ErrorClassifier.d.ts +77 -0
- package/dist/types/core/GatewayClient.d.ts +4 -4
- package/dist/types/core/MetaTrackManager.d.ts +2 -2
- package/dist/types/core/MistReporter.d.ts +3 -3
- package/dist/types/core/MistSignaling.d.ts +12 -12
- package/dist/types/core/PlayerController.d.ts +19 -14
- package/dist/types/core/PlayerInterface.d.ts +100 -2
- package/dist/types/core/PlayerManager.d.ts +36 -9
- package/dist/types/core/PlayerRegistry.d.ts +11 -11
- package/dist/types/core/QualityMonitor.d.ts +2 -2
- package/dist/types/core/SeekingUtils.d.ts +2 -2
- package/dist/types/core/StreamStateClient.d.ts +2 -2
- package/dist/types/core/TelemetryReporter.d.ts +1 -1
- package/dist/types/core/TimerManager.d.ts +1 -1
- package/dist/types/core/detector.d.ts +1 -1
- package/dist/types/core/index.d.ts +44 -44
- package/dist/types/core/scorer.d.ts +1 -1
- package/dist/types/core/selector.d.ts +2 -2
- package/dist/types/index.d.ts +35 -34
- package/dist/types/players/DashJsPlayer.d.ts +3 -3
- package/dist/types/players/HlsJsPlayer.d.ts +3 -3
- package/dist/types/players/MewsWsPlayer/SourceBufferManager.d.ts +1 -1
- package/dist/types/players/MewsWsPlayer/WebSocketManager.d.ts +1 -1
- package/dist/types/players/MewsWsPlayer/index.d.ts +2 -2
- package/dist/types/players/MewsWsPlayer/types.d.ts +15 -15
- package/dist/types/players/MistPlayer.d.ts +2 -2
- package/dist/types/players/MistWebRTCPlayer/index.d.ts +3 -3
- package/dist/types/players/NativePlayer.d.ts +3 -3
- package/dist/types/players/VideoJsPlayer.d.ts +3 -3
- package/dist/types/players/WebCodecsPlayer/JitterBuffer.d.ts +3 -3
- package/dist/types/players/WebCodecsPlayer/LatencyProfiles.d.ts +1 -1
- package/dist/types/players/WebCodecsPlayer/RawChunkParser.d.ts +2 -2
- package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +2 -2
- package/dist/types/players/WebCodecsPlayer/WebSocketController.d.ts +3 -3
- package/dist/types/players/WebCodecsPlayer/index.d.ts +9 -9
- package/dist/types/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.d.ts +1 -1
- package/dist/types/players/WebCodecsPlayer/types.d.ts +49 -49
- package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +31 -31
- package/dist/types/players/index.d.ts +5 -8
- package/dist/types/types.d.ts +15 -15
- package/dist/types/vanilla/FrameWorksPlayer.d.ts +2 -2
- package/dist/types/vanilla/index.d.ts +4 -4
- package/dist/workers/decoder.worker.js +129 -122
- package/dist/workers/decoder.worker.js.map +1 -1
- package/package.json +31 -15
- package/src/core/ABRController.ts +38 -36
- package/src/core/CodecUtils.ts +49 -46
- package/src/core/Disposable.ts +4 -4
- package/src/core/ErrorClassifier.ts +499 -0
- package/src/core/EventEmitter.ts +1 -1
- package/src/core/GatewayClient.ts +41 -39
- package/src/core/InteractionController.ts +89 -82
- package/src/core/LiveDurationProxy.ts +14 -15
- package/src/core/MetaTrackManager.ts +73 -65
- package/src/core/MistReporter.ts +72 -45
- package/src/core/MistSignaling.ts +59 -56
- package/src/core/PlayerController.ts +542 -384
- package/src/core/PlayerInterface.ts +192 -59
- package/src/core/PlayerManager.ts +354 -164
- package/src/core/PlayerRegistry.ts +238 -87
- package/src/core/QualityMonitor.ts +38 -31
- package/src/core/ScreenWakeLockManager.ts +8 -9
- package/src/core/SeekingUtils.ts +31 -22
- package/src/core/StreamStateClient.ts +74 -68
- package/src/core/SubtitleManager.ts +24 -22
- package/src/core/TelemetryReporter.ts +38 -32
- package/src/core/TimeFormat.ts +13 -17
- package/src/core/TimerManager.ts +24 -8
- package/src/core/UrlUtils.ts +20 -17
- package/src/core/detector.ts +44 -44
- package/src/core/index.ts +57 -48
- package/src/core/scorer.ts +136 -141
- package/src/core/selector.ts +2 -6
- package/src/global.d.ts +1 -1
- package/src/index.ts +56 -36
- package/src/players/DashJsPlayer.ts +164 -115
- package/src/players/HlsJsPlayer.ts +132 -78
- package/src/players/MewsWsPlayer/SourceBufferManager.ts +41 -36
- package/src/players/MewsWsPlayer/WebSocketManager.ts +9 -9
- package/src/players/MewsWsPlayer/index.ts +192 -152
- package/src/players/MewsWsPlayer/types.ts +21 -21
- package/src/players/MistPlayer.ts +45 -26
- package/src/players/MistWebRTCPlayer/index.ts +175 -129
- package/src/players/NativePlayer.ts +203 -143
- package/src/players/VideoJsPlayer.ts +170 -118
- package/src/players/WebCodecsPlayer/JitterBuffer.ts +6 -7
- package/src/players/WebCodecsPlayer/LatencyProfiles.ts +43 -43
- package/src/players/WebCodecsPlayer/RawChunkParser.ts +10 -10
- package/src/players/WebCodecsPlayer/SyncController.ts +45 -53
- package/src/players/WebCodecsPlayer/WebSocketController.ts +66 -68
- package/src/players/WebCodecsPlayer/index.ts +265 -223
- package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +12 -17
- package/src/players/WebCodecsPlayer/types.ts +56 -56
- package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +238 -182
- package/src/players/WebCodecsPlayer/worker/types.ts +31 -31
- package/src/players/index.ts +5 -16
- package/src/styles/animations.css +2 -1
- package/src/styles/player.css +185 -42
- package/src/styles/tailwind.css +473 -159
- package/src/types.ts +43 -43
- package/src/vanilla/FrameWorksPlayer.ts +26 -14
- package/src/vanilla/index.ts +4 -4
package/README.md
CHANGED
|
@@ -15,36 +15,37 @@ npm i @livepeer-frameworks/player-core
|
|
|
15
15
|
## Basic Usage
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
-
import { PlayerController } from
|
|
18
|
+
import { PlayerController } from "@livepeer-frameworks/player-core";
|
|
19
19
|
|
|
20
20
|
const controller = new PlayerController({
|
|
21
|
-
contentId:
|
|
22
|
-
contentType:
|
|
23
|
-
gatewayUrl:
|
|
21
|
+
contentId: "pk_...", // playbackId
|
|
22
|
+
contentType: "live",
|
|
23
|
+
gatewayUrl: "https://your-bridge/graphql",
|
|
24
24
|
debug: true,
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
const container = document.getElementById(
|
|
27
|
+
const container = document.getElementById("player")!;
|
|
28
28
|
await controller.attach(container);
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Notes:
|
|
32
|
+
|
|
32
33
|
- There is **no default gateway**; provide `gatewayUrl` unless you pass `endpoints` or `mistUrl`.
|
|
33
34
|
|
|
34
35
|
### Direct MistServer Node (mistUrl)
|
|
35
36
|
|
|
36
37
|
```ts
|
|
37
38
|
const controller = new PlayerController({
|
|
38
|
-
contentId:
|
|
39
|
-
contentType:
|
|
40
|
-
mistUrl:
|
|
39
|
+
contentId: "pk_...",
|
|
40
|
+
contentType: "live",
|
|
41
|
+
mistUrl: "https://edge.example.com",
|
|
41
42
|
});
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
### Styles
|
|
45
46
|
|
|
46
47
|
```ts
|
|
47
|
-
import
|
|
48
|
+
import "@livepeer-frameworks/player-core/player.css";
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
## Controls & Shortcuts
|
|
@@ -73,6 +74,7 @@ The player ships with keyboard/mouse shortcuts when the player is focused (click
|
|
|
73
74
|
| Click/Tap and hold | 2x speed | Disabled on live-only |
|
|
74
75
|
|
|
75
76
|
**Constraints**
|
|
77
|
+
|
|
76
78
|
- Live-only streams disable seeking/skip/2x hold and frame-step.
|
|
77
79
|
- Live with DVR buffer enables the same shortcuts as VOD.
|
|
78
80
|
- Frame stepping only moves within already buffered ranges (no network seek). WebCodecs supports true frame stepping when paused.
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var TimerManager = require('./TimerManager.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default ABR options
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_OPTIONS = {
|
|
9
|
+
mode: "auto",
|
|
10
|
+
maxResolution: { width: 1920, height: 1080 },
|
|
11
|
+
maxBitrate: 8000000, // 8 Mbps
|
|
12
|
+
minBufferForUpgrade: 10,
|
|
13
|
+
downgradeThreshold: 60,
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* ABRController - Adaptive Bitrate Controller
|
|
17
|
+
*
|
|
18
|
+
* Manages automatic quality selection based on:
|
|
19
|
+
* - ABR_resize: Matches video resolution to viewport size
|
|
20
|
+
* - ABR_bitrate: Switches quality based on playback performance
|
|
21
|
+
* - auto: Combines both modes
|
|
22
|
+
* - manual: No automatic switching
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const abr = new ABRController({
|
|
27
|
+
* options: { mode: 'auto' },
|
|
28
|
+
* getQualities: () => player.getQualities(),
|
|
29
|
+
* selectQuality: (id) => player.selectQuality(id),
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* abr.start(videoElement);
|
|
33
|
+
* abr.onQualityChange((quality) => console.log('Quality:', quality.score));
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
class ABRController {
|
|
37
|
+
constructor(config) {
|
|
38
|
+
this.videoElement = null;
|
|
39
|
+
this.currentQualityId = "auto";
|
|
40
|
+
this.lastDecision = "none";
|
|
41
|
+
this.lastDecisionTime = 0;
|
|
42
|
+
this.resizeObserver = null;
|
|
43
|
+
this.qualityChangeCallbacks = [];
|
|
44
|
+
// Active monitoring
|
|
45
|
+
this.timers = new TimerManager.TimerManager();
|
|
46
|
+
this.bandwidthHistory = [];
|
|
47
|
+
this.lastUpgradeTime = 0;
|
|
48
|
+
this.lastDowngradeTime = 0;
|
|
49
|
+
this.currentQualityBitrate = 0;
|
|
50
|
+
this.options = { ...DEFAULT_OPTIONS, ...config.options };
|
|
51
|
+
this.config = config;
|
|
52
|
+
this.debug = config.debug ?? false;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Start ABR control
|
|
56
|
+
*/
|
|
57
|
+
start(videoElement) {
|
|
58
|
+
this.stop();
|
|
59
|
+
this.videoElement = videoElement;
|
|
60
|
+
if (this.options.mode === "manual") {
|
|
61
|
+
this.log("Manual mode - no automatic ABR");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Setup resize observer for ABR_resize mode
|
|
65
|
+
if (this.options.mode === "resize" || this.options.mode === "auto") {
|
|
66
|
+
this.setupResizeObserver();
|
|
67
|
+
}
|
|
68
|
+
// Start active bandwidth monitoring for bitrate mode
|
|
69
|
+
if (this.options.mode === "bitrate" || this.options.mode === "auto") {
|
|
70
|
+
this.startActiveMonitoring();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Stop ABR control
|
|
75
|
+
*/
|
|
76
|
+
stop() {
|
|
77
|
+
if (this.resizeObserver) {
|
|
78
|
+
this.resizeObserver.disconnect();
|
|
79
|
+
this.resizeObserver = null;
|
|
80
|
+
}
|
|
81
|
+
this.timers.destroy();
|
|
82
|
+
this.videoElement = null;
|
|
83
|
+
this.bandwidthHistory = [];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Start active bandwidth monitoring loop
|
|
87
|
+
* Continuously monitors bandwidth and proactively switches quality
|
|
88
|
+
*/
|
|
89
|
+
startActiveMonitoring() {
|
|
90
|
+
this.timers.startInterval(() => this.checkBandwidthAndSwitch(), ABRController.MONITORING_INTERVAL_MS, "monitoring");
|
|
91
|
+
// Initial check
|
|
92
|
+
this.checkBandwidthAndSwitch();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check current bandwidth and switch quality if needed
|
|
96
|
+
*
|
|
97
|
+
* Uses hysteresis (D2) and separate cooldowns (D3) to prevent oscillation:
|
|
98
|
+
* - Downgrade: immediate response (0ms cooldown), triggers at 0.8x
|
|
99
|
+
* - Upgrade: 5s cooldown, requires 1.5x headroom, holds until 1.2x
|
|
100
|
+
*/
|
|
101
|
+
async checkBandwidthAndSwitch() {
|
|
102
|
+
if (!this.videoElement)
|
|
103
|
+
return;
|
|
104
|
+
const now = Date.now();
|
|
105
|
+
// Get bandwidth estimate from player stats
|
|
106
|
+
const bandwidth = await this.getBandwidthEstimate();
|
|
107
|
+
if (bandwidth <= 0)
|
|
108
|
+
return;
|
|
109
|
+
// Add to history
|
|
110
|
+
this.bandwidthHistory.push(bandwidth);
|
|
111
|
+
if (this.bandwidthHistory.length > ABRController.BANDWIDTH_HISTORY_SIZE) {
|
|
112
|
+
this.bandwidthHistory.shift();
|
|
113
|
+
}
|
|
114
|
+
// Use smoothed bandwidth (average of history)
|
|
115
|
+
const smoothedBandwidth = this.getSmoothedBandwidth();
|
|
116
|
+
if (smoothedBandwidth <= 0)
|
|
117
|
+
return;
|
|
118
|
+
const qualities = this.config.getQualities();
|
|
119
|
+
const currentQuality = this.config.getCurrentQuality?.();
|
|
120
|
+
if (!currentQuality || qualities.length === 0)
|
|
121
|
+
return;
|
|
122
|
+
const currentBitrate = currentQuality.bitrate || 0;
|
|
123
|
+
// Track current quality bitrate for hysteresis
|
|
124
|
+
if (this.currentQualityBitrate !== currentBitrate) {
|
|
125
|
+
this.currentQualityBitrate = currentBitrate;
|
|
126
|
+
}
|
|
127
|
+
// D3: Check for downgrade (immediate, no cooldown)
|
|
128
|
+
if (now - this.lastDowngradeTime >= ABRController.DOWNGRADE_COOLDOWN_MS) {
|
|
129
|
+
if (smoothedBandwidth < currentBitrate * ABRController.DOWNGRADE_THRESHOLD) {
|
|
130
|
+
const lowerQuality = this.findLowerQuality(qualities, currentQuality);
|
|
131
|
+
if (lowerQuality) {
|
|
132
|
+
this.log(`ABR: bandwidth ${Math.round(smoothedBandwidth / 1000)}kbps < ${Math.round((currentBitrate * ABRController.DOWNGRADE_THRESHOLD) / 1000)}kbps threshold -> downgrading to ${lowerQuality.label}`);
|
|
133
|
+
this.lastDecision = "downgrade";
|
|
134
|
+
this.lastDecisionTime = now;
|
|
135
|
+
this.lastDowngradeTime = now;
|
|
136
|
+
this.selectQuality(lowerQuality.id);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// D3: Check for upgrade (5s cooldown required)
|
|
142
|
+
if (now - this.lastUpgradeTime >= ABRController.UPGRADE_COOLDOWN_MS) {
|
|
143
|
+
const higherQuality = this.findHigherQuality(qualities, currentQuality);
|
|
144
|
+
if (higherQuality && this.isWithinConstraints(higherQuality)) {
|
|
145
|
+
const targetBitrate = higherQuality.bitrate || 0;
|
|
146
|
+
// D2: Hysteresis - require 1.5x headroom to upgrade
|
|
147
|
+
// Once at a quality level, stay until bandwidth drops below 1.2x (not 1.0x)
|
|
148
|
+
const shouldUpgrade = smoothedBandwidth >= targetBitrate * ABRController.UPGRADE_HEADROOM;
|
|
149
|
+
smoothedBandwidth >= targetBitrate * ABRController.UPGRADE_HOLD_THRESHOLD;
|
|
150
|
+
if (shouldUpgrade) {
|
|
151
|
+
this.log(`ABR: bandwidth ${Math.round(smoothedBandwidth / 1000)}kbps >= ${Math.round((targetBitrate * ABRController.UPGRADE_HEADROOM) / 1000)}kbps headroom -> upgrading to ${higherQuality.label}`);
|
|
152
|
+
this.lastDecision = "upgrade";
|
|
153
|
+
this.lastDecisionTime = now;
|
|
154
|
+
this.lastUpgradeTime = now;
|
|
155
|
+
this.selectQuality(higherQuality.id);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get bandwidth estimate from player stats
|
|
163
|
+
*/
|
|
164
|
+
async getBandwidthEstimate() {
|
|
165
|
+
// Try to get bandwidth from player stats
|
|
166
|
+
if (this.config.getBandwidthEstimate) {
|
|
167
|
+
const estimate = await this.config.getBandwidthEstimate();
|
|
168
|
+
if (estimate > 0)
|
|
169
|
+
return estimate;
|
|
170
|
+
}
|
|
171
|
+
// Fallback: estimate from buffer growth (rough)
|
|
172
|
+
const video = this.videoElement;
|
|
173
|
+
if (!video || video.buffered.length === 0)
|
|
174
|
+
return 0;
|
|
175
|
+
// This is a rough fallback - prefer player-specific estimates
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get smoothed bandwidth from history
|
|
180
|
+
*/
|
|
181
|
+
getSmoothedBandwidth() {
|
|
182
|
+
if (this.bandwidthHistory.length === 0)
|
|
183
|
+
return 0;
|
|
184
|
+
if (this.bandwidthHistory.length < 3) {
|
|
185
|
+
// Need at least 3 samples for reliable estimate
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
const sum = this.bandwidthHistory.reduce((a, b) => a + b, 0);
|
|
189
|
+
return sum / this.bandwidthHistory.length;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get current bandwidth estimate (for external use)
|
|
193
|
+
*/
|
|
194
|
+
getCurrentBandwidth() {
|
|
195
|
+
return this.getSmoothedBandwidth();
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Setup resize observer for viewport-based quality selection
|
|
199
|
+
*/
|
|
200
|
+
setupResizeObserver() {
|
|
201
|
+
const video = this.videoElement;
|
|
202
|
+
if (!video)
|
|
203
|
+
return;
|
|
204
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
205
|
+
for (const entry of entries) {
|
|
206
|
+
const { width, height } = entry.contentRect;
|
|
207
|
+
this.handleResize(width, height);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
// Observe the video element's container
|
|
211
|
+
const container = video.parentElement;
|
|
212
|
+
if (container) {
|
|
213
|
+
this.resizeObserver.observe(container);
|
|
214
|
+
}
|
|
215
|
+
// Initial resize handling
|
|
216
|
+
const rect = video.getBoundingClientRect();
|
|
217
|
+
this.handleResize(rect.width, rect.height);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Handle viewport resize (ABR_resize mode)
|
|
221
|
+
*/
|
|
222
|
+
handleResize(width, height) {
|
|
223
|
+
if (this.options.mode !== "resize" && this.options.mode !== "auto") {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const qualities = this.config.getQualities();
|
|
227
|
+
if (qualities.length === 0)
|
|
228
|
+
return;
|
|
229
|
+
// Find best quality for viewport size
|
|
230
|
+
const targetWidth = Math.min(width * window.devicePixelRatio, this.options.maxResolution.width);
|
|
231
|
+
const targetHeight = Math.min(height * window.devicePixelRatio, this.options.maxResolution.height);
|
|
232
|
+
const bestQuality = this.findBestQualityForResolution(qualities, targetWidth, targetHeight);
|
|
233
|
+
if (bestQuality && bestQuality.id !== this.currentQualityId) {
|
|
234
|
+
this.log(`Resize ABR: ${width}x${height} -> selecting ${bestQuality.label}`);
|
|
235
|
+
this.selectQuality(bestQuality.id);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Handle quality degradation (ABR_bitrate mode)
|
|
240
|
+
*
|
|
241
|
+
* Called by QualityMonitor when playback quality drops
|
|
242
|
+
*/
|
|
243
|
+
handleQualityDegraded(quality) {
|
|
244
|
+
if (this.options.mode !== "bitrate" && this.options.mode !== "auto") {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// D3: Downgrade should be fast (0ms default), but still respects configured cooldown
|
|
248
|
+
const now = Date.now();
|
|
249
|
+
if (now - this.lastDowngradeTime < ABRController.DOWNGRADE_COOLDOWN_MS) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (quality.score < this.options.downgradeThreshold) {
|
|
253
|
+
const qualities = this.config.getQualities();
|
|
254
|
+
const currentQuality = this.config.getCurrentQuality?.();
|
|
255
|
+
if (currentQuality) {
|
|
256
|
+
// Find a lower quality level
|
|
257
|
+
const lowerQuality = this.findLowerQuality(qualities, currentQuality);
|
|
258
|
+
if (lowerQuality) {
|
|
259
|
+
this.log(`Bitrate ABR: score ${quality.score} -> downgrading to ${lowerQuality.label}`);
|
|
260
|
+
this.lastDecision = "downgrade";
|
|
261
|
+
this.lastDecisionTime = now;
|
|
262
|
+
this.lastDowngradeTime = now;
|
|
263
|
+
this.selectQuality(lowerQuality.id);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Handle quality improvement opportunity
|
|
270
|
+
*
|
|
271
|
+
* Called when conditions are good enough to try higher quality
|
|
272
|
+
*/
|
|
273
|
+
handleQualityImproved(quality) {
|
|
274
|
+
if (this.options.mode !== "bitrate" && this.options.mode !== "auto") {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// D3: Upgrade requires sustained stability (5s default) to prevent flapping
|
|
278
|
+
const now = Date.now();
|
|
279
|
+
if (now - this.lastUpgradeTime < ABRController.UPGRADE_COOLDOWN_MS) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// Only upgrade if buffer is healthy and quality is good
|
|
283
|
+
if (quality.score >= 90 && quality.bufferedAhead >= this.options.minBufferForUpgrade) {
|
|
284
|
+
const qualities = this.config.getQualities();
|
|
285
|
+
const currentQuality = this.config.getCurrentQuality?.();
|
|
286
|
+
if (currentQuality) {
|
|
287
|
+
// Find a higher quality level
|
|
288
|
+
const higherQuality = this.findHigherQuality(qualities, currentQuality);
|
|
289
|
+
if (higherQuality && this.isWithinConstraints(higherQuality)) {
|
|
290
|
+
this.log(`Bitrate ABR: score ${quality.score} -> upgrading to ${higherQuality.label}`);
|
|
291
|
+
this.lastDecision = "upgrade";
|
|
292
|
+
this.lastDecisionTime = now;
|
|
293
|
+
this.lastUpgradeTime = now;
|
|
294
|
+
this.selectQuality(higherQuality.id);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Find best quality level for given resolution
|
|
301
|
+
*/
|
|
302
|
+
findBestQualityForResolution(qualities, targetWidth, targetHeight) {
|
|
303
|
+
// Filter out qualities that exceed constraints
|
|
304
|
+
const validQualities = qualities.filter((q) => this.isWithinConstraints(q));
|
|
305
|
+
if (validQualities.length === 0)
|
|
306
|
+
return null;
|
|
307
|
+
// Sort by resolution (ascending)
|
|
308
|
+
const sorted = [...validQualities].sort((a, b) => {
|
|
309
|
+
const aPixels = (a.width ?? 0) * (a.height ?? 0);
|
|
310
|
+
const bPixels = (b.width ?? 0) * (b.height ?? 0);
|
|
311
|
+
return aPixels - bPixels;
|
|
312
|
+
});
|
|
313
|
+
// Find smallest quality that is >= target resolution
|
|
314
|
+
for (const q of sorted) {
|
|
315
|
+
const qWidth = q.width ?? 0;
|
|
316
|
+
const qHeight = q.height ?? 0;
|
|
317
|
+
if (qWidth >= targetWidth && qHeight >= targetHeight) {
|
|
318
|
+
return q;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// If no quality is large enough, return the highest available
|
|
322
|
+
return sorted[sorted.length - 1];
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Find a lower quality level
|
|
326
|
+
*/
|
|
327
|
+
findLowerQuality(qualities, current) {
|
|
328
|
+
const currentBitrate = current.bitrate ?? 0;
|
|
329
|
+
// Sort by bitrate descending
|
|
330
|
+
const sorted = [...qualities].sort((a, b) => (b.bitrate ?? 0) - (a.bitrate ?? 0));
|
|
331
|
+
// Find next lower bitrate
|
|
332
|
+
for (const q of sorted) {
|
|
333
|
+
if ((q.bitrate ?? 0) < currentBitrate) {
|
|
334
|
+
return q;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Find a higher quality level
|
|
341
|
+
*/
|
|
342
|
+
findHigherQuality(qualities, current) {
|
|
343
|
+
const currentBitrate = current.bitrate ?? 0;
|
|
344
|
+
// Sort by bitrate ascending
|
|
345
|
+
const sorted = [...qualities].sort((a, b) => (a.bitrate ?? 0) - (b.bitrate ?? 0));
|
|
346
|
+
// Find next higher bitrate
|
|
347
|
+
for (const q of sorted) {
|
|
348
|
+
if ((q.bitrate ?? 0) > currentBitrate) {
|
|
349
|
+
return q;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Check if quality is within configured constraints
|
|
356
|
+
*/
|
|
357
|
+
isWithinConstraints(quality) {
|
|
358
|
+
const { maxResolution, maxBitrate } = this.options;
|
|
359
|
+
if (quality.width && quality.width > maxResolution.width)
|
|
360
|
+
return false;
|
|
361
|
+
if (quality.height && quality.height > maxResolution.height)
|
|
362
|
+
return false;
|
|
363
|
+
if (quality.bitrate && quality.bitrate > maxBitrate)
|
|
364
|
+
return false;
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Select a quality level
|
|
369
|
+
*/
|
|
370
|
+
selectQuality(id) {
|
|
371
|
+
this.currentQualityId = id;
|
|
372
|
+
this.config.selectQuality(id);
|
|
373
|
+
// Notify callbacks
|
|
374
|
+
const qualities = this.config.getQualities();
|
|
375
|
+
const selected = qualities.find((q) => q.id === id);
|
|
376
|
+
if (selected) {
|
|
377
|
+
this.qualityChangeCallbacks.forEach((cb) => cb(selected));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Register callback for quality changes
|
|
382
|
+
*/
|
|
383
|
+
onQualityChange(callback) {
|
|
384
|
+
this.qualityChangeCallbacks.push(callback);
|
|
385
|
+
return () => {
|
|
386
|
+
const idx = this.qualityChangeCallbacks.indexOf(callback);
|
|
387
|
+
if (idx >= 0) {
|
|
388
|
+
this.qualityChangeCallbacks.splice(idx, 1);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Manually set quality (switches to manual mode temporarily)
|
|
394
|
+
*/
|
|
395
|
+
setQuality(id) {
|
|
396
|
+
this.selectQuality(id);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get current ABR mode
|
|
400
|
+
*/
|
|
401
|
+
getMode() {
|
|
402
|
+
return this.options.mode;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Set ABR mode at runtime.
|
|
406
|
+
* Restarts monitoring if video element is attached.
|
|
407
|
+
*/
|
|
408
|
+
setMode(mode) {
|
|
409
|
+
if (this.options.mode === mode)
|
|
410
|
+
return;
|
|
411
|
+
this.options.mode = mode;
|
|
412
|
+
this.log(`Mode changed to: ${mode}`);
|
|
413
|
+
// Restart with new mode if we have a video element
|
|
414
|
+
if (this.videoElement) {
|
|
415
|
+
const video = this.videoElement;
|
|
416
|
+
this.stop();
|
|
417
|
+
this.start(video);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Update ABR options
|
|
422
|
+
*/
|
|
423
|
+
updateOptions(options) {
|
|
424
|
+
this.options = { ...this.options, ...options };
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Get last ABR decision
|
|
428
|
+
*/
|
|
429
|
+
getLastDecision() {
|
|
430
|
+
return this.lastDecision;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Debug logging
|
|
434
|
+
*/
|
|
435
|
+
log(message) {
|
|
436
|
+
if (this.debug) {
|
|
437
|
+
console.debug(`[ABRController] ${message}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
ABRController.BANDWIDTH_HISTORY_SIZE = 10;
|
|
442
|
+
ABRController.MONITORING_INTERVAL_MS = 1000;
|
|
443
|
+
// D3: Separate upgrade/downgrade cooldowns
|
|
444
|
+
// Downgrade is immediate (0ms) for quick response to problems
|
|
445
|
+
// Upgrade requires 5s stability to prevent flapping
|
|
446
|
+
ABRController.UPGRADE_COOLDOWN_MS = 5000;
|
|
447
|
+
ABRController.DOWNGRADE_COOLDOWN_MS = 0;
|
|
448
|
+
// D2: Hysteresis bands to prevent oscillation at boundaries
|
|
449
|
+
// Upgrade: must exceed 1.5x to upgrade, stay until drops below 1.2x
|
|
450
|
+
// Downgrade: must drop below 0.8x to downgrade
|
|
451
|
+
ABRController.UPGRADE_HEADROOM = 1.5;
|
|
452
|
+
ABRController.UPGRADE_HOLD_THRESHOLD = 1.2;
|
|
453
|
+
ABRController.DOWNGRADE_THRESHOLD = 0.8;
|
|
454
|
+
|
|
455
|
+
exports.ABRController = ABRController;
|
|
456
|
+
//# sourceMappingURL=ABRController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ABRController.js","sources":["../../../../src/core/ABRController.ts"],"sourcesContent":["import type { ABRMode, ABROptions, PlaybackQuality, QualityLevel } from \"../types\";\nimport { TimerManager } from \"./TimerManager\";\n\n/**\n * Default ABR options\n */\nconst DEFAULT_OPTIONS: Required<ABROptions> = {\n mode: \"auto\",\n maxResolution: { width: 1920, height: 1080 },\n maxBitrate: 8000000, // 8 Mbps\n minBufferForUpgrade: 10,\n downgradeThreshold: 60,\n};\n\nexport interface ABRControllerConfig {\n /** ABR options */\n options?: Partial<ABROptions>;\n /** Callback to get available qualities */\n getQualities: () => QualityLevel[];\n /** Callback to select a quality */\n selectQuality: (id: string | \"auto\") => void;\n /** Callback to get current quality */\n getCurrentQuality?: () => QualityLevel | null;\n /** Callback to get bandwidth estimate (bits per second) - typically from player stats */\n getBandwidthEstimate?: () => Promise<number>;\n /** Debug logging */\n debug?: boolean;\n}\n\nexport type ABRDecision = \"upgrade\" | \"downgrade\" | \"maintain\" | \"none\";\n\n/**\n * ABRController - Adaptive Bitrate Controller\n *\n * Manages automatic quality selection based on:\n * - ABR_resize: Matches video resolution to viewport size\n * - ABR_bitrate: Switches quality based on playback performance\n * - auto: Combines both modes\n * - manual: No automatic switching\n *\n * @example\n * ```ts\n * const abr = new ABRController({\n * options: { mode: 'auto' },\n * getQualities: () => player.getQualities(),\n * selectQuality: (id) => player.selectQuality(id),\n * });\n *\n * abr.start(videoElement);\n * abr.onQualityChange((quality) => console.log('Quality:', quality.score));\n * ```\n */\nexport class ABRController {\n private options: Required<ABROptions>;\n private config: ABRControllerConfig;\n private videoElement: HTMLVideoElement | null = null;\n private currentQualityId: string | \"auto\" = \"auto\";\n private lastDecision: ABRDecision = \"none\";\n private lastDecisionTime = 0;\n private resizeObserver: ResizeObserver | null = null;\n private qualityChangeCallbacks: Array<(level: QualityLevel) => void> = [];\n private debug: boolean;\n\n // Active monitoring\n private timers = new TimerManager();\n private bandwidthHistory: number[] = [];\n private static readonly BANDWIDTH_HISTORY_SIZE = 10;\n private static readonly MONITORING_INTERVAL_MS = 1000;\n\n // D3: Separate upgrade/downgrade cooldowns\n // Downgrade is immediate (0ms) for quick response to problems\n // Upgrade requires 5s stability to prevent flapping\n private static readonly UPGRADE_COOLDOWN_MS = 5000;\n private static readonly DOWNGRADE_COOLDOWN_MS = 0;\n private lastUpgradeTime = 0;\n private lastDowngradeTime = 0;\n\n // D2: Hysteresis bands to prevent oscillation at boundaries\n // Upgrade: must exceed 1.5x to upgrade, stay until drops below 1.2x\n // Downgrade: must drop below 0.8x to downgrade\n private static readonly UPGRADE_HEADROOM = 1.5;\n private static readonly UPGRADE_HOLD_THRESHOLD = 1.2;\n private static readonly DOWNGRADE_THRESHOLD = 0.8;\n private currentQualityBitrate = 0;\n\n constructor(config: ABRControllerConfig) {\n this.options = { ...DEFAULT_OPTIONS, ...config.options };\n this.config = config;\n this.debug = config.debug ?? false;\n }\n\n /**\n * Start ABR control\n */\n start(videoElement: HTMLVideoElement): void {\n this.stop();\n this.videoElement = videoElement;\n\n if (this.options.mode === \"manual\") {\n this.log(\"Manual mode - no automatic ABR\");\n return;\n }\n\n // Setup resize observer for ABR_resize mode\n if (this.options.mode === \"resize\" || this.options.mode === \"auto\") {\n this.setupResizeObserver();\n }\n\n // Start active bandwidth monitoring for bitrate mode\n if (this.options.mode === \"bitrate\" || this.options.mode === \"auto\") {\n this.startActiveMonitoring();\n }\n }\n\n /**\n * Stop ABR control\n */\n stop(): void {\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n this.resizeObserver = null;\n }\n this.timers.destroy();\n this.videoElement = null;\n this.bandwidthHistory = [];\n }\n\n /**\n * Start active bandwidth monitoring loop\n * Continuously monitors bandwidth and proactively switches quality\n */\n private startActiveMonitoring(): void {\n this.timers.startInterval(\n () => this.checkBandwidthAndSwitch(),\n ABRController.MONITORING_INTERVAL_MS,\n \"monitoring\"\n );\n\n // Initial check\n this.checkBandwidthAndSwitch();\n }\n\n /**\n * Check current bandwidth and switch quality if needed\n *\n * Uses hysteresis (D2) and separate cooldowns (D3) to prevent oscillation:\n * - Downgrade: immediate response (0ms cooldown), triggers at 0.8x\n * - Upgrade: 5s cooldown, requires 1.5x headroom, holds until 1.2x\n */\n private async checkBandwidthAndSwitch(): Promise<void> {\n if (!this.videoElement) return;\n\n const now = Date.now();\n\n // Get bandwidth estimate from player stats\n const bandwidth = await this.getBandwidthEstimate();\n if (bandwidth <= 0) return;\n\n // Add to history\n this.bandwidthHistory.push(bandwidth);\n if (this.bandwidthHistory.length > ABRController.BANDWIDTH_HISTORY_SIZE) {\n this.bandwidthHistory.shift();\n }\n\n // Use smoothed bandwidth (average of history)\n const smoothedBandwidth = this.getSmoothedBandwidth();\n if (smoothedBandwidth <= 0) return;\n\n const qualities = this.config.getQualities();\n const currentQuality = this.config.getCurrentQuality?.();\n if (!currentQuality || qualities.length === 0) return;\n\n const currentBitrate = currentQuality.bitrate || 0;\n\n // Track current quality bitrate for hysteresis\n if (this.currentQualityBitrate !== currentBitrate) {\n this.currentQualityBitrate = currentBitrate;\n }\n\n // D3: Check for downgrade (immediate, no cooldown)\n if (now - this.lastDowngradeTime >= ABRController.DOWNGRADE_COOLDOWN_MS) {\n if (smoothedBandwidth < currentBitrate * ABRController.DOWNGRADE_THRESHOLD) {\n const lowerQuality = this.findLowerQuality(qualities, currentQuality);\n if (lowerQuality) {\n this.log(\n `ABR: bandwidth ${Math.round(smoothedBandwidth / 1000)}kbps < ${Math.round((currentBitrate * ABRController.DOWNGRADE_THRESHOLD) / 1000)}kbps threshold -> downgrading to ${lowerQuality.label}`\n );\n this.lastDecision = \"downgrade\";\n this.lastDecisionTime = now;\n this.lastDowngradeTime = now;\n this.selectQuality(lowerQuality.id);\n return;\n }\n }\n }\n\n // D3: Check for upgrade (5s cooldown required)\n if (now - this.lastUpgradeTime >= ABRController.UPGRADE_COOLDOWN_MS) {\n const higherQuality = this.findHigherQuality(qualities, currentQuality);\n if (higherQuality && this.isWithinConstraints(higherQuality)) {\n const targetBitrate = higherQuality.bitrate || 0;\n\n // D2: Hysteresis - require 1.5x headroom to upgrade\n // Once at a quality level, stay until bandwidth drops below 1.2x (not 1.0x)\n const shouldUpgrade = smoothedBandwidth >= targetBitrate * ABRController.UPGRADE_HEADROOM;\n const _canHoldHigher =\n smoothedBandwidth >= targetBitrate * ABRController.UPGRADE_HOLD_THRESHOLD;\n\n if (shouldUpgrade) {\n this.log(\n `ABR: bandwidth ${Math.round(smoothedBandwidth / 1000)}kbps >= ${Math.round((targetBitrate * ABRController.UPGRADE_HEADROOM) / 1000)}kbps headroom -> upgrading to ${higherQuality.label}`\n );\n this.lastDecision = \"upgrade\";\n this.lastDecisionTime = now;\n this.lastUpgradeTime = now;\n this.selectQuality(higherQuality.id);\n return;\n }\n }\n }\n }\n\n /**\n * Get bandwidth estimate from player stats\n */\n private async getBandwidthEstimate(): Promise<number> {\n // Try to get bandwidth from player stats\n if (this.config.getBandwidthEstimate) {\n const estimate = await this.config.getBandwidthEstimate();\n if (estimate > 0) return estimate;\n }\n\n // Fallback: estimate from buffer growth (rough)\n const video = this.videoElement;\n if (!video || video.buffered.length === 0) return 0;\n\n // This is a rough fallback - prefer player-specific estimates\n return 0;\n }\n\n /**\n * Get smoothed bandwidth from history\n */\n private getSmoothedBandwidth(): number {\n if (this.bandwidthHistory.length === 0) return 0;\n if (this.bandwidthHistory.length < 3) {\n // Need at least 3 samples for reliable estimate\n return 0;\n }\n const sum = this.bandwidthHistory.reduce((a, b) => a + b, 0);\n return sum / this.bandwidthHistory.length;\n }\n\n /**\n * Get current bandwidth estimate (for external use)\n */\n getCurrentBandwidth(): number {\n return this.getSmoothedBandwidth();\n }\n\n /**\n * Setup resize observer for viewport-based quality selection\n */\n private setupResizeObserver(): void {\n const video = this.videoElement;\n if (!video) return;\n\n this.resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width, height } = entry.contentRect;\n this.handleResize(width, height);\n }\n });\n\n // Observe the video element's container\n const container = video.parentElement;\n if (container) {\n this.resizeObserver.observe(container);\n }\n\n // Initial resize handling\n const rect = video.getBoundingClientRect();\n this.handleResize(rect.width, rect.height);\n }\n\n /**\n * Handle viewport resize (ABR_resize mode)\n */\n private handleResize(width: number, height: number): void {\n if (this.options.mode !== \"resize\" && this.options.mode !== \"auto\") {\n return;\n }\n\n const qualities = this.config.getQualities();\n if (qualities.length === 0) return;\n\n // Find best quality for viewport size\n const targetWidth = Math.min(width * window.devicePixelRatio, this.options.maxResolution.width);\n const targetHeight = Math.min(\n height * window.devicePixelRatio,\n this.options.maxResolution.height\n );\n\n const bestQuality = this.findBestQualityForResolution(qualities, targetWidth, targetHeight);\n\n if (bestQuality && bestQuality.id !== this.currentQualityId) {\n this.log(`Resize ABR: ${width}x${height} -> selecting ${bestQuality.label}`);\n this.selectQuality(bestQuality.id);\n }\n }\n\n /**\n * Handle quality degradation (ABR_bitrate mode)\n *\n * Called by QualityMonitor when playback quality drops\n */\n handleQualityDegraded(quality: PlaybackQuality): void {\n if (this.options.mode !== \"bitrate\" && this.options.mode !== \"auto\") {\n return;\n }\n\n // D3: Downgrade should be fast (0ms default), but still respects configured cooldown\n const now = Date.now();\n if (now - this.lastDowngradeTime < ABRController.DOWNGRADE_COOLDOWN_MS) {\n return;\n }\n\n if (quality.score < this.options.downgradeThreshold) {\n const qualities = this.config.getQualities();\n const currentQuality = this.config.getCurrentQuality?.();\n\n if (currentQuality) {\n // Find a lower quality level\n const lowerQuality = this.findLowerQuality(qualities, currentQuality);\n\n if (lowerQuality) {\n this.log(`Bitrate ABR: score ${quality.score} -> downgrading to ${lowerQuality.label}`);\n this.lastDecision = \"downgrade\";\n this.lastDecisionTime = now;\n this.lastDowngradeTime = now;\n this.selectQuality(lowerQuality.id);\n }\n }\n }\n }\n\n /**\n * Handle quality improvement opportunity\n *\n * Called when conditions are good enough to try higher quality\n */\n handleQualityImproved(quality: PlaybackQuality): void {\n if (this.options.mode !== \"bitrate\" && this.options.mode !== \"auto\") {\n return;\n }\n\n // D3: Upgrade requires sustained stability (5s default) to prevent flapping\n const now = Date.now();\n if (now - this.lastUpgradeTime < ABRController.UPGRADE_COOLDOWN_MS) {\n return;\n }\n\n // Only upgrade if buffer is healthy and quality is good\n if (quality.score >= 90 && quality.bufferedAhead >= this.options.minBufferForUpgrade) {\n const qualities = this.config.getQualities();\n const currentQuality = this.config.getCurrentQuality?.();\n\n if (currentQuality) {\n // Find a higher quality level\n const higherQuality = this.findHigherQuality(qualities, currentQuality);\n\n if (higherQuality && this.isWithinConstraints(higherQuality)) {\n this.log(`Bitrate ABR: score ${quality.score} -> upgrading to ${higherQuality.label}`);\n this.lastDecision = \"upgrade\";\n this.lastDecisionTime = now;\n this.lastUpgradeTime = now;\n this.selectQuality(higherQuality.id);\n }\n }\n }\n }\n\n /**\n * Find best quality level for given resolution\n */\n private findBestQualityForResolution(\n qualities: QualityLevel[],\n targetWidth: number,\n targetHeight: number\n ): QualityLevel | null {\n // Filter out qualities that exceed constraints\n const validQualities = qualities.filter((q) => this.isWithinConstraints(q));\n\n if (validQualities.length === 0) return null;\n\n // Sort by resolution (ascending)\n const sorted = [...validQualities].sort((a, b) => {\n const aPixels = (a.width ?? 0) * (a.height ?? 0);\n const bPixels = (b.width ?? 0) * (b.height ?? 0);\n return aPixels - bPixels;\n });\n\n // Find smallest quality that is >= target resolution\n for (const q of sorted) {\n const qWidth = q.width ?? 0;\n const qHeight = q.height ?? 0;\n\n if (qWidth >= targetWidth && qHeight >= targetHeight) {\n return q;\n }\n }\n\n // If no quality is large enough, return the highest available\n return sorted[sorted.length - 1];\n }\n\n /**\n * Find a lower quality level\n */\n private findLowerQuality(qualities: QualityLevel[], current: QualityLevel): QualityLevel | null {\n const currentBitrate = current.bitrate ?? 0;\n\n // Sort by bitrate descending\n const sorted = [...qualities].sort((a, b) => (b.bitrate ?? 0) - (a.bitrate ?? 0));\n\n // Find next lower bitrate\n for (const q of sorted) {\n if ((q.bitrate ?? 0) < currentBitrate) {\n return q;\n }\n }\n\n return null;\n }\n\n /**\n * Find a higher quality level\n */\n private findHigherQuality(qualities: QualityLevel[], current: QualityLevel): QualityLevel | null {\n const currentBitrate = current.bitrate ?? 0;\n\n // Sort by bitrate ascending\n const sorted = [...qualities].sort((a, b) => (a.bitrate ?? 0) - (b.bitrate ?? 0));\n\n // Find next higher bitrate\n for (const q of sorted) {\n if ((q.bitrate ?? 0) > currentBitrate) {\n return q;\n }\n }\n\n return null;\n }\n\n /**\n * Check if quality is within configured constraints\n */\n private isWithinConstraints(quality: QualityLevel): boolean {\n const { maxResolution, maxBitrate } = this.options;\n\n if (quality.width && quality.width > maxResolution.width) return false;\n if (quality.height && quality.height > maxResolution.height) return false;\n if (quality.bitrate && quality.bitrate > maxBitrate) return false;\n\n return true;\n }\n\n /**\n * Select a quality level\n */\n private selectQuality(id: string | \"auto\"): void {\n this.currentQualityId = id;\n this.config.selectQuality(id);\n\n // Notify callbacks\n const qualities = this.config.getQualities();\n const selected = qualities.find((q) => q.id === id);\n if (selected) {\n this.qualityChangeCallbacks.forEach((cb) => cb(selected));\n }\n }\n\n /**\n * Register callback for quality changes\n */\n onQualityChange(callback: (level: QualityLevel) => void): () => void {\n this.qualityChangeCallbacks.push(callback);\n return () => {\n const idx = this.qualityChangeCallbacks.indexOf(callback);\n if (idx >= 0) {\n this.qualityChangeCallbacks.splice(idx, 1);\n }\n };\n }\n\n /**\n * Manually set quality (switches to manual mode temporarily)\n */\n setQuality(id: string | \"auto\"): void {\n this.selectQuality(id);\n }\n\n /**\n * Get current ABR mode\n */\n getMode(): ABRMode {\n return this.options.mode;\n }\n\n /**\n * Set ABR mode at runtime.\n * Restarts monitoring if video element is attached.\n */\n setMode(mode: ABRMode): void {\n if (this.options.mode === mode) return;\n\n this.options.mode = mode;\n this.log(`Mode changed to: ${mode}`);\n\n // Restart with new mode if we have a video element\n if (this.videoElement) {\n const video = this.videoElement;\n this.stop();\n this.start(video);\n }\n }\n\n /**\n * Update ABR options\n */\n updateOptions(options: Partial<ABROptions>): void {\n this.options = { ...this.options, ...options };\n }\n\n /**\n * Get last ABR decision\n */\n getLastDecision(): ABRDecision {\n return this.lastDecision;\n }\n\n /**\n * Debug logging\n */\n private log(message: string): void {\n if (this.debug) {\n console.debug(`[ABRController] ${message}`);\n }\n }\n}\n\nexport default ABRController;\n"],"names":["TimerManager"],"mappings":";;;;AAGA;;AAEG;AACH,MAAM,eAAe,GAAyB;AAC5C,IAAA,IAAI,EAAE,MAAM;IACZ,aAAa,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;IAC5C,UAAU,EAAE,OAAO;AACnB,IAAA,mBAAmB,EAAE,EAAE;AACvB,IAAA,kBAAkB,EAAE,EAAE;CACvB;AAmBD;;;;;;;;;;;;;;;;;;;;AAoBG;MACU,aAAa,CAAA;AAiCxB,IAAA,WAAA,CAAY,MAA2B,EAAA;QA9B/B,IAAA,CAAA,YAAY,GAA4B,IAAI;QAC5C,IAAA,CAAA,gBAAgB,GAAoB,MAAM;QAC1C,IAAA,CAAA,YAAY,GAAgB,MAAM;QAClC,IAAA,CAAA,gBAAgB,GAAG,CAAC;QACpB,IAAA,CAAA,cAAc,GAA0B,IAAI;QAC5C,IAAA,CAAA,sBAAsB,GAAyC,EAAE;;AAIjE,QAAA,IAAA,CAAA,MAAM,GAAG,IAAIA,yBAAY,EAAE;QAC3B,IAAA,CAAA,gBAAgB,GAAa,EAAE;QAS/B,IAAA,CAAA,eAAe,GAAG,CAAC;QACnB,IAAA,CAAA,iBAAiB,GAAG,CAAC;QAQrB,IAAA,CAAA,qBAAqB,GAAG,CAAC;AAG/B,QAAA,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,eAAe,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE;AACxD,QAAA,IAAI,CAAC,MAAM,GAAG,MAAM;QACpB,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,KAAK;IACpC;AAEA;;AAEG;AACH,IAAA,KAAK,CAAC,YAA8B,EAAA;QAClC,IAAI,CAAC,IAAI,EAAE;AACX,QAAA,IAAI,CAAC,YAAY,GAAG,YAAY;QAEhC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE;AAClC,YAAA,IAAI,CAAC,GAAG,CAAC,gCAAgC,CAAC;YAC1C;QACF;;AAGA,QAAA,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE;YAClE,IAAI,CAAC,mBAAmB,EAAE;QAC5B;;AAGA,QAAA,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE;YACnE,IAAI,CAAC,qBAAqB,EAAE;QAC9B;IACF;AAEA;;AAEG;IACH,IAAI,GAAA;AACF,QAAA,IAAI,IAAI,CAAC,cAAc,EAAE;AACvB,YAAA,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE;AAChC,YAAA,IAAI,CAAC,cAAc,GAAG,IAAI;QAC5B;AACA,QAAA,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE;AACrB,QAAA,IAAI,CAAC,YAAY,GAAG,IAAI;AACxB,QAAA,IAAI,CAAC,gBAAgB,GAAG,EAAE;IAC5B;AAEA;;;AAGG;IACK,qBAAqB,GAAA;AAC3B,QAAA,IAAI,CAAC,MAAM,CAAC,aAAa,CACvB,MAAM,IAAI,CAAC,uBAAuB,EAAE,EACpC,aAAa,CAAC,sBAAsB,EACpC,YAAY,CACb;;QAGD,IAAI,CAAC,uBAAuB,EAAE;IAChC;AAEA;;;;;;AAMG;AACK,IAAA,MAAM,uBAAuB,GAAA;QACnC,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE;AAExB,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;;AAGtB,QAAA,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,oBAAoB,EAAE;QACnD,IAAI,SAAS,IAAI,CAAC;YAAE;;AAGpB,QAAA,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC;QACrC,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,GAAG,aAAa,CAAC,sBAAsB,EAAE;AACvE,YAAA,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE;QAC/B;;AAGA,QAAA,MAAM,iBAAiB,GAAG,IAAI,CAAC,oBAAoB,EAAE;QACrD,IAAI,iBAAiB,IAAI,CAAC;YAAE;QAE5B,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE;QAC5C,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,iBAAiB,IAAI;AACxD,QAAA,IAAI,CAAC,cAAc,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE;AAE/C,QAAA,MAAM,cAAc,GAAG,cAAc,CAAC,OAAO,IAAI,CAAC;;AAGlD,QAAA,IAAI,IAAI,CAAC,qBAAqB,KAAK,cAAc,EAAE;AACjD,YAAA,IAAI,CAAC,qBAAqB,GAAG,cAAc;QAC7C;;QAGA,IAAI,GAAG,GAAG,IAAI,CAAC,iBAAiB,IAAI,aAAa,CAAC,qBAAqB,EAAE;YACvE,IAAI,iBAAiB,GAAG,cAAc,GAAG,aAAa,CAAC,mBAAmB,EAAE;gBAC1E,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,cAAc,CAAC;gBACrE,IAAI,YAAY,EAAE;AAChB,oBAAA,IAAI,CAAC,GAAG,CACN,CAAA,eAAA,EAAkB,IAAI,CAAC,KAAK,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAA,OAAA,EAAU,IAAI,CAAC,KAAK,CAAC,CAAC,cAAc,GAAG,aAAa,CAAC,mBAAmB,IAAI,IAAI,CAAC,oCAAoC,YAAY,CAAC,KAAK,CAAA,CAAE,CAChM;AACD,oBAAA,IAAI,CAAC,YAAY,GAAG,WAAW;AAC/B,oBAAA,IAAI,CAAC,gBAAgB,GAAG,GAAG;AAC3B,oBAAA,IAAI,CAAC,iBAAiB,GAAG,GAAG;AAC5B,oBAAA,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,CAAC;oBACnC;gBACF;YACF;QACF;;QAGA,IAAI,GAAG,GAAG,IAAI,CAAC,eAAe,IAAI,aAAa,CAAC,mBAAmB,EAAE;YACnE,MAAM,aAAa,GAAG,IAAI,CAAC,iBAAiB,CAAC,SAAS,EAAE,cAAc,CAAC;YACvE,IAAI,aAAa,IAAI,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC,EAAE;AAC5D,gBAAA,MAAM,aAAa,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC;;;gBAIhD,MAAM,aAAa,GAAG,iBAAiB,IAAI,aAAa,GAAG,aAAa,CAAC,gBAAgB;gBAEvF,iBAAiB,IAAI,aAAa,GAAG,aAAa,CAAC;gBAErD,IAAI,aAAa,EAAE;AACjB,oBAAA,IAAI,CAAC,GAAG,CACN,CAAA,eAAA,EAAkB,IAAI,CAAC,KAAK,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAA,QAAA,EAAW,IAAI,CAAC,KAAK,CAAC,CAAC,aAAa,GAAG,aAAa,CAAC,gBAAgB,IAAI,IAAI,CAAC,iCAAiC,aAAa,CAAC,KAAK,CAAA,CAAE,CAC3L;AACD,oBAAA,IAAI,CAAC,YAAY,GAAG,SAAS;AAC7B,oBAAA,IAAI,CAAC,gBAAgB,GAAG,GAAG;AAC3B,oBAAA,IAAI,CAAC,eAAe,GAAG,GAAG;AAC1B,oBAAA,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC;oBACpC;gBACF;YACF;QACF;IACF;AAEA;;AAEG;AACK,IAAA,MAAM,oBAAoB,GAAA;;AAEhC,QAAA,IAAI,IAAI,CAAC,MAAM,CAAC,oBAAoB,EAAE;YACpC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,oBAAoB,EAAE;YACzD,IAAI,QAAQ,GAAG,CAAC;AAAE,gBAAA,OAAO,QAAQ;QACnC;;AAGA,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY;QAC/B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,CAAC;;AAGnD,QAAA,OAAO,CAAC;IACV;AAEA;;AAEG;IACK,oBAAoB,GAAA;AAC1B,QAAA,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,CAAC;QAChD,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE;;AAEpC,YAAA,OAAO,CAAC;QACV;QACA,MAAM,GAAG,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAC5D,QAAA,OAAO,GAAG,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM;IAC3C;AAEA;;AAEG;IACH,mBAAmB,GAAA;AACjB,QAAA,OAAO,IAAI,CAAC,oBAAoB,EAAE;IACpC;AAEA;;AAEG;IACK,mBAAmB,GAAA;AACzB,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY;AAC/B,QAAA,IAAI,CAAC,KAAK;YAAE;QAEZ,IAAI,CAAC,cAAc,GAAG,IAAI,cAAc,CAAC,CAAC,OAAO,KAAI;AACnD,YAAA,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE;gBAC3B,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,WAAW;AAC3C,gBAAA,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC;YAClC;AACF,QAAA,CAAC,CAAC;;AAGF,QAAA,MAAM,SAAS,GAAG,KAAK,CAAC,aAAa;QACrC,IAAI,SAAS,EAAE;AACb,YAAA,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,SAAS,CAAC;QACxC;;AAGA,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,qBAAqB,EAAE;QAC1C,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC;IAC5C;AAEA;;AAEG;IACK,YAAY,CAAC,KAAa,EAAE,MAAc,EAAA;AAChD,QAAA,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE;YAClE;QACF;QAEA,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE;AAC5C,QAAA,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE;;QAG5B,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,gBAAgB,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC;QAC/F,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAC3B,MAAM,GAAG,MAAM,CAAC,gBAAgB,EAChC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAClC;AAED,QAAA,MAAM,WAAW,GAAG,IAAI,CAAC,4BAA4B,CAAC,SAAS,EAAE,WAAW,EAAE,YAAY,CAAC;QAE3F,IAAI,WAAW,IAAI,WAAW,CAAC,EAAE,KAAK,IAAI,CAAC,gBAAgB,EAAE;AAC3D,YAAA,IAAI,CAAC,GAAG,CAAC,CAAA,YAAA,EAAe,KAAK,CAAA,CAAA,EAAI,MAAM,CAAA,cAAA,EAAiB,WAAW,CAAC,KAAK,CAAA,CAAE,CAAC;AAC5E,YAAA,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,CAAC;QACpC;IACF;AAEA;;;;AAIG;AACH,IAAA,qBAAqB,CAAC,OAAwB,EAAA;AAC5C,QAAA,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE;YACnE;QACF;;AAGA,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACtB,IAAI,GAAG,GAAG,IAAI,CAAC,iBAAiB,GAAG,aAAa,CAAC,qBAAqB,EAAE;YACtE;QACF;QAEA,IAAI,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE;YACnD,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE;YAC5C,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,iBAAiB,IAAI;YAExD,IAAI,cAAc,EAAE;;gBAElB,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,cAAc,CAAC;gBAErE,IAAI,YAAY,EAAE;AAChB,oBAAA,IAAI,CAAC,GAAG,CAAC,CAAA,mBAAA,EAAsB,OAAO,CAAC,KAAK,CAAA,mBAAA,EAAsB,YAAY,CAAC,KAAK,CAAA,CAAE,CAAC;AACvF,oBAAA,IAAI,CAAC,YAAY,GAAG,WAAW;AAC/B,oBAAA,IAAI,CAAC,gBAAgB,GAAG,GAAG;AAC3B,oBAAA,IAAI,CAAC,iBAAiB,GAAG,GAAG;AAC5B,oBAAA,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,CAAC;gBACrC;YACF;QACF;IACF;AAEA;;;;AAIG;AACH,IAAA,qBAAqB,CAAC,OAAwB,EAAA;AAC5C,QAAA,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE;YACnE;QACF;;AAGA,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACtB,IAAI,GAAG,GAAG,IAAI,CAAC,eAAe,GAAG,aAAa,CAAC,mBAAmB,EAAE;YAClE;QACF;;AAGA,QAAA,IAAI,OAAO,CAAC,KAAK,IAAI,EAAE,IAAI,OAAO,CAAC,aAAa,IAAI,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE;YACpF,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE;YAC5C,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,iBAAiB,IAAI;YAExD,IAAI,cAAc,EAAE;;gBAElB,MAAM,aAAa,GAAG,IAAI,CAAC,iBAAiB,CAAC,SAAS,EAAE,cAAc,CAAC;gBAEvE,IAAI,aAAa,IAAI,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC,EAAE;AAC5D,oBAAA,IAAI,CAAC,GAAG,CAAC,CAAA,mBAAA,EAAsB,OAAO,CAAC,KAAK,CAAA,iBAAA,EAAoB,aAAa,CAAC,KAAK,CAAA,CAAE,CAAC;AACtF,oBAAA,IAAI,CAAC,YAAY,GAAG,SAAS;AAC7B,oBAAA,IAAI,CAAC,gBAAgB,GAAG,GAAG;AAC3B,oBAAA,IAAI,CAAC,eAAe,GAAG,GAAG;AAC1B,oBAAA,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC;gBACtC;YACF;QACF;IACF;AAEA;;AAEG;AACK,IAAA,4BAA4B,CAClC,SAAyB,EACzB,WAAmB,EACnB,YAAoB,EAAA;;AAGpB,QAAA,MAAM,cAAc,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAE3E,QAAA,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,IAAI;;AAG5C,QAAA,MAAM,MAAM,GAAG,CAAC,GAAG,cAAc,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAI;AAC/C,YAAA,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;AAChD,YAAA,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;YAChD,OAAO,OAAO,GAAG,OAAO;AAC1B,QAAA,CAAC,CAAC;;AAGF,QAAA,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE;AACtB,YAAA,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC;AAC3B,YAAA,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC;YAE7B,IAAI,MAAM,IAAI,WAAW,IAAI,OAAO,IAAI,YAAY,EAAE;AACpD,gBAAA,OAAO,CAAC;YACV;QACF;;QAGA,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAClC;AAEA;;AAEG;IACK,gBAAgB,CAAC,SAAyB,EAAE,OAAqB,EAAA;AACvE,QAAA,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC;;AAG3C,QAAA,MAAM,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;;AAGjF,QAAA,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE;YACtB,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,IAAI,cAAc,EAAE;AACrC,gBAAA,OAAO,CAAC;YACV;QACF;AAEA,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;IACK,iBAAiB,CAAC,SAAyB,EAAE,OAAqB,EAAA;AACxE,QAAA,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC;;AAG3C,QAAA,MAAM,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;;AAGjF,QAAA,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE;YACtB,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,IAAI,cAAc,EAAE;AACrC,gBAAA,OAAO,CAAC;YACV;QACF;AAEA,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;AACK,IAAA,mBAAmB,CAAC,OAAqB,EAAA;QAC/C,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,OAAO;QAElD,IAAI,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,GAAG,aAAa,CAAC,KAAK;AAAE,YAAA,OAAO,KAAK;QACtE,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM;AAAE,YAAA,OAAO,KAAK;QACzE,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,GAAG,UAAU;AAAE,YAAA,OAAO,KAAK;AAEjE,QAAA,OAAO,IAAI;IACb;AAEA;;AAEG;AACK,IAAA,aAAa,CAAC,EAAmB,EAAA;AACvC,QAAA,IAAI,CAAC,gBAAgB,GAAG,EAAE;AAC1B,QAAA,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;;QAG7B,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE;AAC5C,QAAA,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;QACnD,IAAI,QAAQ,EAAE;AACZ,YAAA,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,CAAC;QAC3D;IACF;AAEA;;AAEG;AACH,IAAA,eAAe,CAAC,QAAuC,EAAA;AACrD,QAAA,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC;AAC1C,QAAA,OAAO,MAAK;YACV,MAAM,GAAG,GAAG,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,QAAQ,CAAC;AACzD,YAAA,IAAI,GAAG,IAAI,CAAC,EAAE;gBACZ,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;YAC5C;AACF,QAAA,CAAC;IACH;AAEA;;AAEG;AACH,IAAA,UAAU,CAAC,EAAmB,EAAA;AAC5B,QAAA,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;IACxB;AAEA;;AAEG;IACH,OAAO,GAAA;AACL,QAAA,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI;IAC1B;AAEA;;;AAGG;AACH,IAAA,OAAO,CAAC,IAAa,EAAA;AACnB,QAAA,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI;YAAE;AAEhC,QAAA,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI;AACxB,QAAA,IAAI,CAAC,GAAG,CAAC,oBAAoB,IAAI,CAAA,CAAE,CAAC;;AAGpC,QAAA,IAAI,IAAI,CAAC,YAAY,EAAE;AACrB,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY;YAC/B,IAAI,CAAC,IAAI,EAAE;AACX,YAAA,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QACnB;IACF;AAEA;;AAEG;AACH,IAAA,aAAa,CAAC,OAA4B,EAAA;AACxC,QAAA,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,EAAE;IAChD;AAEA;;AAEG;IACH,eAAe,GAAA;QACb,OAAO,IAAI,CAAC,YAAY;IAC1B;AAEA;;AAEG;AACK,IAAA,GAAG,CAAC,OAAe,EAAA;AACzB,QAAA,IAAI,IAAI,CAAC,KAAK,EAAE;AACd,YAAA,OAAO,CAAC,KAAK,CAAC,mBAAmB,OAAO,CAAA,CAAE,CAAC;QAC7C;IACF;;AAlewB,aAAA,CAAA,sBAAsB,GAAG,EAAH;AACtB,aAAA,CAAA,sBAAsB,GAAG,IAAH;AAE9C;AACA;AACA;AACwB,aAAA,CAAA,mBAAmB,GAAG,IAAH;AACnB,aAAA,CAAA,qBAAqB,GAAG,CAAH;AAI7C;AACA;AACA;AACwB,aAAA,CAAA,gBAAgB,GAAG,GAAH;AAChB,aAAA,CAAA,sBAAsB,GAAG,GAAH;AACtB,aAAA,CAAA,mBAAmB,GAAG,GAAH;;;;"}
|