vidply 1.0.39 → 1.0.41
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/LICENSE +1 -1
- package/README.md +14 -4
- package/dist/dev/vidply.AudioDescriptionManager-XG32DISZ.js +559 -0
- package/dist/dev/vidply.AudioDescriptionManager-XG32DISZ.js.map +7 -0
- package/dist/dev/{vidply.HLSRenderer-ZLTE6K3O.js → vidply.HLSRenderer-QXQPR4KT.js} +64 -1
- package/dist/dev/vidply.HLSRenderer-QXQPR4KT.js.map +7 -0
- package/dist/dev/vidply.SignLanguageManager-YQ2ETTUO.js +1209 -0
- package/dist/dev/vidply.SignLanguageManager-YQ2ETTUO.js.map +7 -0
- package/dist/dev/{vidply.TranscriptManager-UWM2WNAV.js → vidply.TranscriptManager-VCRQXB5W.js} +57 -21
- package/dist/dev/vidply.TranscriptManager-VCRQXB5W.js.map +7 -0
- package/dist/dev/vidply.chunk-7TDF7KK7.js +79 -0
- package/dist/dev/vidply.chunk-7TDF7KK7.js.map +7 -0
- package/dist/dev/vidply.chunk-JZWZJC4C.js +203 -0
- package/dist/dev/vidply.chunk-JZWZJC4C.js.map +7 -0
- package/dist/dev/{vidply.chunk-LD3OELXW.js → vidply.chunk-MQRVLOTX.js} +7 -769
- package/dist/dev/vidply.chunk-MQRVLOTX.js.map +7 -0
- package/dist/dev/vidply.chunk-RX6NLSHH.js +410 -0
- package/dist/dev/vidply.chunk-RX6NLSHH.js.map +7 -0
- package/dist/dev/vidply.chunk-XAFVSP6D.js +577 -0
- package/dist/dev/vidply.chunk-XAFVSP6D.js.map +7 -0
- package/dist/dev/{vidply.de-IXUNTP3C.js → vidply.de-BV6HZDQX.js} +6 -1
- package/dist/dev/{vidply.de-IXUNTP3C.js.map → vidply.de-BV6HZDQX.js.map} +2 -2
- package/dist/dev/{vidply.es-PSWHNCXC.js → vidply.es-CRD7N5YZ.js} +6 -1
- package/dist/dev/{vidply.es-PSWHNCXC.js.map → vidply.es-CRD7N5YZ.js.map} +2 -2
- package/dist/dev/vidply.esm.js +789 -2212
- package/dist/dev/vidply.esm.js.map +4 -4
- package/dist/dev/{vidply.fr-3ZGYEON2.js → vidply.fr-WIFNQTHI.js} +6 -1
- package/dist/dev/{vidply.fr-3ZGYEON2.js.map → vidply.fr-WIFNQTHI.js.map} +2 -2
- package/dist/dev/{vidply.ja-HS2NMBQZ.js → vidply.ja-FPCJXERD.js} +6 -1
- package/dist/dev/{vidply.ja-HS2NMBQZ.js.map → vidply.ja-FPCJXERD.js.map} +2 -2
- package/dist/legacy/vidply.js +8114 -7185
- package/dist/legacy/vidply.js.map +4 -4
- package/dist/legacy/vidply.min.js +1 -1
- package/dist/legacy/vidply.min.meta.json +70 -64
- package/dist/prod/vidply.AudioDescriptionManager-S2B3YPBT.min.js +6 -0
- package/dist/prod/vidply.HLSRenderer-TBGNB3HF.min.js +6 -0
- package/dist/prod/{vidply.HTML5Renderer-4DX43LUF.min.js → vidply.HTML5Renderer-HTHCSDW2.min.js} +1 -1
- package/dist/prod/vidply.SignLanguageManager-6M4S4KNR.min.js +6 -0
- package/dist/prod/vidply.SoundCloudRenderer-S7DCYVO3.min.js +6 -0
- package/dist/prod/vidply.TranscriptManager-457ACZUN.min.js +6 -0
- package/dist/prod/vidply.VimeoRenderer-PVOJ6FAM.min.js +6 -0
- package/dist/prod/vidply.YouTubeRenderer-CTEREOSQ.min.js +6 -0
- package/dist/prod/vidply.chunk-7OK6TG2Y.min.js +6 -0
- package/dist/prod/vidply.chunk-AE2Z46L5.min.js +6 -0
- package/dist/prod/vidply.chunk-AK4IZ5DH.min.js +6 -0
- package/dist/prod/vidply.chunk-G2P52V5M.min.js +6 -0
- package/dist/prod/vidply.chunk-NFACYQG2.min.js +6 -0
- package/dist/prod/vidply.chunk-YLKHFL57.min.js +6 -0
- package/dist/prod/{vidply.de-MFGEETCC.min.js → vidply.de-235U7FBB.min.js} +1 -1
- package/dist/prod/{vidply.es-HZGMS5GG.min.js → vidply.es-KAKOGBQZ.min.js} +1 -1
- package/dist/prod/vidply.esm.min.js +1 -16
- package/dist/prod/{vidply.fr-HW3KSCFM.min.js → vidply.fr-5CSEV3MB.min.js} +1 -1
- package/dist/prod/{vidply.ja-GXSYRBQY.min.js → vidply.ja-7EI7KZ6G.min.js} +1 -1
- package/dist/vidply.css +1057 -1
- package/dist/vidply.esm.min.meta.json +273 -139
- package/dist/vidply.min.css +1 -1
- package/package.json +3 -2
- package/src/controls/CaptionManager.js +32 -0
- package/src/controls/ControlBar.js +283 -16
- package/src/controls/TranscriptManager.js +69 -18
- package/src/core/AudioDescriptionManager.js +23 -6
- package/src/core/Player.js +569 -47
- package/src/features/PlaylistManager.js +1705 -1705
- package/src/i18n/languages/de.js +5 -0
- package/src/i18n/languages/en.js +5 -0
- package/src/i18n/languages/es.js +5 -0
- package/src/i18n/languages/fr.js +5 -0
- package/src/i18n/languages/ja.js +5 -0
- package/src/icons/Icons.js +2 -2
- package/src/index.js +101 -2
- package/src/renderers/HLSRenderer.js +81 -0
- package/src/styles/vidply.css +1057 -1
- package/src/utils/StorageManager.js +76 -0
- package/dist/dev/vidply.HLSRenderer-ZLTE6K3O.js.map +0 -7
- package/dist/dev/vidply.TranscriptManager-UWM2WNAV.js.map +0 -7
- package/dist/dev/vidply.chunk-LD3OELXW.js.map +0 -7
- package/dist/prod/vidply.HLSRenderer-YLZMDGS2.min.js +0 -6
- package/dist/prod/vidply.SoundCloudRenderer-D2FNOEG6.min.js +0 -6
- package/dist/prod/vidply.TranscriptManager-AYJL4AG3.min.js +0 -6
- package/dist/prod/vidply.VimeoRenderer-QELFZVDU.min.js +0 -6
- package/dist/prod/vidply.YouTubeRenderer-ZL6YUHTF.min.js +0 -6
- package/dist/prod/vidply.chunk-I7BQYXMN.min.js +0 -6
- package/dist/prod/vidply.chunk-LAFINYE4.min.js +0 -6
package/LICENSE
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
GNU GENERAL PUBLIC LICENSE
|
|
2
2
|
Version 2, June 1991
|
|
3
3
|
|
|
4
|
-
Copyright (C)
|
|
4
|
+
Copyright (C) 2026 Matthias Peltzer
|
|
5
5
|
|
|
6
6
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
|
7
7
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ A modern, feature-rich media player built with vanilla ES6 JavaScript. Combines
|
|
|
7
7
|

