avbridge 2.9.0 → 2.10.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.
- package/CHANGELOG.md +31 -0
- package/dist/{chunk-SN4WZE24.js → chunk-3GKM5DFM.js} +42 -5
- package/dist/chunk-3GKM5DFM.js.map +1 -0
- package/dist/{chunk-EY6DZEDT.cjs → chunk-NQULEIA3.cjs} +42 -5
- package/dist/chunk-NQULEIA3.cjs.map +1 -0
- package/dist/element-browser.js +40 -3
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +2 -2
- package/dist/element.d.cts +1 -1
- package/dist/element.d.ts +1 -1
- package/dist/element.js +1 -1
- package/dist/index.cjs +8 -8
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{player-DEcidWk6.d.cts → player-DDdNVFDv.d.cts} +23 -1
- package/dist/{player-DEcidWk6.d.ts → player-DDdNVFDv.d.ts} +23 -1
- package/dist/player.cjs +230 -98
- package/dist/player.cjs.map +1 -1
- package/dist/player.d.cts +27 -0
- package/dist/player.d.ts +27 -0
- package/dist/player.js +230 -98
- package/dist/player.js.map +1 -1
- package/package.json +1 -1
- package/src/element/avbridge-player.ts +150 -75
- package/src/element/player-styles.ts +85 -35
- package/src/index.ts +1 -0
- package/src/strategies/fallback/audio-output.ts +29 -4
- package/src/strategies/fallback/index.ts +12 -0
- package/src/strategies/hybrid/index.ts +9 -0
- package/src/types.ts +25 -0
- package/dist/chunk-EY6DZEDT.cjs.map +0 -1
- package/dist/chunk-SN4WZE24.js.map +0 -1
package/dist/player.cjs
CHANGED
|
@@ -1607,6 +1607,10 @@ var AudioOutput = class {
|
|
|
1607
1607
|
_volume = 1;
|
|
1608
1608
|
/** User-set muted flag. When true, gain is forced to 0. */
|
|
1609
1609
|
_muted = false;
|
|
1610
|
+
/** Playback rate. Scales the media clock and each AudioBufferSourceNode's
|
|
1611
|
+
* playbackRate so audio pitches up/down accordingly (same as native
|
|
1612
|
+
* <video>.playbackRate). Default 1. */
|
|
1613
|
+
_rate = 1;
|
|
1610
1614
|
constructor() {
|
|
1611
1615
|
this.ctx = new AudioContext();
|
|
1612
1616
|
this.gain = this.ctx.createGain();
|
|
@@ -1628,6 +1632,20 @@ var AudioOutput = class {
|
|
|
1628
1632
|
getMuted() {
|
|
1629
1633
|
return this._muted;
|
|
1630
1634
|
}
|
|
1635
|
+
/** Set playback rate. Scales the media clock and pitches audio output
|
|
1636
|
+
* (same as native <video>.playbackRate — speed without pitch correction).
|
|
1637
|
+
* Rebases the anchor so the clock transition is seamless. */
|
|
1638
|
+
setPlaybackRate(rate) {
|
|
1639
|
+
if (rate === this._rate) return;
|
|
1640
|
+
const t = this.now();
|
|
1641
|
+
this.mediaTimeOfAnchor = t;
|
|
1642
|
+
this.ctxTimeAtAnchor = this.ctx.currentTime;
|
|
1643
|
+
this.wallAnchorMs = performance.now();
|
|
1644
|
+
this._rate = rate;
|
|
1645
|
+
}
|
|
1646
|
+
getPlaybackRate() {
|
|
1647
|
+
return this._rate;
|
|
1648
|
+
}
|
|
1631
1649
|
applyGain() {
|
|
1632
1650
|
const target = this._muted ? 0 : this._volume;
|
|
1633
1651
|
try {
|
|
@@ -1648,12 +1666,12 @@ var AudioOutput = class {
|
|
|
1648
1666
|
now() {
|
|
1649
1667
|
if (this.noAudio) {
|
|
1650
1668
|
if (this.state === "playing") {
|
|
1651
|
-
return this.mediaTimeOfAnchor + (performance.now() - this.wallAnchorMs) / 1e3;
|
|
1669
|
+
return this.mediaTimeOfAnchor + (performance.now() - this.wallAnchorMs) / 1e3 * this._rate;
|
|
1652
1670
|
}
|
|
1653
1671
|
return this.mediaTimeOfAnchor;
|
|
1654
1672
|
}
|
|
1655
1673
|
if (this.state === "playing") {
|
|
1656
|
-
return this.mediaTimeOfAnchor + (this.ctx.currentTime - this.ctxTimeAtAnchor);
|
|
1674
|
+
return this.mediaTimeOfAnchor + (this.ctx.currentTime - this.ctxTimeAtAnchor) * this._rate;
|
|
1657
1675
|
}
|
|
1658
1676
|
return this.mediaTimeOfAnchor;
|
|
1659
1677
|
}
|
|
@@ -1705,7 +1723,8 @@ var AudioOutput = class {
|
|
|
1705
1723
|
const node = this.ctx.createBufferSource();
|
|
1706
1724
|
node.buffer = buffer;
|
|
1707
1725
|
node.connect(this.gain);
|
|
1708
|
-
|
|
1726
|
+
if (this._rate !== 1) node.playbackRate.value = this._rate;
|
|
1727
|
+
let ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor) / this._rate;
|
|
1709
1728
|
if (ctxStart < this.ctx.currentTime) {
|
|
1710
1729
|
this.ctxTimeAtAnchor = this.ctx.currentTime;
|
|
1711
1730
|
this.mediaTimeOfAnchor = this.mediaTimeOfNext;
|
|
@@ -2533,6 +2552,14 @@ async function createHybridSession(ctx, target, transport) {
|
|
|
2533
2552
|
get: () => ctx.duration ?? NaN
|
|
2534
2553
|
});
|
|
2535
2554
|
}
|
|
2555
|
+
Object.defineProperty(target, "playbackRate", {
|
|
2556
|
+
configurable: true,
|
|
2557
|
+
get: () => audio.getPlaybackRate(),
|
|
2558
|
+
set: (v) => {
|
|
2559
|
+
audio.setPlaybackRate(v);
|
|
2560
|
+
target.dispatchEvent(new Event("ratechange"));
|
|
2561
|
+
}
|
|
2562
|
+
});
|
|
2536
2563
|
Object.defineProperty(target, "readyState", {
|
|
2537
2564
|
configurable: true,
|
|
2538
2565
|
get: () => {
|
|
@@ -2643,6 +2670,7 @@ async function createHybridSession(ctx, target, transport) {
|
|
|
2643
2670
|
delete target.muted;
|
|
2644
2671
|
delete target.readyState;
|
|
2645
2672
|
delete target.seekable;
|
|
2673
|
+
delete target.playbackRate;
|
|
2646
2674
|
} catch {
|
|
2647
2675
|
}
|
|
2648
2676
|
},
|
|
@@ -3178,6 +3206,14 @@ async function createFallbackSession(ctx, target, transport) {
|
|
|
3178
3206
|
get: () => ctx.duration ?? NaN
|
|
3179
3207
|
});
|
|
3180
3208
|
}
|
|
3209
|
+
Object.defineProperty(target, "playbackRate", {
|
|
3210
|
+
configurable: true,
|
|
3211
|
+
get: () => audio.getPlaybackRate(),
|
|
3212
|
+
set: (v) => {
|
|
3213
|
+
audio.setPlaybackRate(v);
|
|
3214
|
+
target.dispatchEvent(new Event("ratechange"));
|
|
3215
|
+
}
|
|
3216
|
+
});
|
|
3181
3217
|
Object.defineProperty(target, "readyState", {
|
|
3182
3218
|
configurable: true,
|
|
3183
3219
|
get: () => {
|
|
@@ -3309,6 +3345,7 @@ async function createFallbackSession(ctx, target, transport) {
|
|
|
3309
3345
|
delete target.muted;
|
|
3310
3346
|
delete target.readyState;
|
|
3311
3347
|
delete target.seekable;
|
|
3348
|
+
delete target.playbackRate;
|
|
3312
3349
|
} catch {
|
|
3313
3350
|
}
|
|
3314
3351
|
},
|
|
@@ -4725,7 +4762,6 @@ var PLAYER_STYLES = (
|
|
|
4725
4762
|
position: relative;
|
|
4726
4763
|
width: 100%;
|
|
4727
4764
|
height: 100%;
|
|
4728
|
-
cursor: pointer;
|
|
4729
4765
|
-webkit-tap-highlight-color: transparent;
|
|
4730
4766
|
user-select: none;
|
|
4731
4767
|
}
|
|
@@ -5175,63 +5211,114 @@ var PLAYER_STYLES = (
|
|
|
5175
5211
|
|
|
5176
5212
|
.avp-spacer { flex: 1; }
|
|
5177
5213
|
|
|
5178
|
-
/* \u2500\u2500 Settings
|
|
5214
|
+
/* \u2500\u2500 Settings bottom sheet \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5179
5215
|
|
|
5216
|
+
/* Scrim \u2014 semi-transparent overlay behind the sheet, above the video.
|
|
5217
|
+
Tapping it dismisses the sheet. */
|
|
5218
|
+
.avp-settings-scrim {
|
|
5219
|
+
position: absolute;
|
|
5220
|
+
inset: 0;
|
|
5221
|
+
z-index: 9;
|
|
5222
|
+
background: rgba(0, 0, 0, 0.4);
|
|
5223
|
+
opacity: 0;
|
|
5224
|
+
pointer-events: none;
|
|
5225
|
+
transition: opacity 0.2s;
|
|
5226
|
+
}
|
|
5227
|
+
|
|
5228
|
+
.avp-settings-scrim.open {
|
|
5229
|
+
opacity: 1;
|
|
5230
|
+
pointer-events: auto;
|
|
5231
|
+
}
|
|
5232
|
+
|
|
5233
|
+
/* Sheet container \u2014 slides up from the bottom. Height is content-driven
|
|
5234
|
+
up to a JS-measured max (set on open via style.maxHeight). */
|
|
5180
5235
|
.avp-settings {
|
|
5181
5236
|
position: absolute;
|
|
5182
|
-
bottom:
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
border-radius: 8px;
|
|
5186
|
-
min-width: 220px;
|
|
5187
|
-
/* Fit within the player: leave room for the controls bar (52px bottom)
|
|
5188
|
-
and a small top margin (8px). On tall players this caps at 300px;
|
|
5189
|
-
on short players it shrinks to whatever fits. */
|
|
5190
|
-
max-height: min(300px, calc(100% - 52px - 8px));
|
|
5191
|
-
overflow-y: auto;
|
|
5192
|
-
display: none;
|
|
5237
|
+
bottom: 0;
|
|
5238
|
+
left: 0;
|
|
5239
|
+
right: 0;
|
|
5193
5240
|
z-index: 10;
|
|
5194
|
-
|
|
5241
|
+
background: rgba(28, 28, 28, 0.97);
|
|
5242
|
+
border-radius: 12px 12px 0 0;
|
|
5243
|
+
overflow-y: auto;
|
|
5244
|
+
overscroll-behavior: contain;
|
|
5245
|
+
transform: translateY(100%);
|
|
5246
|
+
transition: transform 0.2s ease-out;
|
|
5247
|
+
max-height: 70%;
|
|
5248
|
+
padding-bottom: 52px;
|
|
5249
|
+
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.5);
|
|
5195
5250
|
}
|
|
5196
5251
|
|
|
5197
|
-
.avp-settings.open {
|
|
5252
|
+
.avp-settings.open {
|
|
5253
|
+
transform: translateY(0);
|
|
5254
|
+
}
|
|
5198
5255
|
|
|
5199
|
-
.
|
|
5200
|
-
|
|
5201
|
-
|
|
5256
|
+
/* Drag handle indicator at top of sheet. */
|
|
5257
|
+
.avp-settings-handle {
|
|
5258
|
+
width: 36px;
|
|
5259
|
+
height: 4px;
|
|
5260
|
+
border-radius: 2px;
|
|
5261
|
+
background: rgba(255, 255, 255, 0.3);
|
|
5262
|
+
margin: 8px auto 4px;
|
|
5202
5263
|
}
|
|
5203
5264
|
|
|
5204
|
-
|
|
5265
|
+
/* \u2500\u2500 Accordion sections \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5205
5266
|
|
|
5206
|
-
.avp-settings-
|
|
5207
|
-
|
|
5208
|
-
font-size: 11px;
|
|
5209
|
-
text-transform: uppercase;
|
|
5210
|
-
letter-spacing: 0.5px;
|
|
5211
|
-
opacity: 0.5;
|
|
5267
|
+
.avp-settings-section {
|
|
5268
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
5212
5269
|
}
|
|
5213
5270
|
|
|
5214
|
-
.avp-settings-
|
|
5271
|
+
.avp-settings-section:last-child { border-bottom: none; }
|
|
5272
|
+
|
|
5273
|
+
/* Section header \u2014 clickable row showing label + current value. */
|
|
5274
|
+
.avp-settings-header {
|
|
5275
|
+
position: relative;
|
|
5215
5276
|
display: flex;
|
|
5216
5277
|
align-items: center;
|
|
5217
|
-
|
|
5218
|
-
|
|
5278
|
+
justify-content: space-between;
|
|
5279
|
+
padding: 12px 16px;
|
|
5219
5280
|
cursor: pointer;
|
|
5281
|
+
font-size: 14px;
|
|
5220
5282
|
transition: background 0.1s;
|
|
5221
5283
|
}
|
|
5222
5284
|
|
|
5223
|
-
.avp-settings-
|
|
5285
|
+
.avp-settings-header:hover { background: rgba(255, 255, 255, 0.06); }
|
|
5224
5286
|
|
|
5225
|
-
.avp-settings-
|
|
5226
|
-
|
|
5287
|
+
.avp-settings-header-label {
|
|
5288
|
+
display: flex;
|
|
5289
|
+
align-items: center;
|
|
5290
|
+
gap: 8px;
|
|
5291
|
+
font-weight: 500;
|
|
5227
5292
|
}
|
|
5228
5293
|
|
|
5229
|
-
.avp-settings-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
font-
|
|
5294
|
+
.avp-settings-header-value {
|
|
5295
|
+
margin-left: auto;
|
|
5296
|
+
opacity: 0.6;
|
|
5297
|
+
font-size: 13px;
|
|
5298
|
+
text-align: right;
|
|
5233
5299
|
}
|
|
5234
5300
|
|
|
5301
|
+
/* Invisible native <select> layered over the value portion of the row.
|
|
5302
|
+
Covers from the value text to the right edge so tapping the value
|
|
5303
|
+
opens the OS picker. The label side remains inert. */
|
|
5304
|
+
.avp-settings-select {
|
|
5305
|
+
position: absolute;
|
|
5306
|
+
top: 0;
|
|
5307
|
+
right: 0;
|
|
5308
|
+
bottom: 0;
|
|
5309
|
+
width: 50%;
|
|
5310
|
+
opacity: 0;
|
|
5311
|
+
cursor: pointer;
|
|
5312
|
+
font-size: 16px;
|
|
5313
|
+
direction: rtl;
|
|
5314
|
+
}
|
|
5315
|
+
|
|
5316
|
+
/* Toggle-style rows (Stats for Nerds) \u2014 no select, just clickable. */
|
|
5317
|
+
.avp-settings-toggle {
|
|
5318
|
+
cursor: pointer;
|
|
5319
|
+
}
|
|
5320
|
+
.avp-settings-toggle:hover { background: rgba(255, 255, 255, 0.06); }
|
|
5321
|
+
|
|
5235
5322
|
/* \u2500\u2500 Stats for nerds \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5236
5323
|
|
|
5237
5324
|
.avp-stats {
|
|
@@ -5369,6 +5456,8 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5369
5456
|
_volumeInput;
|
|
5370
5457
|
_settingsBtn;
|
|
5371
5458
|
_settingsMenu;
|
|
5459
|
+
_settingsScrim;
|
|
5460
|
+
_customSections = [];
|
|
5372
5461
|
_fullscreenBtn;
|
|
5373
5462
|
// Strategy badge removed — visible in Stats for Nerds instead.
|
|
5374
5463
|
// Spinner is rendered but driven entirely by CSS :host([data-state]) selectors.
|
|
@@ -5410,6 +5499,7 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5410
5499
|
this._volumeInput = shadow.querySelector(".avp-volume-input");
|
|
5411
5500
|
this._settingsBtn = shadow.querySelector(".avp-settings-btn");
|
|
5412
5501
|
this._settingsMenu = shadow.querySelector(".avp-settings");
|
|
5502
|
+
this._settingsScrim = shadow.querySelector(".avp-settings-scrim");
|
|
5413
5503
|
this._fullscreenBtn = shadow.querySelector(".avp-fullscreen");
|
|
5414
5504
|
this._speedIndicator = shadow.querySelector(".avp-speed-indicator");
|
|
5415
5505
|
this._statsEl = shadow.querySelector(".avp-stats");
|
|
@@ -5466,7 +5556,8 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5466
5556
|
<button class="avp-btn avp-settings-btn" part="settings-button" aria-label="Settings">${ICON_SETTINGS}</button>
|
|
5467
5557
|
<button class="avp-btn avp-fullscreen" part="fullscreen-button" aria-label="Fullscreen">${ICON_FULLSCREEN}</button>
|
|
5468
5558
|
</div>
|
|
5469
|
-
<div class="avp-settings
|
|
5559
|
+
<div class="avp-settings-scrim"></div>
|
|
5560
|
+
<div class="avp-settings" part="settings-menu"><div class="avp-settings-handle"></div></div>
|
|
5470
5561
|
</div>
|
|
5471
5562
|
</div>`;
|
|
5472
5563
|
}
|
|
@@ -5538,6 +5629,7 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5538
5629
|
e.stopPropagation();
|
|
5539
5630
|
this._toggleSettings();
|
|
5540
5631
|
});
|
|
5632
|
+
on(this._settingsScrim, "click", () => this._closeSettings());
|
|
5541
5633
|
on(this._fullscreenBtn, "click", (e) => {
|
|
5542
5634
|
e.stopPropagation();
|
|
5543
5635
|
this._toggleFullscreen();
|
|
@@ -5546,11 +5638,6 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5546
5638
|
const container = this.shadowRoot.querySelector(".avp");
|
|
5547
5639
|
on(container, "click", (e) => this._onContainerClick(e));
|
|
5548
5640
|
on(container, "dblclick", (e) => this._onContainerDblClick(e));
|
|
5549
|
-
on(container, "click", (e) => {
|
|
5550
|
-
if (this._settingsOpen && !e.target.closest?.(".avp-settings-btn, .avp-settings")) {
|
|
5551
|
-
this._closeSettings();
|
|
5552
|
-
}
|
|
5553
|
-
});
|
|
5554
5641
|
on(document, "click", (e) => {
|
|
5555
5642
|
if (this._settingsOpen && !this.contains(e.target)) {
|
|
5556
5643
|
this._closeSettings();
|
|
@@ -5728,83 +5815,111 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5728
5815
|
_toggleSettings() {
|
|
5729
5816
|
this._settingsOpen = !this._settingsOpen;
|
|
5730
5817
|
this._settingsMenu.classList.toggle("open", this._settingsOpen);
|
|
5731
|
-
|
|
5818
|
+
this._settingsScrim.classList.toggle("open", this._settingsOpen);
|
|
5819
|
+
if (this._settingsOpen) {
|
|
5820
|
+
this._fitSettingsToPlayer();
|
|
5821
|
+
this._showControls();
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5824
|
+
_fitSettingsToPlayer() {
|
|
5825
|
+
const container = this.shadowRoot?.querySelector(".avp");
|
|
5826
|
+
if (!container) return;
|
|
5827
|
+
const rect = container.getBoundingClientRect();
|
|
5828
|
+
const maxH = Math.max(120, Math.floor(rect.height * 0.7));
|
|
5829
|
+
this._settingsMenu.style.maxHeight = `${maxH}px`;
|
|
5732
5830
|
}
|
|
5733
5831
|
_closeSettings() {
|
|
5734
5832
|
this._settingsOpen = false;
|
|
5735
5833
|
this._settingsMenu.classList.remove("open");
|
|
5834
|
+
this._settingsScrim.classList.remove("open");
|
|
5736
5835
|
}
|
|
5737
5836
|
_buildSettingsMenu() {
|
|
5738
5837
|
const sections = [];
|
|
5739
|
-
|
|
5740
|
-
const currentFit = this._video.fit ?? "contain";
|
|
5741
|
-
let fitItems = "";
|
|
5742
|
-
for (const mode of FIT_MODES) {
|
|
5743
|
-
const active = mode === currentFit;
|
|
5744
|
-
const label = mode[0].toUpperCase() + mode.slice(1);
|
|
5745
|
-
fitItems += `<div class="avp-settings-item${active ? " active" : ""}" data-fit="${mode}">${label}</div>`;
|
|
5746
|
-
}
|
|
5747
|
-
sections.push(`<div class="avp-settings-section"><div class="avp-settings-label">Fit</div>${fitItems}</div>`);
|
|
5748
|
-
}
|
|
5838
|
+
const selectRow = (label, currentValue, options, selectAttrs) => `<div class="avp-settings-section"><div class="avp-settings-header"><span class="avp-settings-header-label">${label}</span><span class="avp-settings-header-value">${currentValue}</span><select class="avp-settings-select" ${selectAttrs}>${options}</select></div></div>`;
|
|
5749
5839
|
const currentRate = this._video.playbackRate ?? 1;
|
|
5750
|
-
|
|
5840
|
+
const speedValue = Math.abs(currentRate - 1) < 0.01 ? "Normal" : `${currentRate}x`;
|
|
5841
|
+
let speedOpts = "";
|
|
5751
5842
|
for (const spd of PLAYBACK_SPEEDS) {
|
|
5752
|
-
const
|
|
5843
|
+
const sel = Math.abs(spd - currentRate) < 0.01 ? " selected" : "";
|
|
5753
5844
|
const label = spd === 1 ? "Normal" : `${spd}x`;
|
|
5754
|
-
|
|
5845
|
+
speedOpts += `<option value="${spd}"${sel}>${label}</option>`;
|
|
5846
|
+
}
|
|
5847
|
+
sections.push(selectRow("Speed", speedValue, speedOpts, `data-action="speed"`));
|
|
5848
|
+
const audios = this._video.audioTracks ?? [];
|
|
5849
|
+
if (audios.length > 1) {
|
|
5850
|
+
let audioOpts = "";
|
|
5851
|
+
for (const t of audios) {
|
|
5852
|
+
audioOpts += `<option value="${t.id}">${t.language ?? `Track ${t.id}`}</option>`;
|
|
5853
|
+
}
|
|
5854
|
+
sections.push(selectRow("Audio", audios[0]?.language ?? "Track 1", audioOpts, `data-action="audio"`));
|
|
5755
5855
|
}
|
|
5756
|
-
sections.push(`<div class="avp-settings-section"><div class="avp-settings-label">Speed</div>${speedItems}</div>`);
|
|
5757
5856
|
const subs = this._video.subtitleTracks ?? [];
|
|
5758
5857
|
if (subs.length > 0) {
|
|
5759
|
-
let
|
|
5858
|
+
let subOpts = `<option value="-1" selected>Off</option>`;
|
|
5760
5859
|
for (const t of subs) {
|
|
5761
|
-
|
|
5860
|
+
subOpts += `<option value="${t.id}">${t.language ?? `Track ${t.id}`}</option>`;
|
|
5762
5861
|
}
|
|
5763
|
-
sections.push(
|
|
5862
|
+
sections.push(selectRow("Subtitles", "Off", subOpts, `data-action="subtitle"`));
|
|
5764
5863
|
}
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5864
|
+
if (this.hasAttribute("show-fit")) {
|
|
5865
|
+
const currentFit = this._video.fit ?? "contain";
|
|
5866
|
+
const fitValue = currentFit[0].toUpperCase() + currentFit.slice(1);
|
|
5867
|
+
let fitOpts = "";
|
|
5868
|
+
for (const mode of FIT_MODES) {
|
|
5869
|
+
const sel = mode === currentFit ? " selected" : "";
|
|
5870
|
+
const label = mode[0].toUpperCase() + mode.slice(1);
|
|
5871
|
+
fitOpts += `<option value="${mode}"${sel}>${label}</option>`;
|
|
5770
5872
|
}
|
|
5771
|
-
sections.push(
|
|
5873
|
+
sections.push(selectRow("Fit", fitValue, fitOpts, `data-action="fit"`));
|
|
5772
5874
|
}
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
item.
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
});
|
|
5875
|
+
for (const cfg of this._customSections) {
|
|
5876
|
+
const activeItem = cfg.items.find((i) => i.active);
|
|
5877
|
+
let customOpts = "";
|
|
5878
|
+
for (const item of cfg.items) {
|
|
5879
|
+
const sel = item.active ? " selected" : "";
|
|
5880
|
+
customOpts += `<option value="${item.id}"${sel}>${item.label}</option>`;
|
|
5881
|
+
}
|
|
5882
|
+
sections.push(selectRow(cfg.label, activeItem?.label ?? "", customOpts, `data-action="custom" data-custom-id="${cfg.id}"`));
|
|
5782
5883
|
}
|
|
5783
|
-
|
|
5784
|
-
|
|
5884
|
+
sections.push(
|
|
5885
|
+
`<div class="avp-settings-section"><div class="avp-settings-header avp-settings-toggle" data-stats><span class="avp-settings-header-label">Stats for Nerds</span></div></div>`
|
|
5886
|
+
);
|
|
5887
|
+
const handle = this._settingsMenu.querySelector(".avp-settings-handle");
|
|
5888
|
+
this._settingsMenu.innerHTML = "";
|
|
5889
|
+
if (handle) this._settingsMenu.appendChild(handle);
|
|
5890
|
+
else this._settingsMenu.insertAdjacentHTML("afterbegin", `<div class="avp-settings-handle"></div>`);
|
|
5891
|
+
this._settingsMenu.insertAdjacentHTML("beforeend", sections.join(""));
|
|
5892
|
+
for (const sel of this._settingsMenu.querySelectorAll(".avp-settings-select")) {
|
|
5893
|
+
sel.addEventListener("change", (e) => {
|
|
5785
5894
|
e.stopPropagation();
|
|
5786
|
-
|
|
5895
|
+
const action = sel.dataset.action;
|
|
5896
|
+
const val = sel.value;
|
|
5897
|
+
switch (action) {
|
|
5898
|
+
case "speed":
|
|
5899
|
+
this._video.playbackRate = Number(val);
|
|
5900
|
+
break;
|
|
5901
|
+
case "audio":
|
|
5902
|
+
void this._video.setAudioTrack(Number(val));
|
|
5903
|
+
break;
|
|
5904
|
+
case "subtitle":
|
|
5905
|
+
void this._video.setSubtitleTrack(Number(val) >= 0 ? Number(val) : null);
|
|
5906
|
+
break;
|
|
5907
|
+
case "fit":
|
|
5908
|
+
this.setAttribute("fit", val);
|
|
5909
|
+
break;
|
|
5910
|
+
case "custom": {
|
|
5911
|
+
const cfgId = sel.dataset.customId;
|
|
5912
|
+
const cfg = this._customSections.find((s) => s.id === cfgId);
|
|
5913
|
+
cfg?.onSelect(val);
|
|
5914
|
+
break;
|
|
5915
|
+
}
|
|
5916
|
+
}
|
|
5787
5917
|
this._buildSettingsMenu();
|
|
5788
5918
|
});
|
|
5789
5919
|
}
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
const id = Number(item.dataset.subtitle);
|
|
5794
|
-
void this._video.setSubtitleTrack(id >= 0 ? id : null);
|
|
5795
|
-
this._closeSettings();
|
|
5796
|
-
});
|
|
5797
|
-
}
|
|
5798
|
-
for (const item of this._settingsMenu.querySelectorAll("[data-audio]")) {
|
|
5799
|
-
item.addEventListener("click", (e) => {
|
|
5800
|
-
e.stopPropagation();
|
|
5801
|
-
void this._video.setAudioTrack(Number(item.dataset.audio));
|
|
5802
|
-
this._closeSettings();
|
|
5803
|
-
});
|
|
5804
|
-
}
|
|
5805
|
-
const statsItem = this._settingsMenu.querySelector("[data-stats]");
|
|
5806
|
-
if (statsItem) {
|
|
5807
|
-
statsItem.addEventListener("click", (e) => {
|
|
5920
|
+
const statsRow = this._settingsMenu.querySelector("[data-stats]");
|
|
5921
|
+
if (statsRow) {
|
|
5922
|
+
statsRow.addEventListener("click", (e) => {
|
|
5808
5923
|
e.stopPropagation();
|
|
5809
5924
|
this._toggleStats();
|
|
5810
5925
|
this._closeSettings();
|
|
@@ -5917,6 +6032,10 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5917
6032
|
_onContainerClick(e) {
|
|
5918
6033
|
if (e.target.closest?.(".avp-controls, .avp-settings, .avp-overlay-btn")) return;
|
|
5919
6034
|
if (this._isSlottedContentEvent(e)) return;
|
|
6035
|
+
if (this._settingsOpen) {
|
|
6036
|
+
this._closeSettings();
|
|
6037
|
+
return;
|
|
6038
|
+
}
|
|
5920
6039
|
if (this._lastPointerTypeWasTouch) {
|
|
5921
6040
|
this._lastPointerTypeWasTouch = false;
|
|
5922
6041
|
return;
|
|
@@ -5955,6 +6074,10 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5955
6074
|
this._lastPointerTypeWasTouch = true;
|
|
5956
6075
|
if (e.target.closest?.(".avp-controls, .avp-settings, .avp-overlay-btn")) return;
|
|
5957
6076
|
if (this._isSlottedContentEvent(e)) return;
|
|
6077
|
+
if (this._settingsOpen) {
|
|
6078
|
+
this._closeSettings();
|
|
6079
|
+
return;
|
|
6080
|
+
}
|
|
5958
6081
|
const now = Date.now();
|
|
5959
6082
|
if (now - this._lastTapTime < 300) {
|
|
5960
6083
|
if (this._tapTimer) {
|
|
@@ -6204,6 +6327,15 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
6204
6327
|
async setAudioTrack(id) {
|
|
6205
6328
|
return this._video.setAudioTrack(id);
|
|
6206
6329
|
}
|
|
6330
|
+
addSettingsSection(config) {
|
|
6331
|
+
this._customSections = this._customSections.filter((s) => s.id !== config.id);
|
|
6332
|
+
this._customSections.push(config);
|
|
6333
|
+
this._buildSettingsMenu();
|
|
6334
|
+
}
|
|
6335
|
+
removeSettingsSection(id) {
|
|
6336
|
+
this._customSections = this._customSections.filter((s) => s.id !== id);
|
|
6337
|
+
this._buildSettingsMenu();
|
|
6338
|
+
}
|
|
6207
6339
|
async setSubtitleTrack(id) {
|
|
6208
6340
|
return this._video.setSubtitleTrack(id);
|
|
6209
6341
|
}
|