ts-video-player 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn.min.js +31 -7
- package/dist/{chunk-hvjcmbfn.js → chunk-fvxawgp5.js} +29 -3
- package/dist/{chunk-ds1nj487.js → chunk-ndbfytd6.js} +1 -1
- package/dist/{chunk-rhykj0qy.js → chunk-pvq6e7gz.js} +17 -3
- package/dist/core/events.d.ts +3 -0
- package/dist/elements/index.js +1 -1
- package/dist/features/index.js +1 -1
- package/dist/index.js +3 -3
- package/package.json +1 -1
- package/src/core/events.ts +19 -3
- package/src/elements/media-airplay-button.ts +2 -0
- package/src/elements/media-fullscreen-button.ts +2 -0
- package/src/elements/media-live-button.ts +2 -0
- package/src/elements/media-mute-button.ts +2 -0
- package/src/elements/media-pip-button.ts +2 -0
- package/src/elements/media-play-button.ts +2 -0
- package/src/elements/media-playback-rate-button.ts +2 -0
- package/src/elements/media-progress-bar.ts +2 -0
- package/src/elements/media-remote-playback-button.ts +2 -0
- package/src/elements/media-seek-button.ts +2 -0
- package/src/elements/media-settings-menu.ts +2 -0
- package/src/elements/media-volume-slider.ts +2 -0
- package/src/elements/video-player.ts +3 -1
- package/src/player.ts +6 -1
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
detectPipAvailability2,
|
|
17
17
|
enterPiP2,
|
|
18
18
|
exitPiP2
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-pvq6e7gz.js";
|
|
20
20
|
|
|
21
21
|
// src/core/media-session.ts
|
|
22
22
|
function isMediaSessionSupported2() {
|
|
@@ -2848,6 +2848,8 @@ class Player2 {
|
|
|
2848
2848
|
toggleCaptions: () => this.toggleCaptions(),
|
|
2849
2849
|
seekBackward: () => this.seekBy(-5),
|
|
2850
2850
|
seekForward: () => this.seekBy(5),
|
|
2851
|
+
seekBackwardLong: () => this.seekBy(-10),
|
|
2852
|
+
seekForwardLong: () => this.seekBy(10),
|
|
2851
2853
|
volumeUp: () => this.setVolume(Math.min(1, this.state.volume + 0.1)),
|
|
2852
2854
|
volumeDown: () => this.setVolume(Math.max(0, this.state.volume - 0.1)),
|
|
2853
2855
|
speedUp: () => this.setPlaybackRate(Math.min(4, this.state.playbackRate + 0.25)),
|
|
@@ -3088,7 +3090,7 @@ class Player2 {
|
|
|
3088
3090
|
this.announce(label(this._el, "playing"));
|
|
3089
3091
|
});
|
|
3090
3092
|
provider.on("pause", () => {
|
|
3091
|
-
this._store.
|
|
3093
|
+
this._store.batch({ paused: true, playing: false, playbackState: "paused" });
|
|
3092
3094
|
this._events.emit("pause");
|
|
3093
3095
|
this.announce(label(this._el, "paused"));
|
|
3094
3096
|
});
|
|
@@ -3357,7 +3359,7 @@ class VideoPlayerElement2 extends HTMLElementBase {
|
|
|
3357
3359
|
this._disconnectId++;
|
|
3358
3360
|
if (this._player)
|
|
3359
3361
|
return;
|
|
3360
|
-
const shadow = this.shadowRoot ?? this.attachShadow({ mode: "open" });
|
|
3362
|
+
const shadow = this.shadowRoot ?? this.attachShadow({ mode: "open", delegatesFocus: true });
|
|
3361
3363
|
this._container = document.createElement("div");
|
|
3362
3364
|
this._container.setAttribute("part", "container");
|
|
3363
3365
|
this._container.style.cssText = "position:relative;width:100%;height:100%;";
|
|
@@ -3643,6 +3645,8 @@ class MediaPlayButton2 extends HTMLElementBase {
|
|
|
3643
3645
|
background: transparent; border: none; color: inherit; cursor: pointer;
|
|
3644
3646
|
}
|
|
3645
3647
|
button:hover { opacity: 0.8; }
|
|
3648
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
3649
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
3646
3650
|
</style>
|
|
3647
3651
|
<button part="button" type="button" aria-label="${label(this, "play")}">
|
|
3648
3652
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
|
|
@@ -3704,6 +3708,8 @@ class MediaMuteButton2 extends HTMLElementBase {
|
|
|
3704
3708
|
background: transparent; border: none; color: inherit; cursor: pointer;
|
|
3705
3709
|
}
|
|
3706
3710
|
button:hover { opacity: 0.8; }
|
|
3711
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
3712
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
3707
3713
|
</style>
|
|
3708
3714
|
<button part="button" type="button" aria-label="${label(this, "mute")}">
|
|
3709
3715
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
|
|
@@ -3753,6 +3759,8 @@ class MediaFullscreenButton2 extends HTMLElementBase {
|
|
|
3753
3759
|
}
|
|
3754
3760
|
button:hover { opacity: 0.8; }
|
|
3755
3761
|
button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3762
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
3763
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
3756
3764
|
</style>
|
|
3757
3765
|
<button part="button" type="button" aria-label="${label(this, "enterFullscreen")}">
|
|
3758
3766
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
|
|
@@ -3808,6 +3816,8 @@ class MediaPipButton2 extends HTMLElementBase {
|
|
|
3808
3816
|
}
|
|
3809
3817
|
button:hover { opacity: 0.8; }
|
|
3810
3818
|
button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3819
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
3820
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
3811
3821
|
</style>
|
|
3812
3822
|
<button part="button" type="button" aria-label="${label(this, "enterPiP")}">
|
|
3813
3823
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
|
|
@@ -4068,6 +4078,8 @@ class MediaProgressBar2 extends HTMLElementBase {
|
|
|
4068
4078
|
border-top-color: rgba(0,0,0,0.85);
|
|
4069
4079
|
}
|
|
4070
4080
|
.container:hover .tooltip { opacity: 1; }
|
|
4081
|
+
/* A finger needs more than the 20px a mouse does. */
|
|
4082
|
+
@media (pointer: coarse) { .container { height: 36px; } .thumb { width: 18px; height: 18px; } }
|
|
4071
4083
|
</style>
|
|
4072
4084
|
<div class="container" part="container" role="slider"
|
|
4073
4085
|
aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"
|
|
@@ -4216,6 +4228,8 @@ class MediaVolumeSlider2 extends HTMLElementBase {
|
|
|
4216
4228
|
transition: transform 0.1s;
|
|
4217
4229
|
}
|
|
4218
4230
|
.container:hover .thumb { transform: translate(-50%, -50%) scale(1.2); }
|
|
4231
|
+
/* A finger needs more than the 20px a mouse does. */
|
|
4232
|
+
@media (pointer: coarse) { .container { height: 36px; } .thumb { width: 18px; height: 18px; } }
|
|
4219
4233
|
</style>
|
|
4220
4234
|
<div class="container" part="container" role="slider"
|
|
4221
4235
|
aria-valuemin="0" aria-valuemax="100" aria-valuenow="100"
|
|
@@ -4412,6 +4426,8 @@ class MediaSettingsMenu2 extends HTMLElementBase {
|
|
|
4412
4426
|
.item[aria-checked="true"]::before { content: '\\2713'; }
|
|
4413
4427
|
.item[aria-checked="false"]::before { content: ''; min-width: 12px; display: inline-block; }
|
|
4414
4428
|
.back { font-weight: bold; border-bottom: 1px solid rgba(255,255,255,0.2); }
|
|
4429
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
4430
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
4415
4431
|
</style>
|
|
4416
4432
|
<button class="trigger" part="trigger" type="button"
|
|
4417
4433
|
aria-label="${label(this, "settings")}" aria-haspopup="true" aria-expanded="false">
|
|
@@ -4641,6 +4657,8 @@ class MediaSeekButton2 extends HTMLElementBase {
|
|
|
4641
4657
|
button:hover { opacity: .8; }
|
|
4642
4658
|
button:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
|
|
4643
4659
|
button:disabled { cursor: default; opacity: .45; }
|
|
4660
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
4661
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
4644
4662
|
</style>
|
|
4645
4663
|
<button part="button" type="button"></button>
|
|
4646
4664
|
`;
|
|
@@ -4686,6 +4704,8 @@ class MediaPlaybackRateButton2 extends HTMLElementBase {
|
|
|
4686
4704
|
button { min-width: 42px; height: 40px; padding: 0 8px; border: 0; background: transparent; color: inherit; cursor: pointer; font: inherit; font-variant-numeric: tabular-nums; }
|
|
4687
4705
|
button:hover { opacity: .8; }
|
|
4688
4706
|
button:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
|
|
4707
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
4708
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
4689
4709
|
</style>
|
|
4690
4710
|
<button part="button" type="button" aria-label="${label(this, "speed")}"></button>
|
|
4691
4711
|
`;
|
|
@@ -4736,6 +4756,8 @@ class MediaAirplayButton2 extends HTMLElementBase {
|
|
|
4736
4756
|
button { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 8px; background: transparent; border: 0; color: inherit; cursor: pointer; }
|
|
4737
4757
|
button:hover { opacity: 0.8; }
|
|
4738
4758
|
:host([connected]) button { color: var(--media-airplay-active-color, #38bdf8); }
|
|
4759
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
4760
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
4739
4761
|
</style>
|
|
4740
4762
|
<button part="button" type="button" aria-label="${label(this, "airPlayPlay")}">
|
|
4741
4763
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon"><path d="${AIRPLAY_ICON}"/></svg>
|
|
@@ -4795,6 +4817,8 @@ class MediaLiveButton2 extends HTMLElementBase {
|
|
|
4795
4817
|
button { display: inline-flex; align-items: center; gap: 6px; min-height: 40px; padding: 8px; background: transparent; border: 0; color: inherit; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; }
|
|
4796
4818
|
i { width: 8px; height: 8px; border-radius: 9999px; background: #ef4444; }
|
|
4797
4819
|
:host(:not([at-live-edge])) button { opacity: 0.72; }
|
|
4820
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
4821
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
4798
4822
|
</style>
|
|
4799
4823
|
<button part="button" type="button" aria-label="${label(this, "seekToLive")}"><i aria-hidden="true"></i><span>${label(this, "live")}</span></button>
|
|
4800
4824
|
`;
|
|
@@ -4891,6 +4915,8 @@ class MediaRemotePlaybackButton2 extends HTMLElementBase {
|
|
|
4891
4915
|
button { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 8px; background: transparent; border: 0; color: inherit; cursor: pointer; }
|
|
4892
4916
|
button:hover { opacity: 0.8; }
|
|
4893
4917
|
:host([connected]) button { color: var(--media-remote-active-color, #38bdf8); }
|
|
4918
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
4919
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
4894
4920
|
</style>
|
|
4895
4921
|
<button part="button" type="button" aria-label="${label(this, "remotePlayback")}">
|
|
4896
4922
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon"><path d="${REMOTE_ICON}"/></svg>
|
|
@@ -383,7 +383,9 @@ class MediaEventsNormalizer2 {
|
|
|
383
383
|
}
|
|
384
384
|
function onFullscreenChange2(handler) {
|
|
385
385
|
const events = ["fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "MSFullscreenChange"];
|
|
386
|
-
const listener = () => {
|
|
386
|
+
const listener = (event) => {
|
|
387
|
+
if (event && typeof CustomEvent !== "undefined" && event instanceof CustomEvent)
|
|
388
|
+
return;
|
|
387
389
|
const fullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
|
|
388
390
|
handler(fullscreen);
|
|
389
391
|
};
|
|
@@ -411,8 +413,8 @@ var DEFAULT_KEYBOARD_SHORTCUTS2 = {
|
|
|
411
413
|
c: "toggleCaptions",
|
|
412
414
|
ArrowLeft: "seekBackward",
|
|
413
415
|
ArrowRight: "seekForward",
|
|
414
|
-
j: "
|
|
415
|
-
l: "
|
|
416
|
+
j: "seekBackwardLong",
|
|
417
|
+
l: "seekForwardLong",
|
|
416
418
|
ArrowUp: "volumeUp",
|
|
417
419
|
ArrowDown: "volumeDown",
|
|
418
420
|
">": "speedUp",
|
|
@@ -463,6 +465,18 @@ function createKeyboardHandler2(actions, options = {}) {
|
|
|
463
465
|
case "seekForward":
|
|
464
466
|
actions.seekForward();
|
|
465
467
|
break;
|
|
468
|
+
case "seekBackwardLong":
|
|
469
|
+
if (actions.seekBackwardLong)
|
|
470
|
+
actions.seekBackwardLong();
|
|
471
|
+
else
|
|
472
|
+
actions.seekBackward();
|
|
473
|
+
break;
|
|
474
|
+
case "seekForwardLong":
|
|
475
|
+
if (actions.seekForwardLong)
|
|
476
|
+
actions.seekForwardLong();
|
|
477
|
+
else
|
|
478
|
+
actions.seekForward();
|
|
479
|
+
break;
|
|
466
480
|
case "volumeUp":
|
|
467
481
|
actions.volumeUp();
|
|
468
482
|
break;
|
package/dist/core/events.d.ts
CHANGED
|
@@ -78,6 +78,9 @@ export interface KeyboardActions {
|
|
|
78
78
|
toggleCaptions(): void;
|
|
79
79
|
seekBackward(): void;
|
|
80
80
|
seekForward(): void;
|
|
81
|
+
/** J and L: ten seconds, as YouTube's keys go (the arrows are five). Falls back to seekBackward/seekForward. */
|
|
82
|
+
seekBackwardLong?(): void;
|
|
83
|
+
seekForwardLong?(): void;
|
|
81
84
|
volumeUp(): void;
|
|
82
85
|
volumeDown(): void;
|
|
83
86
|
speedUp(): void;
|
package/dist/elements/index.js
CHANGED
package/dist/features/index.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
applyPosition2,
|
|
64
64
|
MediaSettingsMenu2,
|
|
65
65
|
registerElements2
|
|
66
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-fvxawgp5.js";
|
|
67
67
|
import {
|
|
68
68
|
playback2,
|
|
69
69
|
volume2,
|
|
@@ -77,7 +77,7 @@ import {
|
|
|
77
77
|
videoFeatures2,
|
|
78
78
|
audioFeatures2,
|
|
79
79
|
minimalFeatures2
|
|
80
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-ndbfytd6.js";
|
|
81
81
|
import {
|
|
82
82
|
shallowEqual2,
|
|
83
83
|
createSignal2,
|
|
@@ -118,7 +118,7 @@ import {
|
|
|
118
118
|
enterPiP2,
|
|
119
119
|
exitPiP2,
|
|
120
120
|
detectAllFeatures2
|
|
121
|
-
} from "./chunk-
|
|
121
|
+
} from "./chunk-pvq6e7gz.js";
|
|
122
122
|
// src/core/audio-gain.ts
|
|
123
123
|
class AudioGain {
|
|
124
124
|
media;
|
package/package.json
CHANGED
package/src/core/events.ts
CHANGED
|
@@ -208,7 +208,12 @@ export class MediaEventsNormalizer {
|
|
|
208
208
|
export function onFullscreenChange(handler: (fullscreen: boolean) => void): () => void {
|
|
209
209
|
const events = ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange']
|
|
210
210
|
|
|
211
|
-
const listener = () => {
|
|
211
|
+
const listener = (event?: Event) => {
|
|
212
|
+
// The browser's own change only. <video-player> re-dispatches the player's
|
|
213
|
+
// events on itself, bubbling and composed, under the same names; its
|
|
214
|
+
// "fullscreenchange" reached this document listener, which announced it
|
|
215
|
+
// again, and the two fed each other until the stack overflowed.
|
|
216
|
+
if (event && typeof CustomEvent !== 'undefined' && event instanceof CustomEvent) return
|
|
212
217
|
const fullscreen = !!(
|
|
213
218
|
document.fullscreenElement ||
|
|
214
219
|
(document as any).webkitFullscreenElement ||
|
|
@@ -261,8 +266,8 @@ export const DEFAULT_KEYBOARD_SHORTCUTS: Record<string, string> = {
|
|
|
261
266
|
c: 'toggleCaptions',
|
|
262
267
|
ArrowLeft: 'seekBackward',
|
|
263
268
|
ArrowRight: 'seekForward',
|
|
264
|
-
j: '
|
|
265
|
-
l: '
|
|
269
|
+
j: 'seekBackwardLong',
|
|
270
|
+
l: 'seekForwardLong',
|
|
266
271
|
ArrowUp: 'volumeUp',
|
|
267
272
|
ArrowDown: 'volumeDown',
|
|
268
273
|
'>': 'speedUp',
|
|
@@ -292,6 +297,9 @@ export interface KeyboardActions {
|
|
|
292
297
|
toggleCaptions(): void
|
|
293
298
|
seekBackward(): void
|
|
294
299
|
seekForward(): void
|
|
300
|
+
/** J and L: ten seconds, as YouTube's keys go (the arrows are five). Falls back to seekBackward/seekForward. */
|
|
301
|
+
seekBackwardLong?(): void
|
|
302
|
+
seekForwardLong?(): void
|
|
295
303
|
volumeUp(): void
|
|
296
304
|
volumeDown(): void
|
|
297
305
|
speedUp(): void
|
|
@@ -351,6 +359,14 @@ export function createKeyboardHandler(
|
|
|
351
359
|
case 'seekForward':
|
|
352
360
|
actions.seekForward()
|
|
353
361
|
break
|
|
362
|
+
case 'seekBackwardLong':
|
|
363
|
+
if (actions.seekBackwardLong) actions.seekBackwardLong()
|
|
364
|
+
else actions.seekBackward()
|
|
365
|
+
break
|
|
366
|
+
case 'seekForwardLong':
|
|
367
|
+
if (actions.seekForwardLong) actions.seekForwardLong()
|
|
368
|
+
else actions.seekForward()
|
|
369
|
+
break
|
|
354
370
|
case 'volumeUp':
|
|
355
371
|
actions.volumeUp()
|
|
356
372
|
break
|
|
@@ -27,6 +27,8 @@ export class MediaAirplayButton extends HTMLElementBase {
|
|
|
27
27
|
button { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 8px; background: transparent; border: 0; color: inherit; cursor: pointer; }
|
|
28
28
|
button:hover { opacity: 0.8; }
|
|
29
29
|
:host([connected]) button { color: var(--media-airplay-active-color, #38bdf8); }
|
|
30
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
31
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
30
32
|
</style>
|
|
31
33
|
<button part="button" type="button" aria-label="${label(this, 'airPlayPlay')}">
|
|
32
34
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon"><path d="${AIRPLAY_ICON}"/></svg>
|
|
@@ -28,6 +28,8 @@ export class MediaFullscreenButton extends HTMLElementBase {
|
|
|
28
28
|
}
|
|
29
29
|
button:hover { opacity: 0.8; }
|
|
30
30
|
button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
31
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
32
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
31
33
|
</style>
|
|
32
34
|
<button part="button" type="button" aria-label="${label(this, 'enterFullscreen')}">
|
|
33
35
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
|
|
@@ -20,6 +20,8 @@ export class MediaLiveButton extends HTMLElementBase {
|
|
|
20
20
|
button { display: inline-flex; align-items: center; gap: 6px; min-height: 40px; padding: 8px; background: transparent; border: 0; color: inherit; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; }
|
|
21
21
|
i { width: 8px; height: 8px; border-radius: 9999px; background: #ef4444; }
|
|
22
22
|
:host(:not([at-live-edge])) button { opacity: 0.72; }
|
|
23
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
24
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
23
25
|
</style>
|
|
24
26
|
<button part="button" type="button" aria-label="${label(this, 'seekToLive')}"><i aria-hidden="true"></i><span>${label(this, 'live')}</span></button>
|
|
25
27
|
`
|
|
@@ -33,6 +33,8 @@ export class MediaMuteButton extends HTMLElementBase {
|
|
|
33
33
|
background: transparent; border: none; color: inherit; cursor: pointer;
|
|
34
34
|
}
|
|
35
35
|
button:hover { opacity: 0.8; }
|
|
36
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
37
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
36
38
|
</style>
|
|
37
39
|
<button part="button" type="button" aria-label="${label(this, 'mute')}">
|
|
38
40
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
|
|
@@ -27,6 +27,8 @@ export class MediaPipButton extends HTMLElementBase {
|
|
|
27
27
|
}
|
|
28
28
|
button:hover { opacity: 0.8; }
|
|
29
29
|
button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
30
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
31
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
30
32
|
</style>
|
|
31
33
|
<button part="button" type="button" aria-label="${label(this, 'enterPiP')}">
|
|
32
34
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
|
|
@@ -27,6 +27,8 @@ export class MediaPlayButton extends HTMLElementBase {
|
|
|
27
27
|
background: transparent; border: none; color: inherit; cursor: pointer;
|
|
28
28
|
}
|
|
29
29
|
button:hover { opacity: 0.8; }
|
|
30
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
31
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
30
32
|
</style>
|
|
31
33
|
<button part="button" type="button" aria-label="${label(this, 'play')}">
|
|
32
34
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon">
|
|
@@ -18,6 +18,8 @@ export class MediaPlaybackRateButton extends HTMLElementBase {
|
|
|
18
18
|
button { min-width: 42px; height: 40px; padding: 0 8px; border: 0; background: transparent; color: inherit; cursor: pointer; font: inherit; font-variant-numeric: tabular-nums; }
|
|
19
19
|
button:hover { opacity: .8; }
|
|
20
20
|
button:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
|
|
21
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
22
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
21
23
|
</style>
|
|
22
24
|
<button part="button" type="button" aria-label="${label(this, 'speed')}"></button>
|
|
23
25
|
`
|
|
@@ -65,6 +65,8 @@ export class MediaProgressBar extends HTMLElementBase {
|
|
|
65
65
|
border-top-color: rgba(0,0,0,0.85);
|
|
66
66
|
}
|
|
67
67
|
.container:hover .tooltip { opacity: 1; }
|
|
68
|
+
/* A finger needs more than the 20px a mouse does. */
|
|
69
|
+
@media (pointer: coarse) { .container { height: 36px; } .thumb { width: 18px; height: 18px; } }
|
|
68
70
|
</style>
|
|
69
71
|
<div class="container" part="container" role="slider"
|
|
70
72
|
aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"
|
|
@@ -32,6 +32,8 @@ export class MediaRemotePlaybackButton extends HTMLElementBase {
|
|
|
32
32
|
button { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 8px; background: transparent; border: 0; color: inherit; cursor: pointer; }
|
|
33
33
|
button:hover { opacity: 0.8; }
|
|
34
34
|
:host([connected]) button { color: var(--media-remote-active-color, #38bdf8); }
|
|
35
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
36
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
35
37
|
</style>
|
|
36
38
|
<button part="button" type="button" aria-label="${label(this, 'remotePlayback')}">
|
|
37
39
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor" part="icon"><path d="${REMOTE_ICON}"/></svg>
|
|
@@ -17,6 +17,8 @@ export class MediaSeekButton extends HTMLElementBase {
|
|
|
17
17
|
button:hover { opacity: .8; }
|
|
18
18
|
button:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
|
|
19
19
|
button:disabled { cursor: default; opacity: .45; }
|
|
20
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
21
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
20
22
|
</style>
|
|
21
23
|
<button part="button" type="button"></button>
|
|
22
24
|
`
|
|
@@ -54,6 +54,8 @@ export class MediaSettingsMenu extends HTMLElementBase {
|
|
|
54
54
|
.item[aria-checked="true"]::before { content: '\\2713'; }
|
|
55
55
|
.item[aria-checked="false"]::before { content: ''; min-width: 12px; display: inline-block; }
|
|
56
56
|
.back { font-weight: bold; border-bottom: 1px solid rgba(255,255,255,0.2); }
|
|
57
|
+
/* 44px, the smallest a finger hits reliably, on a touch screen. */
|
|
58
|
+
@media (pointer: coarse) { button { min-width: 44px; min-height: 44px; } }
|
|
57
59
|
</style>
|
|
58
60
|
<button class="trigger" part="trigger" type="button"
|
|
59
61
|
aria-label="${say(this, 'settings')}" aria-haspopup="true" aria-expanded="false">
|
|
@@ -42,6 +42,8 @@ export class MediaVolumeSlider extends HTMLElementBase {
|
|
|
42
42
|
transition: transform 0.1s;
|
|
43
43
|
}
|
|
44
44
|
.container:hover .thumb { transform: translate(-50%, -50%) scale(1.2); }
|
|
45
|
+
/* A finger needs more than the 20px a mouse does. */
|
|
46
|
+
@media (pointer: coarse) { .container { height: 36px; } .thumb { width: 18px; height: 18px; } }
|
|
45
47
|
</style>
|
|
46
48
|
<div class="container" part="container" role="slider"
|
|
47
49
|
aria-valuemin="0" aria-valuemax="100" aria-valuenow="100"
|
|
@@ -31,7 +31,9 @@ export class VideoPlayerElement extends HTMLElementBase {
|
|
|
31
31
|
if (this._player) return
|
|
32
32
|
|
|
33
33
|
// Create shadow DOM
|
|
34
|
-
|
|
34
|
+
// delegatesFocus: focusing <video-player> (a dialog's first focus, an
|
|
35
|
+
// `autofocus`) puts focus on the player itself, where its keys work.
|
|
36
|
+
const shadow = this.shadowRoot ?? this.attachShadow({ mode: 'open', delegatesFocus: true })
|
|
35
37
|
|
|
36
38
|
// Container
|
|
37
39
|
this._container = document.createElement('div')
|
package/src/player.ts
CHANGED
|
@@ -263,6 +263,8 @@ export class Player implements IPlayer {
|
|
|
263
263
|
toggleCaptions: () => this.toggleCaptions(),
|
|
264
264
|
seekBackward: () => this.seekBy(-5),
|
|
265
265
|
seekForward: () => this.seekBy(5),
|
|
266
|
+
seekBackwardLong: () => this.seekBy(-10),
|
|
267
|
+
seekForwardLong: () => this.seekBy(10),
|
|
266
268
|
volumeUp: () => this.setVolume(Math.min(1, this.state.volume + 0.1)),
|
|
267
269
|
volumeDown: () => this.setVolume(Math.max(0, this.state.volume - 0.1)),
|
|
268
270
|
speedUp: () => this.setPlaybackRate(Math.min(4, this.state.playbackRate + 0.25)),
|
|
@@ -599,7 +601,10 @@ export class Player implements IPlayer {
|
|
|
599
601
|
})
|
|
600
602
|
|
|
601
603
|
provider.on('pause', () => {
|
|
602
|
-
|
|
604
|
+
// Not playing any more either: the controls hide only while playing,
|
|
605
|
+
// and a paused player that still said `playing` hid them over a still
|
|
606
|
+
// frame, and told everyone reading state.playing it was still going.
|
|
607
|
+
this._store.batch({ paused: true, playing: false, playbackState: 'paused' })
|
|
603
608
|
this._events.emit('pause')
|
|
604
609
|
this.announce(label(this._el, 'paused'))
|
|
605
610
|
})
|