|
|
8
8
|

|
|
9
9
|

|
|
10
|
-

|
|
11
11
|
|
|
12
12
|
## Live Demos
|
|
13
13
|
|
|
@@ -15,6 +15,7 @@ Try VidPly in action:
|
|
|
15
15
|
- **[Main Demo](https://matthiaspeltzer.github.io/vidply/demo/demo.html)** - Full-featured video player showcase
|
|
16
16
|
- **[Audio Playlist](https://matthiaspeltzer.github.io/vidply/demo/playlist-audio.html)** - Audio player with playlist support
|
|
17
17
|
- **[Video Playlist](https://matthiaspeltzer.github.io/vidply/demo/playlist-video.html)** - Video playlist with thumbnails
|
|
18
|
+
- **[Mixed Playlist](https://matthiaspeltzer.github.io/vidply/demo/playlist-mixed.html)** - Combined audio and video playlist
|
|
18
19
|
- **[HLS Streaming](https://matthiaspeltzer.github.io/vidply/demo/hls-test.html)** - Adaptive bitrate streaming demo
|
|
19
20
|
|
|
20
21
|
## Why VidPly?
|
|
@@ -33,10 +34,12 @@ Try VidPly in action:
|
|
|
33
34
|
- **Multiple Formats** - MP3, OGG, WAV (audio) / MP4, WebM (video)
|
|
34
35
|
- **YouTube Integration** - Embed YouTube videos with unified controls
|
|
35
36
|
- **Vimeo Integration** - Seamless Vimeo player integration
|
|
36
|
-
- **HLS Streaming** - Adaptive bitrate streaming with quality selection
|
|
37
|
+
- **HLS Streaming** - Adaptive bitrate streaming with quality selection and dynamic subtitle detection
|
|
38
|
+
- **Preview Thumbnails** - Video preview thumbnails on progress bar hover
|
|
37
39
|
- **Playlists** - Full playlist support with auto-advance and navigation
|
|
38
40
|
- Audio playlists with track info
|
|
39
41
|
- Video playlists with thumbnails
|
|
42
|
+
- **Mixed playlists** - Combine audio and video in a single playlist
|
|
40
43
|
- Previous/Next controls
|
|
41
44
|
- Visual playlist panel
|
|
42
45
|
- **Fullscreen Mode**: YouTube-style horizontal carousel
|
|
@@ -301,7 +304,11 @@ const player = new Player('#video', {
|
|
|
301
304
|
|
|
302
305
|
// Advanced
|
|
303
306
|
debug: false,
|
|
304
|
-
pauseOthersOnPlay: true
|
|
307
|
+
pauseOthersOnPlay: true,
|
|
308
|
+
|
|
309
|
+
// Performance
|
|
310
|
+
preload: 'metadata', // 'none', 'metadata', or 'auto'
|
|
311
|
+
deferLoad: false // Delay loading until user plays (good for many players)
|
|
305
312
|
});
|
|
306
313
|
```
|
|
307
314
|
|
|
@@ -643,6 +650,9 @@ npm run build:css # Build CSS only
|
|
|
643
650
|
npm run watch # Watch mode for development
|
|
644
651
|
npm run clean # Clean dist directory
|
|
645
652
|
npm run dev # Start dev server
|
|
653
|
+
npm run test # Run unit tests (Vitest)
|
|
654
|
+
npm run test:e2e # Run end-to-end tests (Playwright)
|
|
655
|
+
npm run test:all # Run all tests
|
|
646
656
|
```
|
|
647
657
|
|
|
648
658
|
### Output Files
|
|
@@ -669,7 +679,7 @@ See [BUILD.md](docs/BUILD.md) for detailed build documentation.
|
|
|
669
679
|
|
|
670
680
|
GNU General Public License v2.0 or later
|
|
671
681
|
|
|
672
|
-
Copyright (C)
|
|
682
|
+
Copyright (C) 2026 Matthias Peltzer
|
|
673
683
|
|
|
674
684
|
This program is free software; you can redistribute it and/or modify
|
|
675
685
|
it under the terms of the GNU General Public License as published by
|
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Universal, Accessible Video Player
|
|
3
|
+
* (c) 2026 Matthias Peltzer
|
|
4
|
+
* Released under GPL-2.0-or-later License
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
CaptionManager
|
|
8
|
+
} from "./vidply.chunk-RX6NLSHH.js";
|
|
9
|
+
import "./vidply.chunk-JZWZJC4C.js";
|
|
10
|
+
import "./vidply.chunk-XAFVSP6D.js";
|
|
11
|
+
|
|
12
|
+
// src/core/AudioDescriptionManager.js
|
|
13
|
+
var AudioDescriptionManager = class {
|
|
14
|
+
constructor(player) {
|
|
15
|
+
this.player = player;
|
|
16
|
+
this.enabled = false;
|
|
17
|
+
this.desiredState = false;
|
|
18
|
+
this.src = player.options.audioDescriptionSrc;
|
|
19
|
+
this.sourceElement = null;
|
|
20
|
+
this.originalSource = null;
|
|
21
|
+
this.captionTracks = [];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Initialize audio description from source elements
|
|
25
|
+
* Called during player initialization
|
|
26
|
+
*/
|
|
27
|
+
initFromSourceElements(sourceElements, trackElements) {
|
|
28
|
+
for (const sourceEl of sourceElements) {
|
|
29
|
+
const descSrc = sourceEl.getAttribute("data-desc-src");
|
|
30
|
+
const origSrc = sourceEl.getAttribute("data-orig-src");
|
|
31
|
+
if (descSrc || origSrc) {
|
|
32
|
+
if (!this.sourceElement) {
|
|
33
|
+
this.sourceElement = sourceEl;
|
|
34
|
+
}
|
|
35
|
+
if (origSrc) {
|
|
36
|
+
if (!this.originalSource) {
|
|
37
|
+
this.originalSource = origSrc;
|
|
38
|
+
}
|
|
39
|
+
if (!this.player.originalSrc) {
|
|
40
|
+
this.player.originalSrc = origSrc;
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
const currentSrcAttr = sourceEl.getAttribute("src");
|
|
44
|
+
if (!this.originalSource && currentSrcAttr) {
|
|
45
|
+
this.originalSource = currentSrcAttr;
|
|
46
|
+
}
|
|
47
|
+
if (!this.player.originalSrc && currentSrcAttr) {
|
|
48
|
+
this.player.originalSrc = currentSrcAttr;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (descSrc && !this.src) {
|
|
52
|
+
this.src = descSrc;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
trackElements.forEach((trackEl) => {
|
|
57
|
+
const trackKind = trackEl.getAttribute("kind");
|
|
58
|
+
const trackDescSrc = trackEl.getAttribute("data-desc-src");
|
|
59
|
+
if ((trackKind === "captions" || trackKind === "subtitles" || trackKind === "chapters" || trackKind === "descriptions") && trackDescSrc) {
|
|
60
|
+
this.captionTracks.push({
|
|
61
|
+
trackElement: trackEl,
|
|
62
|
+
originalSrc: trackEl.getAttribute("src"),
|
|
63
|
+
describedSrc: trackDescSrc,
|
|
64
|
+
originalTrackSrc: trackEl.getAttribute("data-orig-src") || trackEl.getAttribute("src"),
|
|
65
|
+
explicit: true
|
|
66
|
+
});
|
|
67
|
+
this.player.log(`Found explicit described ${trackKind} track: ${trackEl.getAttribute("src")} -> ${trackDescSrc}`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Check if audio description is available
|
|
73
|
+
*/
|
|
74
|
+
isAvailable() {
|
|
75
|
+
const hasSourceElementsWithDesc = this.player.sourceElements.some(
|
|
76
|
+
(el) => el.getAttribute("data-desc-src")
|
|
77
|
+
);
|
|
78
|
+
return !!(this.src || hasSourceElementsWithDesc || this.captionTracks.length > 0);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Enable audio description
|
|
82
|
+
*/
|
|
83
|
+
async enable() {
|
|
84
|
+
const hasSourceElementsWithDesc = this.player.sourceElements.some(
|
|
85
|
+
(el) => el.getAttribute("data-desc-src")
|
|
86
|
+
);
|
|
87
|
+
const hasTracksWithDesc = this.captionTracks.length > 0;
|
|
88
|
+
if (!this.src && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
|
|
89
|
+
console.warn("VidPly: No audio description source, source elements, or tracks provided");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.desiredState = true;
|
|
93
|
+
const currentTime = this.player.state.currentTime;
|
|
94
|
+
const wasPlaying = this.player.state.playing;
|
|
95
|
+
const posterValue = this.player.element.poster || this.player.element.getAttribute("poster") || this.player.options.poster;
|
|
96
|
+
const shouldKeepPoster = currentTime < 0.1 && !wasPlaying;
|
|
97
|
+
const currentCaptionText = this._getCurrentCaptionText();
|
|
98
|
+
if (this.sourceElement) {
|
|
99
|
+
await this._enableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText);
|
|
100
|
+
} else if (this.src) {
|
|
101
|
+
await this._enableWithDirectSrc(currentTime, wasPlaying, posterValue, shouldKeepPoster);
|
|
102
|
+
} else if (hasTracksWithDesc) {
|
|
103
|
+
await this._swapCaptionTracks(true);
|
|
104
|
+
this.enabled = true;
|
|
105
|
+
this.player.emit("audiodescriptionenabled");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Disable audio description
|
|
110
|
+
*/
|
|
111
|
+
async disable() {
|
|
112
|
+
this.desiredState = false;
|
|
113
|
+
const hasTracksWithDesc = this.captionTracks.length > 0;
|
|
114
|
+
if (!this.sourceElement && !this.src && hasTracksWithDesc) {
|
|
115
|
+
await this._swapCaptionTracks(false);
|
|
116
|
+
this.enabled = false;
|
|
117
|
+
this.player.emit("audiodescriptiondisabled");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (!this.player.originalSrc) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const currentTime = this.player.state.currentTime;
|
|
124
|
+
const wasPlaying = this.player.state.playing;
|
|
125
|
+
const posterValue = this.player.element.poster || this.player.element.getAttribute("poster") || this.player.options.poster;
|
|
126
|
+
const shouldKeepPoster = currentTime < 0.1 && !wasPlaying;
|
|
127
|
+
const currentCaptionText = this._getCurrentCaptionText();
|
|
128
|
+
if (this.sourceElement) {
|
|
129
|
+
await this._disableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText);
|
|
130
|
+
} else if (this.src) {
|
|
131
|
+
await this._disableWithDirectSrc(currentTime, wasPlaying, posterValue);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Toggle audio description
|
|
136
|
+
*/
|
|
137
|
+
async toggle() {
|
|
138
|
+
const descriptionTrack = this.player.findTextTrack("descriptions");
|
|
139
|
+
const hasAudioDescriptionSrc = this.isAvailable();
|
|
140
|
+
if (descriptionTrack && !hasAudioDescriptionSrc) {
|
|
141
|
+
if (descriptionTrack.mode === "showing") {
|
|
142
|
+
descriptionTrack.mode = "hidden";
|
|
143
|
+
this.enabled = false;
|
|
144
|
+
this.player.emit("audiodescriptiondisabled");
|
|
145
|
+
} else {
|
|
146
|
+
descriptionTrack.mode = "showing";
|
|
147
|
+
this.enabled = true;
|
|
148
|
+
this.player.emit("audiodescriptionenabled");
|
|
149
|
+
}
|
|
150
|
+
} else if (descriptionTrack && hasAudioDescriptionSrc) {
|
|
151
|
+
if (this.enabled) {
|
|
152
|
+
this.desiredState = false;
|
|
153
|
+
await this.disable();
|
|
154
|
+
} else {
|
|
155
|
+
descriptionTrack.mode = "showing";
|
|
156
|
+
this.desiredState = true;
|
|
157
|
+
await this.enable();
|
|
158
|
+
}
|
|
159
|
+
} else if (hasAudioDescriptionSrc) {
|
|
160
|
+
if (this.enabled) {
|
|
161
|
+
this.desiredState = false;
|
|
162
|
+
await this.disable();
|
|
163
|
+
} else {
|
|
164
|
+
this.desiredState = true;
|
|
165
|
+
await this.enable();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get current caption text for synchronization
|
|
171
|
+
*/
|
|
172
|
+
_getCurrentCaptionText() {
|
|
173
|
+
if (this.player.captionManager && this.player.captionManager.currentTrack && this.player.captionManager.currentCue) {
|
|
174
|
+
return this.player.captionManager.currentCue.text;
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Validate that a track URL exists
|
|
180
|
+
*/
|
|
181
|
+
async _validateTrackExists(url) {
|
|
182
|
+
try {
|
|
183
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
184
|
+
return response.ok;
|
|
185
|
+
} catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Swap caption tracks to described versions
|
|
191
|
+
*/
|
|
192
|
+
async _swapCaptionTracks(toDescribed = true) {
|
|
193
|
+
if (this.captionTracks.length === 0) return [];
|
|
194
|
+
const swappedTracks = [];
|
|
195
|
+
const validationPromises = this.captionTracks.map(async (trackInfo) => {
|
|
196
|
+
if (trackInfo.trackElement && trackInfo.describedSrc) {
|
|
197
|
+
if (trackInfo.explicit === true) {
|
|
198
|
+
try {
|
|
199
|
+
const exists = await this._validateTrackExists(
|
|
200
|
+
toDescribed ? trackInfo.describedSrc : trackInfo.originalSrc
|
|
201
|
+
);
|
|
202
|
+
return { trackInfo, exists };
|
|
203
|
+
} catch {
|
|
204
|
+
return { trackInfo, exists: false };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return { trackInfo, exists: false };
|
|
209
|
+
});
|
|
210
|
+
const validationResults = await Promise.all(validationPromises);
|
|
211
|
+
const tracksToSwap = validationResults.filter((result) => result.exists);
|
|
212
|
+
if (tracksToSwap.length > 0) {
|
|
213
|
+
const trackModes = /* @__PURE__ */ new Map();
|
|
214
|
+
tracksToSwap.forEach(({ trackInfo }) => {
|
|
215
|
+
const textTrack = trackInfo.trackElement.track;
|
|
216
|
+
if (textTrack) {
|
|
217
|
+
trackModes.set(trackInfo, {
|
|
218
|
+
wasShowing: textTrack.mode === "showing",
|
|
219
|
+
wasHidden: textTrack.mode === "hidden"
|
|
220
|
+
});
|
|
221
|
+
} else {
|
|
222
|
+
trackModes.set(trackInfo, { wasShowing: false, wasHidden: false });
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
const tracksToReadd = tracksToSwap.map(({ trackInfo }) => {
|
|
226
|
+
const attributes = {};
|
|
227
|
+
Array.from(trackInfo.trackElement.attributes).forEach((attr) => {
|
|
228
|
+
attributes[attr.name] = attr.value;
|
|
229
|
+
});
|
|
230
|
+
const result = {
|
|
231
|
+
trackInfo,
|
|
232
|
+
oldSrc: trackInfo.trackElement.getAttribute("src"),
|
|
233
|
+
parent: trackInfo.trackElement.parentNode,
|
|
234
|
+
nextSibling: trackInfo.trackElement.nextSibling,
|
|
235
|
+
attributes
|
|
236
|
+
};
|
|
237
|
+
trackInfo.trackElement.remove();
|
|
238
|
+
return result;
|
|
239
|
+
});
|
|
240
|
+
this.player.element.load();
|
|
241
|
+
await new Promise((resolve) => {
|
|
242
|
+
setTimeout(() => {
|
|
243
|
+
tracksToReadd.forEach(({ trackInfo, parent, nextSibling, attributes }) => {
|
|
244
|
+
swappedTracks.push(trackInfo);
|
|
245
|
+
const newTrackElement = document.createElement("track");
|
|
246
|
+
const newSrc = toDescribed ? trackInfo.describedSrc : trackInfo.originalSrc;
|
|
247
|
+
newTrackElement.setAttribute("src", newSrc);
|
|
248
|
+
Object.keys(attributes).forEach((attrName) => {
|
|
249
|
+
if (attrName !== "src" && attrName !== "data-desc-src") {
|
|
250
|
+
newTrackElement.setAttribute(attrName, attributes[attrName]);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
const targetParent = parent || this.player.element;
|
|
254
|
+
if (nextSibling && nextSibling.parentNode) {
|
|
255
|
+
targetParent.insertBefore(newTrackElement, nextSibling);
|
|
256
|
+
} else {
|
|
257
|
+
targetParent.appendChild(newTrackElement);
|
|
258
|
+
}
|
|
259
|
+
trackInfo.trackElement = newTrackElement;
|
|
260
|
+
});
|
|
261
|
+
this.player.invalidateTrackCache();
|
|
262
|
+
const setupNewTracks = () => {
|
|
263
|
+
this.player.setManagedTimeout(() => {
|
|
264
|
+
swappedTracks.forEach((trackInfo) => {
|
|
265
|
+
const newTextTrack = trackInfo.trackElement.track;
|
|
266
|
+
if (newTextTrack) {
|
|
267
|
+
const modeInfo = trackModes.get(trackInfo) || { wasShowing: false, wasHidden: false };
|
|
268
|
+
newTextTrack.mode = "hidden";
|
|
269
|
+
const restoreMode = () => {
|
|
270
|
+
if (modeInfo.wasShowing || modeInfo.wasHidden) {
|
|
271
|
+
newTextTrack.mode = "hidden";
|
|
272
|
+
} else {
|
|
273
|
+
newTextTrack.mode = "disabled";
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
if (newTextTrack.readyState >= 2) {
|
|
277
|
+
restoreMode();
|
|
278
|
+
} else {
|
|
279
|
+
newTextTrack.addEventListener("load", restoreMode, { once: true });
|
|
280
|
+
newTextTrack.addEventListener("error", restoreMode, { once: true });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}, 300);
|
|
285
|
+
};
|
|
286
|
+
if (this.player.element.readyState >= 1) {
|
|
287
|
+
setTimeout(setupNewTracks, 200);
|
|
288
|
+
} else {
|
|
289
|
+
this.player.element.addEventListener("loadedmetadata", setupNewTracks, { once: true });
|
|
290
|
+
setTimeout(setupNewTracks, 2e3);
|
|
291
|
+
}
|
|
292
|
+
resolve();
|
|
293
|
+
}, 100);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return swappedTracks;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Update source elements to described versions
|
|
300
|
+
*/
|
|
301
|
+
_updateSourceElements(toDescribed = true) {
|
|
302
|
+
const sourceElements = this.player.sourceElements;
|
|
303
|
+
const sourcesToUpdate = [];
|
|
304
|
+
sourceElements.forEach((sourceEl) => {
|
|
305
|
+
const descSrcAttr = sourceEl.getAttribute("data-desc-src");
|
|
306
|
+
const currentSrc = sourceEl.getAttribute("src");
|
|
307
|
+
if (descSrcAttr) {
|
|
308
|
+
const type = sourceEl.getAttribute("type");
|
|
309
|
+
let origSrc = sourceEl.getAttribute("data-orig-src") || currentSrc;
|
|
310
|
+
sourcesToUpdate.push({
|
|
311
|
+
src: toDescribed ? descSrcAttr : origSrc,
|
|
312
|
+
type,
|
|
313
|
+
origSrc,
|
|
314
|
+
descSrc: descSrcAttr
|
|
315
|
+
});
|
|
316
|
+
} else {
|
|
317
|
+
sourcesToUpdate.push({
|
|
318
|
+
src: sourceEl.getAttribute("src"),
|
|
319
|
+
type: sourceEl.getAttribute("type"),
|
|
320
|
+
origSrc: null,
|
|
321
|
+
descSrc: null
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
if (this.player.element.hasAttribute("src")) {
|
|
326
|
+
this.player.element.removeAttribute("src");
|
|
327
|
+
}
|
|
328
|
+
sourceElements.forEach((sourceEl) => sourceEl.remove());
|
|
329
|
+
sourcesToUpdate.forEach((sourceInfo) => {
|
|
330
|
+
const newSource = document.createElement("source");
|
|
331
|
+
newSource.setAttribute("src", sourceInfo.src);
|
|
332
|
+
if (sourceInfo.type) {
|
|
333
|
+
newSource.setAttribute("type", sourceInfo.type);
|
|
334
|
+
}
|
|
335
|
+
if (sourceInfo.origSrc) {
|
|
336
|
+
newSource.setAttribute("data-orig-src", sourceInfo.origSrc);
|
|
337
|
+
}
|
|
338
|
+
if (sourceInfo.descSrc) {
|
|
339
|
+
newSource.setAttribute("data-desc-src", sourceInfo.descSrc);
|
|
340
|
+
}
|
|
341
|
+
const firstTrack = this.player.element.querySelector("track");
|
|
342
|
+
if (firstTrack) {
|
|
343
|
+
this.player.element.insertBefore(newSource, firstTrack);
|
|
344
|
+
} else {
|
|
345
|
+
this.player.element.appendChild(newSource);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
this.player._sourceElementsDirty = true;
|
|
349
|
+
this.player._sourceElementsCache = null;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Wait for media to be ready
|
|
353
|
+
*/
|
|
354
|
+
async _waitForMediaReady(needSeek = false) {
|
|
355
|
+
await new Promise((resolve) => {
|
|
356
|
+
if (this.player.element.readyState >= 1) {
|
|
357
|
+
resolve();
|
|
358
|
+
} else {
|
|
359
|
+
const onLoad = () => {
|
|
360
|
+
this.player.element.removeEventListener("loadedmetadata", onLoad);
|
|
361
|
+
resolve();
|
|
362
|
+
};
|
|
363
|
+
this.player.element.addEventListener("loadedmetadata", onLoad);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
367
|
+
if (needSeek) {
|
|
368
|
+
await new Promise((resolve) => {
|
|
369
|
+
if (this.player.element.readyState >= 3) {
|
|
370
|
+
resolve();
|
|
371
|
+
} else {
|
|
372
|
+
const onCanPlay = () => {
|
|
373
|
+
this.player.element.removeEventListener("canplay", onCanPlay);
|
|
374
|
+
this.player.element.removeEventListener("canplaythrough", onCanPlay);
|
|
375
|
+
resolve();
|
|
376
|
+
};
|
|
377
|
+
this.player.element.addEventListener("canplay", onCanPlay, { once: true });
|
|
378
|
+
this.player.element.addEventListener("canplaythrough", onCanPlay, { once: true });
|
|
379
|
+
setTimeout(() => {
|
|
380
|
+
this.player.element.removeEventListener("canplay", onCanPlay);
|
|
381
|
+
this.player.element.removeEventListener("canplaythrough", onCanPlay);
|
|
382
|
+
resolve();
|
|
383
|
+
}, 3e3);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Restore playback state after source change
|
|
390
|
+
*/
|
|
391
|
+
async _restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText) {
|
|
392
|
+
let syncTime = currentTime;
|
|
393
|
+
if (currentCaptionText && this.player.captionManager && this.player.captionManager.tracks.length > 0) {
|
|
394
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
395
|
+
const matchingTime = this.player.findMatchingCaptionTime(
|
|
396
|
+
currentCaptionText,
|
|
397
|
+
this.player.captionManager.tracks
|
|
398
|
+
);
|
|
399
|
+
if (matchingTime !== null) {
|
|
400
|
+
syncTime = matchingTime;
|
|
401
|
+
if (this.player.options.debug) {
|
|
402
|
+
this.player.log(`[VidPly] Syncing via caption: ${currentTime}s -> ${syncTime}s`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (syncTime > 0) {
|
|
407
|
+
this.player.seek(syncTime);
|
|
408
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
409
|
+
}
|
|
410
|
+
if (wasPlaying) {
|
|
411
|
+
await this.player.play();
|
|
412
|
+
this.player.setManagedTimeout(() => {
|
|
413
|
+
this.player.hidePosterOverlay();
|
|
414
|
+
}, 100);
|
|
415
|
+
} else {
|
|
416
|
+
this.player.pause();
|
|
417
|
+
if (!shouldKeepPoster) {
|
|
418
|
+
this.player.hidePosterOverlay();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Enable with source element method
|
|
424
|
+
*/
|
|
425
|
+
async _enableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText) {
|
|
426
|
+
await this._swapCaptionTracks(true);
|
|
427
|
+
this._updateSourceElements(true);
|
|
428
|
+
if (posterValue && this.player.element.tagName === "VIDEO") {
|
|
429
|
+
this.player.element.poster = posterValue;
|
|
430
|
+
}
|
|
431
|
+
this.player.element.load();
|
|
432
|
+
await this._waitForMediaReady(currentTime > 0 || wasPlaying);
|
|
433
|
+
await this._restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText);
|
|
434
|
+
if (!this.desiredState) return;
|
|
435
|
+
this.enabled = true;
|
|
436
|
+
this.player.state.audioDescriptionEnabled = true;
|
|
437
|
+
this.player.emit("audiodescriptionenabled");
|
|
438
|
+
this._reloadTranscript();
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Enable with direct src method
|
|
442
|
+
*/
|
|
443
|
+
async _enableWithDirectSrc(currentTime, wasPlaying, posterValue, shouldKeepPoster) {
|
|
444
|
+
await this._swapCaptionTracks(true);
|
|
445
|
+
if (posterValue && this.player.element.tagName === "VIDEO") {
|
|
446
|
+
this.player.element.poster = posterValue;
|
|
447
|
+
}
|
|
448
|
+
this.player.element.src = this.src;
|
|
449
|
+
await this._waitForMediaReady(currentTime > 0 || wasPlaying);
|
|
450
|
+
if (currentTime > 0) {
|
|
451
|
+
this.player.seek(currentTime);
|
|
452
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
453
|
+
}
|
|
454
|
+
if (wasPlaying) {
|
|
455
|
+
await this.player.play();
|
|
456
|
+
} else {
|
|
457
|
+
this.player.pause();
|
|
458
|
+
if (!shouldKeepPoster) {
|
|
459
|
+
this.player.hidePosterOverlay();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (!this.desiredState) return;
|
|
463
|
+
this.enabled = true;
|
|
464
|
+
this.player.state.audioDescriptionEnabled = true;
|
|
465
|
+
this.player.emit("audiodescriptionenabled");
|
|
466
|
+
this._reloadTranscript();
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Disable with source element method
|
|
470
|
+
*/
|
|
471
|
+
async _disableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText) {
|
|
472
|
+
await this._swapCaptionTracks(false);
|
|
473
|
+
this._updateSourceElements(false);
|
|
474
|
+
if (posterValue && this.player.element.tagName === "VIDEO") {
|
|
475
|
+
this.player.element.poster = posterValue;
|
|
476
|
+
}
|
|
477
|
+
this.player.element.load();
|
|
478
|
+
this.player.invalidateTrackCache();
|
|
479
|
+
await this._waitForMediaReady(currentTime > 0 || wasPlaying);
|
|
480
|
+
await this._restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText);
|
|
481
|
+
if (this.player.captionManager) {
|
|
482
|
+
this.player.captionManager.destroy();
|
|
483
|
+
this.player.captionManager = new CaptionManager(this.player);
|
|
484
|
+
}
|
|
485
|
+
if (this.desiredState) return;
|
|
486
|
+
this.enabled = false;
|
|
487
|
+
this.player.state.audioDescriptionEnabled = false;
|
|
488
|
+
this.player.emit("audiodescriptiondisabled");
|
|
489
|
+
this._reloadTranscript();
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Disable with direct src method
|
|
493
|
+
*/
|
|
494
|
+
async _disableWithDirectSrc(currentTime, wasPlaying, posterValue) {
|
|
495
|
+
await this._swapCaptionTracks(false);
|
|
496
|
+
if (posterValue && this.player.element.tagName === "VIDEO") {
|
|
497
|
+
this.player.element.poster = posterValue;
|
|
498
|
+
}
|
|
499
|
+
const originalSrcToUse = this.originalSource || this.player.originalSrc;
|
|
500
|
+
this.player.element.src = originalSrcToUse;
|
|
501
|
+
this.player.element.load();
|
|
502
|
+
await this._waitForMediaReady(currentTime > 0 || wasPlaying);
|
|
503
|
+
if (currentTime > 0) {
|
|
504
|
+
this.player.seek(currentTime);
|
|
505
|
+
}
|
|
506
|
+
if (wasPlaying) {
|
|
507
|
+
await this.player.play();
|
|
508
|
+
}
|
|
509
|
+
if (this.desiredState) return;
|
|
510
|
+
this.enabled = false;
|
|
511
|
+
this.player.state.audioDescriptionEnabled = false;
|
|
512
|
+
this.player.emit("audiodescriptiondisabled");
|
|
513
|
+
this._reloadTranscript();
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Reload transcript after audio description state change
|
|
517
|
+
*/
|
|
518
|
+
_reloadTranscript() {
|
|
519
|
+
if (this.player.transcriptManager && this.player.transcriptManager.isVisible) {
|
|
520
|
+
this.player.setManagedTimeout(() => {
|
|
521
|
+
if (this.player.transcriptManager && this.player.transcriptManager.loadTranscriptData) {
|
|
522
|
+
this.player.transcriptManager.loadTranscriptData();
|
|
523
|
+
}
|
|
524
|
+
}, 800);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Update sources (called when playlist changes)
|
|
529
|
+
*/
|
|
530
|
+
updateSources(audioDescriptionSrc) {
|
|
531
|
+
this.src = audioDescriptionSrc || null;
|
|
532
|
+
this.enabled = false;
|
|
533
|
+
this.desiredState = false;
|
|
534
|
+
this.sourceElement = null;
|
|
535
|
+
this.originalSource = null;
|
|
536
|
+
this.captionTracks = [];
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Reinitialize from current player elements (called after playlist loads new tracks)
|
|
540
|
+
*/
|
|
541
|
+
reinitialize() {
|
|
542
|
+
this.player.invalidateTrackCache();
|
|
543
|
+
this.initFromSourceElements(this.player.sourceElements, this.player.trackElements);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Cleanup
|
|
547
|
+
*/
|
|
548
|
+
destroy() {
|
|
549
|
+
this.enabled = false;
|
|
550
|
+
this.desiredState = false;
|
|
551
|
+
this.captionTracks = [];
|
|
552
|
+
this.sourceElement = null;
|
|
553
|
+
this.originalSource = null;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
export {
|
|
557
|
+
AudioDescriptionManager
|
|
558
|
+
};
|
|
559
|
+
//# sourceMappingURL=vidply.AudioDescriptionManager-XG32DISZ.js.map
|