avbridge 2.2.0 → 2.3.0

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 (121) hide show
  1. package/CHANGELOG.md +125 -1
  2. package/NOTICE.md +2 -2
  3. package/README.md +100 -74
  4. package/THIRD_PARTY_LICENSES.md +2 -2
  5. package/dist/avi-2JPBSHGA.js +183 -0
  6. package/dist/avi-2JPBSHGA.js.map +1 -0
  7. package/dist/avi-F6WZJK5T.cjs +185 -0
  8. package/dist/avi-F6WZJK5T.cjs.map +1 -0
  9. package/dist/{avi-GCGM7OJI.js → avi-NJXAXUXK.js} +9 -3
  10. package/dist/avi-NJXAXUXK.js.map +1 -0
  11. package/dist/{avi-6SJLWIWW.cjs → avi-W6L3BTWU.cjs} +10 -4
  12. package/dist/avi-W6L3BTWU.cjs.map +1 -0
  13. package/dist/{chunk-ILKDNBSE.js → chunk-2PGRFCWB.js} +59 -10
  14. package/dist/chunk-2PGRFCWB.js.map +1 -0
  15. package/dist/chunk-5YAWWKA3.js +18 -0
  16. package/dist/chunk-5YAWWKA3.js.map +1 -0
  17. package/dist/chunk-6UUT4BEA.cjs +219 -0
  18. package/dist/chunk-6UUT4BEA.cjs.map +1 -0
  19. package/dist/{chunk-OE66B34H.cjs → chunk-7RGG6ME7.cjs} +562 -94
  20. package/dist/chunk-7RGG6ME7.cjs.map +1 -0
  21. package/dist/{chunk-WD2ZNQA7.js → chunk-DCSOQH2N.js} +7 -4
  22. package/dist/chunk-DCSOQH2N.js.map +1 -0
  23. package/dist/chunk-F3LQJKXK.cjs +20 -0
  24. package/dist/chunk-F3LQJKXK.cjs.map +1 -0
  25. package/dist/chunk-IAYKFGFG.js +200 -0
  26. package/dist/chunk-IAYKFGFG.js.map +1 -0
  27. package/dist/chunk-NNVOHKXJ.cjs +204 -0
  28. package/dist/chunk-NNVOHKXJ.cjs.map +1 -0
  29. package/dist/{chunk-C5VA5U5O.js → chunk-NV7ILLWH.js} +556 -92
  30. package/dist/chunk-NV7ILLWH.js.map +1 -0
  31. package/dist/{chunk-HZLQNKFN.cjs → chunk-QQXBPW72.js} +54 -15
  32. package/dist/chunk-QQXBPW72.js.map +1 -0
  33. package/dist/chunk-XKPSTC34.cjs +210 -0
  34. package/dist/chunk-XKPSTC34.cjs.map +1 -0
  35. package/dist/{chunk-L4NPOJ36.cjs → chunk-Z33SBWL5.cjs} +7 -4
  36. package/dist/chunk-Z33SBWL5.cjs.map +1 -0
  37. package/dist/element-browser.js +631 -103
  38. package/dist/element-browser.js.map +1 -1
  39. package/dist/element.cjs +4 -4
  40. package/dist/element.d.cts +1 -1
  41. package/dist/element.d.ts +1 -1
  42. package/dist/element.js +3 -3
  43. package/dist/index.cjs +174 -26
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.d.cts +48 -4
  46. package/dist/index.d.ts +48 -4
  47. package/dist/index.js +93 -12
  48. package/dist/index.js.map +1 -1
  49. package/dist/libav-http-reader-AZLE7YFS.cjs +16 -0
  50. package/dist/{libav-http-reader-FPYDBMYK.cjs.map → libav-http-reader-AZLE7YFS.cjs.map} +1 -1
  51. package/dist/libav-http-reader-WXG3Z7AI.js +3 -0
  52. package/dist/{libav-http-reader-NQJVY273.js.map → libav-http-reader-WXG3Z7AI.js.map} +1 -1
  53. package/dist/{player-DUyvltvy.d.cts → player-B6WB74RD.d.cts} +63 -3
  54. package/dist/{player-DUyvltvy.d.ts → player-B6WB74RD.d.ts} +63 -3
  55. package/dist/player.cjs +5500 -0
  56. package/dist/player.cjs.map +1 -0
  57. package/dist/player.d.cts +649 -0
  58. package/dist/player.d.ts +649 -0
  59. package/dist/player.js +5498 -0
  60. package/dist/player.js.map +1 -0
  61. package/dist/source-73CAH6HW.cjs +28 -0
  62. package/dist/{source-CN43EI7Z.cjs.map → source-73CAH6HW.cjs.map} +1 -1
  63. package/dist/source-F656KYYV.js +3 -0
  64. package/dist/{source-FFZ7TW2B.js.map → source-F656KYYV.js.map} +1 -1
  65. package/dist/source-QJR3OHTW.js +3 -0
  66. package/dist/source-QJR3OHTW.js.map +1 -0
  67. package/dist/source-VB74JQ7Z.cjs +28 -0
  68. package/dist/source-VB74JQ7Z.cjs.map +1 -0
  69. package/dist/variant-routing-434STYAB.js +3 -0
  70. package/dist/{variant-routing-JOBWXYKD.js.map → variant-routing-434STYAB.js.map} +1 -1
  71. package/dist/variant-routing-HONNAA6R.cjs +12 -0
  72. package/dist/{variant-routing-GOHB2RZN.cjs.map → variant-routing-HONNAA6R.cjs.map} +1 -1
  73. package/package.json +9 -1
  74. package/src/classify/rules.ts +27 -5
  75. package/src/convert/remux.ts +8 -0
  76. package/src/convert/transcode.ts +41 -8
  77. package/src/element/avbridge-player.ts +845 -0
  78. package/src/element/player-icons.ts +25 -0
  79. package/src/element/player-styles.ts +472 -0
  80. package/src/errors.ts +47 -0
  81. package/src/index.ts +23 -0
  82. package/src/player-element.ts +18 -0
  83. package/src/player.ts +127 -27
  84. package/src/plugins/builtin.ts +2 -2
  85. package/src/probe/avi.ts +4 -0
  86. package/src/probe/index.ts +40 -10
  87. package/src/strategies/fallback/audio-output.ts +31 -0
  88. package/src/strategies/fallback/decoder.ts +83 -2
  89. package/src/strategies/fallback/index.ts +34 -1
  90. package/src/strategies/fallback/variant-routing.ts +7 -13
  91. package/src/strategies/fallback/video-renderer.ts +129 -33
  92. package/src/strategies/hybrid/decoder.ts +131 -20
  93. package/src/strategies/hybrid/index.ts +36 -2
  94. package/src/strategies/remux/index.ts +13 -1
  95. package/src/strategies/remux/mse.ts +12 -2
  96. package/src/strategies/remux/pipeline.ts +6 -0
  97. package/src/subtitles/index.ts +7 -3
  98. package/src/types.ts +53 -1
  99. package/src/util/libav-http-reader.ts +5 -1
  100. package/src/util/source.ts +28 -8
  101. package/src/util/transport.ts +26 -0
  102. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  103. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  104. package/dist/avi-6SJLWIWW.cjs.map +0 -1
  105. package/dist/avi-GCGM7OJI.js.map +0 -1
  106. package/dist/chunk-C5VA5U5O.js.map +0 -1
  107. package/dist/chunk-HZLQNKFN.cjs.map +0 -1
  108. package/dist/chunk-ILKDNBSE.js.map +0 -1
  109. package/dist/chunk-J5MCMN3S.js +0 -27
  110. package/dist/chunk-J5MCMN3S.js.map +0 -1
  111. package/dist/chunk-L4NPOJ36.cjs.map +0 -1
  112. package/dist/chunk-NZU7W256.cjs +0 -29
  113. package/dist/chunk-NZU7W256.cjs.map +0 -1
  114. package/dist/chunk-OE66B34H.cjs.map +0 -1
  115. package/dist/chunk-WD2ZNQA7.js.map +0 -1
  116. package/dist/libav-http-reader-FPYDBMYK.cjs +0 -16
  117. package/dist/libav-http-reader-NQJVY273.js +0 -3
  118. package/dist/source-CN43EI7Z.cjs +0 -28
  119. package/dist/source-FFZ7TW2B.js +0 -3
  120. package/dist/variant-routing-GOHB2RZN.cjs +0 -12
  121. package/dist/variant-routing-JOBWXYKD.js +0 -3
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Inline SVG icons for <avbridge-player> controls.
3
+ * All icons are 24x24 viewBox, stroke-based where possible for
4
+ * easy theming via CSS `color` (currentColor fill/stroke).
5
+ */
6
+
7
+ export const ICON_PLAY = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>`;
8
+
9
+ export const ICON_PAUSE = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 19h4V5H6zm8-14v14h4V5z"/></svg>`;
10
+
11
+ export const ICON_VOLUME_UP = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/></svg>`;
12
+
13
+ export const ICON_VOLUME_OFF = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/></svg>`;
14
+
15
+ export const ICON_SETTINGS = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.49.49 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 00-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>`;
16
+
17
+ export const ICON_FULLSCREEN = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>`;
18
+
19
+ export const ICON_FULLSCREEN_EXIT = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/></svg>`;
20
+
21
+ export const ICON_REPLAY_10 = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M11.99 5V1l-5 5 5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6h-2c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/><text x="10" y="16" font-size="8" text-anchor="middle" fill="currentColor" font-family="sans-serif">10</text></svg>`;
22
+
23
+ export const ICON_FORWARD_10 = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18 13c0 3.31-2.69 6-6 6s-6-2.69-6-6 2.69-6 6-6v4l5-5-5-5v4c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8h-2z"/><text x="14" y="16" font-size="8" text-anchor="middle" fill="currentColor" font-family="sans-serif">10</text></svg>`;
24
+
25
+ export const ICON_SPEED = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M10 8v8l6-4-6-4zm10.77-1.67L22 7.67l-.77-1.34c-.3-.53-.8-.93-1.41-1.05l-1.7-.3c-.16-.43-.36-.84-.6-1.22l.8-1.54-.77-1.34-.77 1.34.8 1.54c-.24.38-.44.79-.6 1.22l-1.7.3c-.61.12-1.11.52-1.41 1.05L13 7.67l1.23-1.34c-.3.53-.3 1.15 0 1.67L15.46 10l-1.23 2.34c-.3.53-.3 1.15 0 1.67L15.46 16l-1.23 2.34.77 1.34.77-1.34-1.23-2.34c.3-.53.3-1.15 0-1.67L13.31 12l1.23-2.34c.3-.53.3-1.15 0-1.67z"/></svg>`;
@@ -0,0 +1,472 @@
1
+ /**
2
+ * Shadow DOM CSS for <avbridge-player>.
3
+ * YouTube-inspired dark theme. All controls use ::part() for external styling.
4
+ */
5
+
6
+ export const PLAYER_STYLES = /* css */ `
7
+ :host {
8
+ display: block;
9
+ position: relative;
10
+ width: 100%;
11
+ background: #000;
12
+ overflow: hidden;
13
+ user-select: none;
14
+ -webkit-user-select: none;
15
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
16
+ color: #fff;
17
+ line-height: 1;
18
+ }
19
+
20
+ :host(:fullscreen),
21
+ :host(:fullscreen) .avp {
22
+ width: 100vw;
23
+ height: 100vh;
24
+ }
25
+
26
+ /* ── Container ────────────────────────────────────────────────────────── */
27
+
28
+ .avp {
29
+ position: relative;
30
+ width: 100%;
31
+ height: 100%;
32
+ cursor: pointer;
33
+ }
34
+
35
+ .avp avbridge-video {
36
+ display: block;
37
+ width: 100%;
38
+ height: 100%;
39
+ }
40
+
41
+ /* ── Center overlay ───────────────────────────────────────────────────── */
42
+
43
+ .avp-overlay {
44
+ position: absolute;
45
+ inset: 0;
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ pointer-events: none;
50
+ z-index: 2;
51
+ }
52
+
53
+ .avp-overlay-btn {
54
+ width: 68px;
55
+ height: 68px;
56
+ border-radius: 50%;
57
+ background: rgba(0, 0, 0, 0.6);
58
+ border: none;
59
+ color: #fff;
60
+ cursor: pointer;
61
+ pointer-events: auto;
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ transition: opacity 0.2s, transform 0.15s;
66
+ opacity: 0;
67
+ transform: scale(0.9);
68
+ }
69
+
70
+ .avp-overlay-btn svg {
71
+ width: 36px;
72
+ height: 36px;
73
+ }
74
+
75
+ .avp-overlay-btn:hover {
76
+ background: rgba(0, 0, 0, 0.75);
77
+ transform: scale(1);
78
+ }
79
+
80
+ :host([data-state="idle"]) .avp-overlay-btn,
81
+ :host([data-state="paused"]) .avp-overlay-btn {
82
+ opacity: 1;
83
+ transform: scale(1);
84
+ }
85
+
86
+ /* ── Loading spinner ──────────────────────────────────────────────────── */
87
+
88
+ .avp-spinner {
89
+ width: 48px;
90
+ height: 48px;
91
+ border: 4px solid rgba(255, 255, 255, 0.3);
92
+ border-top-color: #fff;
93
+ border-radius: 50%;
94
+ display: none;
95
+ pointer-events: none;
96
+ }
97
+
98
+ :host([data-state="loading"]) .avp-spinner,
99
+ :host([data-state="buffering"]) .avp-spinner {
100
+ display: block;
101
+ animation: avp-spin 0.8s linear infinite;
102
+ }
103
+
104
+ :host([data-state="loading"]) .avp-overlay-btn,
105
+ :host([data-state="buffering"]) .avp-overlay-btn {
106
+ display: none;
107
+ }
108
+
109
+ @keyframes avp-spin {
110
+ to { transform: rotate(360deg); }
111
+ }
112
+
113
+ /* ── Double-tap ripple ────────────────────────────────────────────────── */
114
+
115
+ .avp-ripple {
116
+ position: absolute;
117
+ top: 50%;
118
+ transform: translateY(-50%);
119
+ width: 100px;
120
+ height: 100px;
121
+ border-radius: 50%;
122
+ background: rgba(255, 255, 255, 0.3);
123
+ display: flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ pointer-events: none;
127
+ opacity: 0;
128
+ z-index: 3;
129
+ }
130
+
131
+ .avp-ripple svg { width: 28px; height: 28px; }
132
+ .avp-ripple-left { left: 15%; }
133
+ .avp-ripple-right { right: 15%; }
134
+
135
+ .avp-ripple.active {
136
+ animation: avp-ripple 0.5s ease-out;
137
+ }
138
+
139
+ @keyframes avp-ripple {
140
+ 0% { opacity: 1; transform: translateY(-50%) scale(0.5); }
141
+ 100% { opacity: 0; transform: translateY(-50%) scale(1.5); }
142
+ }
143
+
144
+ /* ── Speed indicator (tap-and-hold) ───────────────────────────────────── */
145
+
146
+ .avp-speed-indicator {
147
+ position: absolute;
148
+ top: 12px;
149
+ left: 50%;
150
+ transform: translateX(-50%);
151
+ background: rgba(0, 0, 0, 0.7);
152
+ padding: 6px 16px;
153
+ border-radius: 20px;
154
+ font-size: 14px;
155
+ font-weight: 600;
156
+ pointer-events: none;
157
+ opacity: 0;
158
+ z-index: 4;
159
+ transition: opacity 0.15s;
160
+ }
161
+
162
+ .avp-speed-indicator.active { opacity: 1; }
163
+
164
+ /* ── Controls bar ─────────────────────────────────────────────────────── */
165
+
166
+ .avp-controls {
167
+ position: absolute;
168
+ bottom: 0;
169
+ left: 0;
170
+ right: 0;
171
+ z-index: 5;
172
+ padding: 0 12px 8px;
173
+ background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
174
+ display: flex;
175
+ flex-direction: column;
176
+ gap: 4px;
177
+ opacity: 1;
178
+ transition: opacity 0.25s;
179
+ }
180
+
181
+ :host([data-controls-hidden]) .avp-controls {
182
+ opacity: 0;
183
+ pointer-events: none;
184
+ }
185
+
186
+ :host([data-controls-hidden]) { cursor: none; }
187
+
188
+ /* ── Seek bar ─────────────────────────────────────────────────────────── */
189
+
190
+ .avp-seek {
191
+ position: relative;
192
+ height: 20px;
193
+ display: flex;
194
+ align-items: center;
195
+ cursor: pointer;
196
+ }
197
+
198
+ .avp-seek-track {
199
+ position: absolute;
200
+ left: 0;
201
+ right: 0;
202
+ height: 3px;
203
+ background: rgba(255, 255, 255, 0.2);
204
+ border-radius: 2px;
205
+ overflow: hidden;
206
+ transition: height 0.1s;
207
+ }
208
+
209
+ .avp-seek:hover .avp-seek-track { height: 5px; }
210
+
211
+ .avp-seek-buffered {
212
+ position: absolute;
213
+ left: 0;
214
+ height: 100%;
215
+ background: rgba(255, 255, 255, 0.35);
216
+ border-radius: inherit;
217
+ }
218
+
219
+ .avp-seek-progress {
220
+ position: absolute;
221
+ left: 0;
222
+ height: 100%;
223
+ background: #f00;
224
+ border-radius: inherit;
225
+ }
226
+
227
+ .avp-seek-input {
228
+ position: absolute;
229
+ width: 100%;
230
+ height: 100%;
231
+ margin: 0;
232
+ opacity: 0;
233
+ /* Disable pointer events — we handle clicks/drags manually on .avp-seek
234
+ * so the click position maps linearly across the full track width.
235
+ * The input is still used for keyboard accessibility. */
236
+ pointer-events: none;
237
+ z-index: 1;
238
+ }
239
+
240
+ .avp-seek-thumb {
241
+ position: absolute;
242
+ width: 12px;
243
+ height: 12px;
244
+ border-radius: 50%;
245
+ background: #f00;
246
+ top: 50%;
247
+ /* Thumb center follows the cursor exactly: left = pct% of track width,
248
+ * then translate(-50%) centers the thumb on that point. Matches the
249
+ * manual pointer-to-time mapping in _timeFromSeekPointer which is
250
+ * also linear from 0% to 100% of the track width. */
251
+ left: calc(var(--pct, 0) * 1%);
252
+ transform: translate(-50%, -50%) scale(0);
253
+ transition: transform 0.1s;
254
+ pointer-events: none;
255
+ }
256
+
257
+ .avp-seek:hover .avp-seek-thumb { transform: translate(-50%, -50%) scale(1); }
258
+
259
+ .avp-seek-tooltip {
260
+ position: absolute;
261
+ bottom: 24px;
262
+ background: rgba(0, 0, 0, 0.8);
263
+ padding: 4px 8px;
264
+ border-radius: 3px;
265
+ font-size: 12px;
266
+ white-space: nowrap;
267
+ transform: translateX(-50%);
268
+ pointer-events: none;
269
+ display: none;
270
+ }
271
+
272
+ .avp-seek:hover .avp-seek-tooltip { display: block; }
273
+
274
+ /* ── Bottom row ───────────────────────────────────────────────────────── */
275
+
276
+ .avp-bottom {
277
+ display: flex;
278
+ align-items: center;
279
+ gap: 8px;
280
+ height: 36px;
281
+ }
282
+
283
+ .avp-btn {
284
+ background: none;
285
+ border: none;
286
+ color: #fff;
287
+ padding: 4px;
288
+ cursor: pointer;
289
+ border-radius: 4px;
290
+ display: flex;
291
+ align-items: center;
292
+ justify-content: center;
293
+ opacity: 0.9;
294
+ transition: opacity 0.1s;
295
+ }
296
+
297
+ .avp-btn:hover { opacity: 1; }
298
+ .avp-btn svg { width: 24px; height: 24px; }
299
+
300
+ /* ── Volume ───────────────────────────────────────────────────────────── */
301
+
302
+ .avp-volume {
303
+ display: flex;
304
+ align-items: center;
305
+ gap: 0;
306
+ }
307
+
308
+ .avp-volume-slider {
309
+ width: 0;
310
+ overflow: hidden;
311
+ transition: width 0.15s;
312
+ display: flex;
313
+ align-items: center;
314
+ /* Extra padding so the thumb isn't clipped at track edges */
315
+ padding: 6px 0;
316
+ margin: -6px 0;
317
+ }
318
+
319
+ .avp-volume:hover .avp-volume-slider { width: 68px; }
320
+
321
+ .avp-volume-input {
322
+ width: 60px;
323
+ height: 4px;
324
+ -webkit-appearance: none;
325
+ appearance: none;
326
+ background: rgba(255, 255, 255, 0.3);
327
+ border-radius: 2px;
328
+ outline: none;
329
+ cursor: pointer;
330
+ /* Prevent thumb clipping — the thumb is taller than the track */
331
+ overflow: visible;
332
+ margin: 0;
333
+ }
334
+
335
+ .avp-volume-input::-webkit-slider-thumb {
336
+ -webkit-appearance: none;
337
+ width: 12px;
338
+ height: 12px;
339
+ border-radius: 50%;
340
+ background: #fff;
341
+ cursor: pointer;
342
+ }
343
+
344
+ .avp-volume-input::-moz-range-thumb {
345
+ width: 12px;
346
+ height: 12px;
347
+ border-radius: 50%;
348
+ background: #fff;
349
+ border: none;
350
+ cursor: pointer;
351
+ }
352
+
353
+ /* ── Time display ─────────────────────────────────────────────────────── */
354
+
355
+ .avp-time {
356
+ font-size: 13px;
357
+ font-variant-numeric: tabular-nums;
358
+ white-space: nowrap;
359
+ opacity: 0.9;
360
+ }
361
+
362
+ /* ── Strategy badge ───────────────────────────────────────────────────── */
363
+
364
+ .avp-badge {
365
+ font-size: 11px;
366
+ font-weight: 600;
367
+ padding: 2px 8px;
368
+ border-radius: 3px;
369
+ background: rgba(255, 255, 255, 0.15);
370
+ text-transform: uppercase;
371
+ letter-spacing: 0.5px;
372
+ opacity: 0.8;
373
+ }
374
+
375
+ .avp-badge[data-strategy="native"] { background: rgba(45, 106, 79, 0.7); }
376
+ .avp-badge[data-strategy="remux"] { background: rgba(30, 96, 145, 0.7); }
377
+ .avp-badge[data-strategy="hybrid"] { background: rgba(199, 125, 255, 0.4); }
378
+ .avp-badge[data-strategy="fallback"] { background: rgba(157, 78, 221, 0.5); }
379
+
380
+ /* ── Spacer ───────────────────────────────────────────────────────────── */
381
+
382
+ .avp-spacer { flex: 1; }
383
+
384
+ /* ── Settings menu ────────────────────────────────────────────────────── */
385
+
386
+ .avp-settings {
387
+ position: absolute;
388
+ bottom: 52px;
389
+ right: 12px;
390
+ background: rgba(28, 28, 28, 0.95);
391
+ border-radius: 8px;
392
+ min-width: 220px;
393
+ max-height: 300px;
394
+ overflow-y: auto;
395
+ display: none;
396
+ z-index: 10;
397
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
398
+ }
399
+
400
+ .avp-settings.open { display: block; }
401
+
402
+ .avp-settings-section {
403
+ padding: 8px 0;
404
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
405
+ }
406
+
407
+ .avp-settings-section:last-child { border-bottom: none; }
408
+
409
+ .avp-settings-label {
410
+ padding: 4px 16px;
411
+ font-size: 11px;
412
+ text-transform: uppercase;
413
+ letter-spacing: 0.5px;
414
+ opacity: 0.5;
415
+ }
416
+
417
+ .avp-settings-item {
418
+ display: flex;
419
+ align-items: center;
420
+ padding: 8px 16px;
421
+ font-size: 13px;
422
+ cursor: pointer;
423
+ transition: background 0.1s;
424
+ }
425
+
426
+ .avp-settings-item:hover { background: rgba(255, 255, 255, 0.1); }
427
+
428
+ .avp-settings-item.active {
429
+ color: #3ea6ff;
430
+ }
431
+
432
+ .avp-settings-item.active::before {
433
+ content: "\\2713";
434
+ margin-right: 8px;
435
+ font-weight: bold;
436
+ }
437
+
438
+ /* ── Stats for nerds ──────────────────────────────────────────────────── */
439
+
440
+ .avp-stats {
441
+ position: absolute;
442
+ top: 12px;
443
+ left: 12px;
444
+ background: rgba(0, 0, 0, 0.8);
445
+ padding: 12px 16px;
446
+ border-radius: 6px;
447
+ font-size: 12px;
448
+ font-family: "SF Mono", "Menlo", "Consolas", monospace;
449
+ line-height: 1.6;
450
+ white-space: pre;
451
+ pointer-events: auto;
452
+ z-index: 6;
453
+ max-width: 400px;
454
+ overflow: auto;
455
+ display: none;
456
+ }
457
+
458
+ .avp-stats.open { display: block; }
459
+
460
+ /* ── Mobile adjustments ───────────────────────────────────────────────── */
461
+
462
+ @media (pointer: coarse) {
463
+ .avp-btn svg { width: 28px; height: 28px; }
464
+ .avp-btn { padding: 8px; }
465
+ .avp-seek-track { height: 4px; }
466
+ .avp-seek:hover .avp-seek-track { height: 4px; }
467
+ .avp-seek-thumb { transform: translate(-50%, -50%) scale(1); }
468
+ .avp-volume:hover .avp-volume-slider { width: 0; }
469
+ .avp-overlay-btn { width: 56px; height: 56px; }
470
+ .avp-overlay-btn svg { width: 30px; height: 30px; }
471
+ }
472
+ `;
package/src/errors.ts ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Structured error with a machine-readable code and human-readable
3
+ * recovery hint. Consumers can switch on `error.code` for programmatic
4
+ * handling and show `error.recovery` in UI.
5
+ *
6
+ * All codes use the `ERR_AVBRIDGE_` prefix to avoid collisions.
7
+ */
8
+ export class AvbridgeError extends Error {
9
+ override name = "AvbridgeError";
10
+
11
+ constructor(
12
+ /** Machine-readable error code. */
13
+ public readonly code: string,
14
+ message: string,
15
+ /** Human-readable recovery suggestion. */
16
+ public readonly recovery?: string,
17
+ options?: ErrorOptions,
18
+ ) {
19
+ super(message, options);
20
+ }
21
+ }
22
+
23
+ // ── Error codes ────────────────────────────────────────────────────────
24
+
25
+ // Probe
26
+ export const ERR_PROBE_FAILED = "ERR_AVBRIDGE_PROBE_FAILED";
27
+ export const ERR_PROBE_UNKNOWN_CONTAINER = "ERR_AVBRIDGE_PROBE_UNKNOWN_CONTAINER";
28
+ export const ERR_PROBE_FETCH_FAILED = "ERR_AVBRIDGE_PROBE_FETCH_FAILED";
29
+
30
+ // Codec / strategy
31
+ export const ERR_CODEC_NOT_SUPPORTED = "ERR_AVBRIDGE_CODEC_NOT_SUPPORTED";
32
+ export const ERR_STRATEGY_FAILED = "ERR_AVBRIDGE_STRATEGY_FAILED";
33
+ export const ERR_ALL_STRATEGIES_EXHAUSTED = "ERR_AVBRIDGE_ALL_STRATEGIES_EXHAUSTED";
34
+
35
+ // Player lifecycle
36
+ export const ERR_PLAYER_NOT_READY = "ERR_AVBRIDGE_PLAYER_NOT_READY";
37
+
38
+ // Transport / network
39
+ export const ERR_RANGE_NOT_SUPPORTED = "ERR_AVBRIDGE_RANGE_NOT_SUPPORTED";
40
+ export const ERR_FETCH_FAILED = "ERR_AVBRIDGE_FETCH_FAILED";
41
+
42
+ // libav
43
+ export const ERR_LIBAV_NOT_REACHABLE = "ERR_AVBRIDGE_LIBAV_NOT_REACHABLE";
44
+
45
+ // MSE
46
+ export const ERR_MSE_NOT_SUPPORTED = "ERR_AVBRIDGE_MSE_NOT_SUPPORTED";
47
+ export const ERR_MSE_CODEC_NOT_SUPPORTED = "ERR_AVBRIDGE_MSE_CODEC_NOT_SUPPORTED";
package/src/index.ts CHANGED
@@ -34,9 +34,32 @@ export type {
34
34
  OutputVideoCodec,
35
35
  OutputAudioCodec,
36
36
  HardwareAccelerationHint,
37
+ FetchFn,
38
+ TransportConfig,
37
39
  } from "./types.js";
