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.
Files changed (83) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +14 -4
  3. package/dist/dev/vidply.AudioDescriptionManager-XG32DISZ.js +559 -0
  4. package/dist/dev/vidply.AudioDescriptionManager-XG32DISZ.js.map +7 -0
  5. package/dist/dev/{vidply.HLSRenderer-ZLTE6K3O.js → vidply.HLSRenderer-QXQPR4KT.js} +64 -1
  6. package/dist/dev/vidply.HLSRenderer-QXQPR4KT.js.map +7 -0
  7. package/dist/dev/vidply.SignLanguageManager-YQ2ETTUO.js +1209 -0
  8. package/dist/dev/vidply.SignLanguageManager-YQ2ETTUO.js.map +7 -0
  9. package/dist/dev/{vidply.TranscriptManager-UWM2WNAV.js → vidply.TranscriptManager-VCRQXB5W.js} +57 -21
  10. package/dist/dev/vidply.TranscriptManager-VCRQXB5W.js.map +7 -0
  11. package/dist/dev/vidply.chunk-7TDF7KK7.js +79 -0
  12. package/dist/dev/vidply.chunk-7TDF7KK7.js.map +7 -0
  13. package/dist/dev/vidply.chunk-JZWZJC4C.js +203 -0
  14. package/dist/dev/vidply.chunk-JZWZJC4C.js.map +7 -0
  15. package/dist/dev/{vidply.chunk-LD3OELXW.js → vidply.chunk-MQRVLOTX.js} +7 -769
  16. package/dist/dev/vidply.chunk-MQRVLOTX.js.map +7 -0
  17. package/dist/dev/vidply.chunk-RX6NLSHH.js +410 -0
  18. package/dist/dev/vidply.chunk-RX6NLSHH.js.map +7 -0
  19. package/dist/dev/vidply.chunk-XAFVSP6D.js +577 -0
  20. package/dist/dev/vidply.chunk-XAFVSP6D.js.map +7 -0
  21. package/dist/dev/{vidply.de-IXUNTP3C.js → vidply.de-BV6HZDQX.js} +6 -1
  22. package/dist/dev/{vidply.de-IXUNTP3C.js.map → vidply.de-BV6HZDQX.js.map} +2 -2
  23. package/dist/dev/{vidply.es-PSWHNCXC.js → vidply.es-CRD7N5YZ.js} +6 -1
  24. package/dist/dev/{vidply.es-PSWHNCXC.js.map → vidply.es-CRD7N5YZ.js.map} +2 -2
  25. package/dist/dev/vidply.esm.js +789 -2212
  26. package/dist/dev/vidply.esm.js.map +4 -4
  27. package/dist/dev/{vidply.fr-3ZGYEON2.js → vidply.fr-WIFNQTHI.js} +6 -1
  28. package/dist/dev/{vidply.fr-3ZGYEON2.js.map → vidply.fr-WIFNQTHI.js.map} +2 -2
  29. package/dist/dev/{vidply.ja-HS2NMBQZ.js → vidply.ja-FPCJXERD.js} +6 -1
  30. package/dist/dev/{vidply.ja-HS2NMBQZ.js.map → vidply.ja-FPCJXERD.js.map} +2 -2
  31. package/dist/legacy/vidply.js +8114 -7185
  32. package/dist/legacy/vidply.js.map +4 -4
  33. package/dist/legacy/vidply.min.js +1 -1
  34. package/dist/legacy/vidply.min.meta.json +70 -64
  35. package/dist/prod/vidply.AudioDescriptionManager-S2B3YPBT.min.js +6 -0
  36. package/dist/prod/vidply.HLSRenderer-TBGNB3HF.min.js +6 -0
  37. package/dist/prod/{vidply.HTML5Renderer-4DX43LUF.min.js → vidply.HTML5Renderer-HTHCSDW2.min.js} +1 -1
  38. package/dist/prod/vidply.SignLanguageManager-6M4S4KNR.min.js +6 -0
  39. package/dist/prod/vidply.SoundCloudRenderer-S7DCYVO3.min.js +6 -0
  40. package/dist/prod/vidply.TranscriptManager-457ACZUN.min.js +6 -0
  41. package/dist/prod/vidply.VimeoRenderer-PVOJ6FAM.min.js +6 -0
  42. package/dist/prod/vidply.YouTubeRenderer-CTEREOSQ.min.js +6 -0
  43. package/dist/prod/vidply.chunk-7OK6TG2Y.min.js +6 -0
  44. package/dist/prod/vidply.chunk-AE2Z46L5.min.js +6 -0
  45. package/dist/prod/vidply.chunk-AK4IZ5DH.min.js +6 -0
  46. package/dist/prod/vidply.chunk-G2P52V5M.min.js +6 -0
  47. package/dist/prod/vidply.chunk-NFACYQG2.min.js +6 -0
  48. package/dist/prod/vidply.chunk-YLKHFL57.min.js +6 -0
  49. package/dist/prod/{vidply.de-MFGEETCC.min.js → vidply.de-235U7FBB.min.js} +1 -1
  50. package/dist/prod/{vidply.es-HZGMS5GG.min.js → vidply.es-KAKOGBQZ.min.js} +1 -1
  51. package/dist/prod/vidply.esm.min.js +1 -16
  52. package/dist/prod/{vidply.fr-HW3KSCFM.min.js → vidply.fr-5CSEV3MB.min.js} +1 -1
  53. package/dist/prod/{vidply.ja-GXSYRBQY.min.js → vidply.ja-7EI7KZ6G.min.js} +1 -1
  54. package/dist/vidply.css +1057 -1
  55. package/dist/vidply.esm.min.meta.json +273 -139
  56. package/dist/vidply.min.css +1 -1
  57. package/package.json +3 -2
  58. package/src/controls/CaptionManager.js +32 -0
  59. package/src/controls/ControlBar.js +283 -16
  60. package/src/controls/TranscriptManager.js +69 -18
  61. package/src/core/AudioDescriptionManager.js +23 -6
  62. package/src/core/Player.js +569 -47
  63. package/src/features/PlaylistManager.js +1705 -1705
  64. package/src/i18n/languages/de.js +5 -0
  65. package/src/i18n/languages/en.js +5 -0
  66. package/src/i18n/languages/es.js +5 -0
  67. package/src/i18n/languages/fr.js +5 -0
  68. package/src/i18n/languages/ja.js +5 -0
  69. package/src/icons/Icons.js +2 -2
  70. package/src/index.js +101 -2
  71. package/src/renderers/HLSRenderer.js +81 -0
  72. package/src/styles/vidply.css +1057 -1
  73. package/src/utils/StorageManager.js +76 -0
  74. package/dist/dev/vidply.HLSRenderer-ZLTE6K3O.js.map +0 -7
  75. package/dist/dev/vidply.TranscriptManager-UWM2WNAV.js.map +0 -7
  76. package/dist/dev/vidply.chunk-LD3OELXW.js.map +0 -7
  77. package/dist/prod/vidply.HLSRenderer-YLZMDGS2.min.js +0 -6
  78. package/dist/prod/vidply.SoundCloudRenderer-D2FNOEG6.min.js +0 -6
  79. package/dist/prod/vidply.TranscriptManager-AYJL4AG3.min.js +0 -6
  80. package/dist/prod/vidply.VimeoRenderer-QELFZVDU.min.js +0 -6
  81. package/dist/prod/vidply.YouTubeRenderer-ZL6YUHTF.min.js +0 -6
  82. package/dist/prod/vidply.chunk-I7BQYXMN.min.js +0 -6
  83. 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) 2025 Matthias Peltzer
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
  ![License](https://img.shields.io/badge/license-GPL--2.0--or--later-blue.svg)
8
8
  ![ES6](https://img.shields.io/badge/ES6-Module-yellow.svg)
9
9
  ![WCAG](https://img.shields.io/badge/WCAG-2.1%20AA-green.svg)
10
- ![Version](https://img.shields.io/badge/version-1.0.18-brightgreen.svg)
10
+ ![Version](https://img.shields.io/badge/version-1.0.40-brightgreen.svg)
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) 2025 Matthias Peltzer
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