@kodezen/player 1.0.2 → 1.0.4
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 +21 -1
- package/dist/index.cjs +244 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +244 -19
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +248 -21
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +248 -21
- package/dist/react/index.js.map +1 -1
- package/dist/styles.css +100 -9
- package/dist/{types-TFltFgey.d.cts → types-30yBj4ll.d.cts} +20 -1
- package/dist/{types-TFltFgey.d.ts → types-30yBj4ll.d.ts} +20 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,7 +70,8 @@ import '@kodezen/player/styles.css';
|
|
|
70
70
|
`createPlayer(container, options)` → instance.
|
|
71
71
|
|
|
72
72
|
**Options:** `source {type,url,id,poster,mime}`, `resumeAt`, `lockSeek`,
|
|
73
|
-
`autoplay`, `muted`, `speeds[]`, `controls{}`, `className
|
|
73
|
+
`autoplay`, `muted`, `speeds[]`, `controls{}`, `className`,
|
|
74
|
+
`tracks[] {src,label,srclang,default}`.
|
|
74
75
|
|
|
75
76
|
**Instance:** `play()`, `pause()`, `toggle()`, `seek(s)`, `getState()`,
|
|
76
77
|
`on(event, cb)`, `off(event, cb)`, `destroy()`.
|
|
@@ -80,6 +81,25 @@ import '@kodezen/player/styles.css';
|
|
|
80
81
|
(`percent` = furthest / duration, so it reflects watched coverage, not the
|
|
81
82
|
scrub position).
|
|
82
83
|
|
|
84
|
+
## Subtitles / captions
|
|
85
|
+
|
|
86
|
+
Pass WebVTT files as `tracks`. The player fetches and renders them itself in
|
|
87
|
+
an overlay, so the same `.vtt` works for `html5`, `youtube` and `vimeo`
|
|
88
|
+
(YouTube/Vimeo iframes can't load an external caption file). A CC button
|
|
89
|
+
toggles a single track, or opens a menu when there are several. A track with
|
|
90
|
+
`default: true` is shown as soon as it loads; otherwise captions start off.
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
createPlayer(el, {
|
|
94
|
+
source: { type: 'youtube', id: 'dQw4w9WgXcQ' },
|
|
95
|
+
tracks: [{ src: '/uploads/lesson-1.vtt', label: 'English', srclang: 'en', default: true }],
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The file must be same-origin or served with CORS. Cue settings
|
|
100
|
+
(position/alignment) and styling blocks are ignored — cues render as plain
|
|
101
|
+
centred text. `parseVtt(text)` is exported if you need the cues yourself.
|
|
102
|
+
|
|
83
103
|
## Host integration notes
|
|
84
104
|
|
|
85
105
|
- **Resume** = pass `resumeAt`; player seeks on `ready` unless within 2s of end.
|
package/dist/index.cjs
CHANGED
|
@@ -93,8 +93,7 @@ var Html5Provider = class {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
play() {
|
|
96
|
-
void this.video.play().catch(() =>
|
|
97
|
-
});
|
|
96
|
+
void this.video.play().catch(() => this.emitter.emit("pause"));
|
|
98
97
|
}
|
|
99
98
|
pause() {
|
|
100
99
|
this.video.pause();
|
|
@@ -376,8 +375,8 @@ var VimeoProvider = class {
|
|
|
376
375
|
this.emitter.emit("ready");
|
|
377
376
|
}
|
|
378
377
|
play() {
|
|
379
|
-
var _a, _b;
|
|
380
|
-
|
|
378
|
+
var _a, _b, _c, _d;
|
|
379
|
+
(_d = (_c = (_b = (_a = this.player) == null ? void 0 : _a.play) == null ? void 0 : _b.call(_a)) == null ? void 0 : _c.catch) == null ? void 0 : _d.call(_c, () => this.emitter.emit("pause"));
|
|
381
380
|
}
|
|
382
381
|
pause() {
|
|
383
382
|
var _a, _b;
|
|
@@ -447,13 +446,124 @@ function createProvider(source) {
|
|
|
447
446
|
}
|
|
448
447
|
}
|
|
449
448
|
|
|
449
|
+
// src/core/Captions.ts
|
|
450
|
+
var TIMING = /^\s*((?:\d+:)?\d{1,2}:\d{2}[.,]\d{1,3})\s+-->\s+((?:\d+:)?\d{1,2}:\d{2}[.,]\d{1,3})/;
|
|
451
|
+
var ENTITIES = {
|
|
452
|
+
amp: "&",
|
|
453
|
+
lt: "<",
|
|
454
|
+
gt: ">",
|
|
455
|
+
quot: '"',
|
|
456
|
+
apos: "'",
|
|
457
|
+
nbsp: "\xA0",
|
|
458
|
+
lrm: "\u200E",
|
|
459
|
+
rlm: "\u200F"
|
|
460
|
+
};
|
|
461
|
+
function toSeconds(stamp) {
|
|
462
|
+
const [clock = "", fraction = "0"] = stamp.replace(",", ".").split(".");
|
|
463
|
+
const seconds = clock.split(":").reduce((total, part) => total * 60 + Number(part), 0);
|
|
464
|
+
return seconds + Number(`0.${fraction}`);
|
|
465
|
+
}
|
|
466
|
+
function plainText(text) {
|
|
467
|
+
return text.replace(/<[^>]*>/g, "").replace(/&(amp|lt|gt|quot|apos|nbsp|lrm|rlm);/g, (_, name) => {
|
|
468
|
+
var _a;
|
|
469
|
+
return (_a = ENTITIES[name]) != null ? _a : "";
|
|
470
|
+
}).replace(/&#(\d+);/g, (_, code) => String.fromCodePoint(Number(code)));
|
|
471
|
+
}
|
|
472
|
+
function parseVtt(input) {
|
|
473
|
+
var _a;
|
|
474
|
+
const cues = [];
|
|
475
|
+
const blocks = input.replace(/^/, "").replace(/\r\n?/g, "\n").split(/\n{2,}/);
|
|
476
|
+
for (const block of blocks) {
|
|
477
|
+
const lines = block.split("\n");
|
|
478
|
+
const timingAt = lines.findIndex((line) => line.includes("-->"));
|
|
479
|
+
const timing = TIMING.exec((_a = lines[timingAt]) != null ? _a : "");
|
|
480
|
+
if (!(timing == null ? void 0 : timing[1]) || !timing[2]) continue;
|
|
481
|
+
const text = plainText(lines.slice(timingAt + 1).join("\n")).trim();
|
|
482
|
+
if (!text) continue;
|
|
483
|
+
cues.push({ start: toSeconds(timing[1]), end: toSeconds(timing[2]), text });
|
|
484
|
+
}
|
|
485
|
+
return cues.sort((a, b) => a.start - b.start);
|
|
486
|
+
}
|
|
487
|
+
var Captions = class {
|
|
488
|
+
/** `onChange` fires when the active track or track availability changes. */
|
|
489
|
+
constructor(tracks, onChange) {
|
|
490
|
+
this.onChange = onChange;
|
|
491
|
+
this.active = -1;
|
|
492
|
+
this.time = 0;
|
|
493
|
+
this.abort = new AbortController();
|
|
494
|
+
this.el = document.createElement("div");
|
|
495
|
+
this.el.className = "kzp-captions";
|
|
496
|
+
this.el.setAttribute("aria-live", "off");
|
|
497
|
+
this.text = document.createElement("span");
|
|
498
|
+
this.text.className = "kzp-captions-text";
|
|
499
|
+
this.text.hidden = true;
|
|
500
|
+
this.el.appendChild(this.text);
|
|
501
|
+
this.tracks = tracks.map((info) => ({ info, cues: null, failed: false }));
|
|
502
|
+
const preferred = tracks.findIndex((t) => t.default);
|
|
503
|
+
this.tracks.forEach((track, index) => {
|
|
504
|
+
void this.load(track).then(() => {
|
|
505
|
+
if (index === preferred && this.active === -1 && track.cues) this.select(index);
|
|
506
|
+
else this.onChange();
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
async load(track) {
|
|
511
|
+
try {
|
|
512
|
+
const res = await fetch(track.info.src, { signal: this.abort.signal, credentials: "same-origin" });
|
|
513
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
514
|
+
track.cues = parseVtt(await res.text());
|
|
515
|
+
} catch (err) {
|
|
516
|
+
if (this.abort.signal.aborted) return;
|
|
517
|
+
track.failed = true;
|
|
518
|
+
console.warn(`@kodezen/player: could not load captions from ${track.info.src}`, err);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/** Labels for the menu, one per track (falls back to the language, then a number). */
|
|
522
|
+
labels() {
|
|
523
|
+
return this.tracks.map((t, i) => t.info.label || t.info.srclang || `Track ${i + 1}`);
|
|
524
|
+
}
|
|
525
|
+
/** Whether each track loaded (a failed track is hidden from the controls). */
|
|
526
|
+
available() {
|
|
527
|
+
return this.tracks.map((t) => !t.failed);
|
|
528
|
+
}
|
|
529
|
+
getActive() {
|
|
530
|
+
return this.active;
|
|
531
|
+
}
|
|
532
|
+
/** Show track `index`, or turn captions off with -1. */
|
|
533
|
+
select(index) {
|
|
534
|
+
var _a;
|
|
535
|
+
const track = this.tracks[index];
|
|
536
|
+
this.active = track && !track.failed ? index : -1;
|
|
537
|
+
this.el.lang = (_a = track == null ? void 0 : track.info.srclang) != null ? _a : "";
|
|
538
|
+
this.render();
|
|
539
|
+
this.onChange();
|
|
540
|
+
}
|
|
541
|
+
/** Called with the playhead position on every tick and after a seek. */
|
|
542
|
+
update(time) {
|
|
543
|
+
this.time = time;
|
|
544
|
+
this.render();
|
|
545
|
+
}
|
|
546
|
+
render() {
|
|
547
|
+
var _a, _b;
|
|
548
|
+
const cues = (_b = (_a = this.tracks[this.active]) == null ? void 0 : _a.cues) != null ? _b : [];
|
|
549
|
+
const text = cues.filter((cue) => this.time >= cue.start && this.time < cue.end).map((cue) => cue.text).join("\n");
|
|
550
|
+
if (text !== this.text.textContent) this.text.textContent = text;
|
|
551
|
+
this.text.hidden = !text;
|
|
552
|
+
}
|
|
553
|
+
destroy() {
|
|
554
|
+
this.abort.abort();
|
|
555
|
+
this.el.remove();
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
|
|
450
559
|
// src/core/Controls.ts
|
|
451
560
|
var ICONS = {
|
|
452
561
|
play: '<path d="M8 5v14l11-7z"/>',
|
|
453
562
|
pause: '<path d="M6 5h4v14H6zM14 5h4v14h-4z"/>',
|
|
454
563
|
volume: '<path d="M3 10v4h4l5 5V5L7 10H3zm13.5 2a4.5 4.5 0 0 0-2.5-4v8a4.5 4.5 0 0 0 2.5-4z"/>',
|
|
455
564
|
muted: '<path d="M3 10v4h4l5 5V5L7 10H3zm13 2 3 3 1-1-3-3 3-3-1-1-3 3-3-3-1 1 3 3-3 3 1 1z"/>',
|
|
456
|
-
full: '<path d="M4 4h6v2H6v4H4V4zm10 0h6v6h-2V6h-4V4zM4 14h2v4h4v2H4v-6zm14 0h2v6h-6v-2h4v-4z"/>'
|
|
565
|
+
full: '<path d="M4 4h6v2H6v4H4V4zm10 0h6v6h-2V6h-4V4zM4 14h2v4h4v2H4v-6zm14 0h2v6h-6v-2h4v-4z"/>',
|
|
566
|
+
captions: '<path d="M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1z"/>'
|
|
457
567
|
};
|
|
458
568
|
function icon(paths) {
|
|
459
569
|
return `<svg viewBox="0 0 24 24" aria-hidden="true">${paths}</svg>`;
|
|
@@ -473,20 +583,34 @@ var DEFAULT_CONFIG = {
|
|
|
473
583
|
time: true,
|
|
474
584
|
volume: true,
|
|
475
585
|
speed: true,
|
|
586
|
+
captions: true,
|
|
476
587
|
fullscreen: true
|
|
477
588
|
};
|
|
478
589
|
var Controls = class {
|
|
479
590
|
constructor(cb, opts) {
|
|
480
591
|
this.cb = cb;
|
|
592
|
+
this.ccActive = -1;
|
|
593
|
+
// Icons currently drawn, so update() only rewrites a button's SVG when it
|
|
594
|
+
// actually changes (see update()).
|
|
595
|
+
this.playIcon = "";
|
|
596
|
+
this.volIcon = "";
|
|
481
597
|
this.scrubbing = false;
|
|
598
|
+
this.rate = 0;
|
|
599
|
+
this.onOutsideClick = (e) => {
|
|
600
|
+
if (this.el.contains(e.target)) return;
|
|
601
|
+
if (this.speedMenu) this.speedMenu.hidden = true;
|
|
602
|
+
if (this.ccMenu) this.ccMenu.hidden = true;
|
|
603
|
+
};
|
|
482
604
|
this.seekableMax = Infinity;
|
|
605
|
+
var _a;
|
|
483
606
|
this.config = { ...DEFAULT_CONFIG, ...opts.config };
|
|
484
607
|
this.lockSeek = opts.lockSeek;
|
|
485
608
|
this.el = document.createElement("div");
|
|
486
609
|
this.el.className = "kzp-controls";
|
|
487
|
-
this.build(opts.speeds);
|
|
610
|
+
this.build(opts.speeds, (_a = opts.captionLabels) != null ? _a : []);
|
|
611
|
+
document.addEventListener("click", this.onOutsideClick);
|
|
488
612
|
}
|
|
489
|
-
build(speeds) {
|
|
613
|
+
build(speeds, captionLabels) {
|
|
490
614
|
if (this.config.playPause) {
|
|
491
615
|
this.playBtn = this.button("kzp-play", "Play", ICONS.play);
|
|
492
616
|
this.playBtn.addEventListener("click", () => this.cb.onToggle());
|
|
@@ -563,16 +687,72 @@ var Controls = class {
|
|
|
563
687
|
}
|
|
564
688
|
this.speedBtn.addEventListener("click", () => {
|
|
565
689
|
this.speedMenu.hidden = !this.speedMenu.hidden;
|
|
690
|
+
if (this.ccMenu) this.ccMenu.hidden = true;
|
|
566
691
|
});
|
|
567
692
|
wrap.append(this.speedBtn, this.speedMenu);
|
|
568
693
|
this.el.appendChild(wrap);
|
|
569
694
|
}
|
|
695
|
+
if (this.config.captions && captionLabels.length) {
|
|
696
|
+
this.buildCaptions(captionLabels);
|
|
697
|
+
}
|
|
570
698
|
if (this.config.fullscreen) {
|
|
571
699
|
const btn = this.button("kzp-full", "Fullscreen", ICONS.full);
|
|
572
700
|
btn.addEventListener("click", () => this.cb.onFullscreen());
|
|
573
701
|
this.el.appendChild(btn);
|
|
574
702
|
}
|
|
575
703
|
}
|
|
704
|
+
/**
|
|
705
|
+
* One track: the CC button toggles it. Several: it opens a menu (Off + each
|
|
706
|
+
* track), styled like the speed menu. Sits left of the speed control.
|
|
707
|
+
*/
|
|
708
|
+
buildCaptions(labels) {
|
|
709
|
+
const wrap = document.createElement("div");
|
|
710
|
+
wrap.className = "kzp-cc";
|
|
711
|
+
this.ccBtn = this.button("kzp-cc-btn", "Subtitles/closed captions", ICONS.captions);
|
|
712
|
+
this.ccBtn.setAttribute("aria-pressed", "false");
|
|
713
|
+
wrap.appendChild(this.ccBtn);
|
|
714
|
+
if (labels.length === 1) {
|
|
715
|
+
this.ccBtn.addEventListener("click", () => this.cb.onCaptions(this.ccActive === -1 ? 0 : -1));
|
|
716
|
+
} else {
|
|
717
|
+
this.ccMenu = document.createElement("div");
|
|
718
|
+
this.ccMenu.className = "kzp-speed-menu kzp-cc-menu";
|
|
719
|
+
this.ccMenu.hidden = true;
|
|
720
|
+
const items = [{ index: -1, label: "Off" }, ...labels.map((label, index) => ({ index, label }))];
|
|
721
|
+
for (const { index, label } of items) {
|
|
722
|
+
const item = document.createElement("button");
|
|
723
|
+
item.type = "button";
|
|
724
|
+
item.textContent = label;
|
|
725
|
+
item.dataset.track = String(index);
|
|
726
|
+
item.addEventListener("click", () => {
|
|
727
|
+
this.cb.onCaptions(index);
|
|
728
|
+
this.ccMenu.hidden = true;
|
|
729
|
+
});
|
|
730
|
+
this.ccMenu.appendChild(item);
|
|
731
|
+
}
|
|
732
|
+
this.ccBtn.addEventListener("click", () => {
|
|
733
|
+
this.ccMenu.hidden = !this.ccMenu.hidden;
|
|
734
|
+
if (this.speedMenu) this.speedMenu.hidden = true;
|
|
735
|
+
});
|
|
736
|
+
wrap.appendChild(this.ccMenu);
|
|
737
|
+
}
|
|
738
|
+
this.ccWrap = wrap;
|
|
739
|
+
const speed = this.el.querySelector(".kzp-speed");
|
|
740
|
+
this.el.insertBefore(wrap, speed);
|
|
741
|
+
}
|
|
742
|
+
/** Reflect the active caption track, and hide tracks (or the whole control) that failed to load. */
|
|
743
|
+
setCaptions(active, available) {
|
|
744
|
+
var _a;
|
|
745
|
+
this.ccActive = active;
|
|
746
|
+
if (!this.ccBtn || !this.ccWrap) return;
|
|
747
|
+
this.ccWrap.hidden = !available.some(Boolean);
|
|
748
|
+
this.ccBtn.classList.toggle("kzp-cc-btn--on", active !== -1);
|
|
749
|
+
this.ccBtn.setAttribute("aria-pressed", String(active !== -1));
|
|
750
|
+
(_a = this.ccMenu) == null ? void 0 : _a.querySelectorAll("button").forEach((item) => {
|
|
751
|
+
const index = Number(item.dataset.track);
|
|
752
|
+
item.hidden = index !== -1 && !available[index];
|
|
753
|
+
item.classList.toggle("kzp-menu-item--active", index === active);
|
|
754
|
+
});
|
|
755
|
+
}
|
|
576
756
|
button(cls, label, svg) {
|
|
577
757
|
const b = document.createElement("button");
|
|
578
758
|
b.type = "button";
|
|
@@ -589,9 +769,14 @@ var Controls = class {
|
|
|
589
769
|
}
|
|
590
770
|
/** Push the latest state into the DOM. Called on every player timeupdate. */
|
|
591
771
|
update(state, extras) {
|
|
772
|
+
var _a;
|
|
592
773
|
if (this.playBtn) {
|
|
593
|
-
|
|
594
|
-
|
|
774
|
+
const next = state.playing ? ICONS.pause : ICONS.play;
|
|
775
|
+
if (next !== this.playIcon) {
|
|
776
|
+
this.playIcon = next;
|
|
777
|
+
this.playBtn.innerHTML = icon(next);
|
|
778
|
+
this.playBtn.setAttribute("aria-label", state.playing ? "Pause" : "Play");
|
|
779
|
+
}
|
|
595
780
|
}
|
|
596
781
|
if (this.scrubber) {
|
|
597
782
|
this.seekableMax = this.lockSeek ? state.furthest : Infinity;
|
|
@@ -602,14 +787,25 @@ var Controls = class {
|
|
|
602
787
|
this.timeEl.textContent = `${fmt(state.current)} / ${fmt(state.duration)}`;
|
|
603
788
|
}
|
|
604
789
|
if (this.volBtn) {
|
|
605
|
-
|
|
790
|
+
const next = extras.muted || extras.volume === 0 ? ICONS.muted : ICONS.volume;
|
|
791
|
+
if (next !== this.volIcon) {
|
|
792
|
+
this.volIcon = next;
|
|
793
|
+
this.volBtn.innerHTML = icon(next);
|
|
794
|
+
}
|
|
606
795
|
}
|
|
607
796
|
if (this.volRange && document.activeElement !== this.volRange) {
|
|
608
797
|
this.volRange.value = String(extras.muted ? 0 : extras.volume);
|
|
609
798
|
}
|
|
610
|
-
if (this.speedBtn
|
|
799
|
+
if (this.speedBtn && extras.rate !== this.rate) {
|
|
800
|
+
this.rate = extras.rate;
|
|
801
|
+
this.speedBtn.textContent = `${extras.rate}x`;
|
|
802
|
+
(_a = this.speedMenu) == null ? void 0 : _a.querySelectorAll("button").forEach((item) => {
|
|
803
|
+
item.classList.toggle("kzp-menu-item--active", Number(item.dataset.rate) === extras.rate);
|
|
804
|
+
});
|
|
805
|
+
}
|
|
611
806
|
}
|
|
612
807
|
destroy() {
|
|
808
|
+
document.removeEventListener("click", this.onOutsideClick);
|
|
613
809
|
this.el.remove();
|
|
614
810
|
}
|
|
615
811
|
};
|
|
@@ -622,13 +818,14 @@ var Player = class {
|
|
|
622
818
|
this.container = container;
|
|
623
819
|
this.options = options;
|
|
624
820
|
this.emitter = new Emitter();
|
|
821
|
+
this.captions = null;
|
|
625
822
|
this.furthest = 0;
|
|
626
823
|
this.lastTick = 0;
|
|
627
824
|
this.duration = 0;
|
|
628
825
|
this.playing = false;
|
|
629
826
|
this.ready = false;
|
|
630
827
|
this.disposers = [];
|
|
631
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
828
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
632
829
|
const speeds = (_a = options.speeds) != null ? _a : DEFAULT_SPEEDS;
|
|
633
830
|
this.root = document.createElement("div");
|
|
634
831
|
this.root.className = "kzp" + (options.className ? ` ${options.className}` : "");
|
|
@@ -643,6 +840,10 @@ var Player = class {
|
|
|
643
840
|
this.stage.appendChild(hitLayer);
|
|
644
841
|
this.provider = createProvider(options.source);
|
|
645
842
|
const caps = this.provider.capabilities;
|
|
843
|
+
if ((_b = options.tracks) == null ? void 0 : _b.length) {
|
|
844
|
+
this.captions = new Captions(options.tracks, () => this.syncCaptions());
|
|
845
|
+
this.root.appendChild(this.captions.el);
|
|
846
|
+
}
|
|
646
847
|
this.controls = new Controls(
|
|
647
848
|
{
|
|
648
849
|
onToggle: () => this.toggle(),
|
|
@@ -660,17 +861,22 @@ var Player = class {
|
|
|
660
861
|
this.provider.setRate(r);
|
|
661
862
|
this.render();
|
|
662
863
|
},
|
|
864
|
+
onCaptions: (index) => {
|
|
865
|
+
var _a2;
|
|
866
|
+
return (_a2 = this.captions) == null ? void 0 : _a2.select(index);
|
|
867
|
+
},
|
|
663
868
|
onFullscreen: () => this.toggleFullscreen()
|
|
664
869
|
},
|
|
665
870
|
{
|
|
666
871
|
config: {
|
|
667
872
|
...options.controls,
|
|
668
|
-
seek: ((
|
|
669
|
-
speed: ((
|
|
670
|
-
volume: ((
|
|
873
|
+
seek: ((_d = (_c = options.controls) == null ? void 0 : _c.seek) != null ? _d : true) && caps.seekable,
|
|
874
|
+
speed: ((_f = (_e = options.controls) == null ? void 0 : _e.speed) != null ? _f : true) && caps.rate,
|
|
875
|
+
volume: ((_h = (_g = options.controls) == null ? void 0 : _g.volume) != null ? _h : true) && caps.volume
|
|
671
876
|
},
|
|
672
877
|
speeds,
|
|
673
|
-
lockSeek: !!options.lockSeek
|
|
878
|
+
lockSeek: !!options.lockSeek,
|
|
879
|
+
captionLabels: (_i = this.captions) == null ? void 0 : _i.labels()
|
|
674
880
|
}
|
|
675
881
|
);
|
|
676
882
|
this.root.appendChild(this.controls.el);
|
|
@@ -723,6 +929,7 @@ var Player = class {
|
|
|
723
929
|
this.render();
|
|
724
930
|
}
|
|
725
931
|
onTimeUpdate(current) {
|
|
932
|
+
var _a;
|
|
726
933
|
const advance = current - this.lastTick;
|
|
727
934
|
if (advance > 0 && advance <= NATURAL_ADVANCE_MAX && current > this.furthest) {
|
|
728
935
|
this.furthest = current;
|
|
@@ -730,9 +937,14 @@ var Player = class {
|
|
|
730
937
|
this.provider.seek(this.furthest);
|
|
731
938
|
}
|
|
732
939
|
this.lastTick = current;
|
|
940
|
+
(_a = this.captions) == null ? void 0 : _a.update(current);
|
|
733
941
|
this.emitter.emit("timeupdate", this.snapshot());
|
|
734
942
|
this.render();
|
|
735
943
|
}
|
|
944
|
+
syncCaptions() {
|
|
945
|
+
if (!this.captions) return;
|
|
946
|
+
this.controls.setCaptions(this.captions.getActive(), this.captions.available());
|
|
947
|
+
}
|
|
736
948
|
snapshot() {
|
|
737
949
|
const current = this.provider.getCurrentTime();
|
|
738
950
|
const duration = this.duration || this.provider.getDuration();
|
|
@@ -755,21 +967,32 @@ var Player = class {
|
|
|
755
967
|
}
|
|
756
968
|
}
|
|
757
969
|
// ---- Public API ----
|
|
970
|
+
// play()/pause() record the intent at once instead of waiting for the
|
|
971
|
+
// provider to confirm: YouTube only reports PLAYING after buffering, and
|
|
972
|
+
// until then a second click read "not playing" and called play() again
|
|
973
|
+
// (a no-op) instead of pausing. The provider's play/pause/ended events
|
|
974
|
+
// still correct it, e.g. when the browser blocks playback.
|
|
758
975
|
play() {
|
|
976
|
+
this.playing = true;
|
|
759
977
|
this.provider.play();
|
|
978
|
+
this.render();
|
|
760
979
|
}
|
|
761
980
|
pause() {
|
|
981
|
+
this.playing = false;
|
|
762
982
|
this.provider.pause();
|
|
983
|
+
this.render();
|
|
763
984
|
}
|
|
764
985
|
toggle() {
|
|
765
|
-
if (this.
|
|
766
|
-
else this.
|
|
986
|
+
if (this.playing) this.pause();
|
|
987
|
+
else this.play();
|
|
767
988
|
}
|
|
768
989
|
seek(seconds) {
|
|
990
|
+
var _a;
|
|
769
991
|
if (!this.provider.capabilities.seekable) return;
|
|
770
992
|
const target = this.options.lockSeek && seconds > this.furthest ? this.furthest : Math.max(0, seconds);
|
|
771
993
|
this.provider.seek(target);
|
|
772
994
|
this.lastTick = target;
|
|
995
|
+
(_a = this.captions) == null ? void 0 : _a.update(target);
|
|
773
996
|
this.render();
|
|
774
997
|
}
|
|
775
998
|
setLockSeek(locked) {
|
|
@@ -788,9 +1011,11 @@ var Player = class {
|
|
|
788
1011
|
this.emitter.off(event, cb);
|
|
789
1012
|
}
|
|
790
1013
|
destroy() {
|
|
1014
|
+
var _a;
|
|
791
1015
|
for (const dispose of this.disposers) dispose();
|
|
792
1016
|
this.disposers = [];
|
|
793
1017
|
this.provider.destroy();
|
|
1018
|
+
(_a = this.captions) == null ? void 0 : _a.destroy();
|
|
794
1019
|
this.controls.destroy();
|
|
795
1020
|
this.emitter.clear();
|
|
796
1021
|
this.root.remove();
|
|
@@ -804,5 +1029,6 @@ function createPlayer(container, options) {
|
|
|
804
1029
|
|
|
805
1030
|
exports.Player = Player;
|
|
806
1031
|
exports.createPlayer = createPlayer;
|
|
1032
|
+
exports.parseVtt = parseVtt;
|
|
807
1033
|
//# sourceMappingURL=index.cjs.map
|
|
808
1034
|
//# sourceMappingURL=index.cjs.map
|