38
40
 
39
41
  export { classify } from "./classify/index.js";
42
+ export {
43
+ NATIVE_VIDEO_CODECS,
44
+ NATIVE_AUDIO_CODECS,
45
+ FALLBACK_VIDEO_CODECS,
46
+ FALLBACK_AUDIO_CODECS,
47
+ } from "./classify/rules.js";
40
48
  export { probe } from "./probe/index.js";
41
49
  export { remux, transcode } from "./convert/index.js";
42
50
  export { srtToVtt } from "./subtitles/srt.js";
51
+ export { AvbridgeError } from "./errors.js";
52
+ export {
53
+ ERR_PROBE_FAILED,
54
+ ERR_PROBE_UNKNOWN_CONTAINER,
55
+ ERR_PROBE_FETCH_FAILED,
56
+ ERR_CODEC_NOT_SUPPORTED,
57
+ ERR_STRATEGY_FAILED,
58
+ ERR_ALL_STRATEGIES_EXHAUSTED,
59
+ ERR_PLAYER_NOT_READY,
60
+ ERR_RANGE_NOT_SUPPORTED,
61
+ ERR_FETCH_FAILED,
62
+ ERR_LIBAV_NOT_REACHABLE,
63
+ ERR_MSE_NOT_SUPPORTED,
64
+ ERR_MSE_CODEC_NOT_SUPPORTED,
65
+ } from "./errors.js";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Registration entry point for `<avbridge-player>`.
3
+ *
4
+ * Import `"avbridge/player"` to register the element. This also registers
5
+ * `<avbridge-video>` (which `<avbridge-player>` wraps internally).
6
+ *
7
+ * Separate from `"avbridge/element"` so consumers who only need the bare
8
+ * `<avbridge-video>` primitive don't pay for the controls CSS/JS.
9
+ */
10
+
11
+ import { AvbridgePlayerElement } from "./element/avbridge-player.js";
12
+
13
+ export { AvbridgePlayerElement } from "./element/avbridge-player.js";
14
+ export type { AvbridgeVideoElement } from "./element/avbridge-video.js";
15
+
16
+ if (!customElements.get("avbridge-player")) {
17
+ customElements.define("avbridge-player", AvbridgePlayerElement);
18
+ }