vidply 1.0.28 → 1.0.29

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 (61) hide show
  1. package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js → vidply.TranscriptManager-T677KF4N.js} +4 -5
  2. package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js.map → vidply.TranscriptManager-T677KF4N.js.map} +2 -2
  3. package/dist/dev/{vidply.chunk-SRM7VNHG.js → vidply.chunk-GS2JX5RQ.js} +136 -95
  4. package/dist/dev/vidply.chunk-GS2JX5RQ.js.map +7 -0
  5. package/dist/dev/vidply.esm.js +1674 -310
  6. package/dist/dev/vidply.esm.js.map +4 -4
  7. package/dist/legacy/vidply.js +1776 -348
  8. package/dist/legacy/vidply.js.map +4 -4
  9. package/dist/legacy/vidply.min.js +1 -1
  10. package/dist/legacy/vidply.min.meta.json +92 -24
  11. package/dist/prod/vidply.TranscriptManager-WFZSW6NR.min.js +6 -0
  12. package/dist/prod/vidply.chunk-LGTJRPUL.min.js +6 -0
  13. package/dist/prod/vidply.esm.min.js +8 -8
  14. package/dist/vidply.esm.min.meta.json +92 -24
  15. package/package.json +1 -1
  16. package/src/controls/ControlBar.js +3 -7
  17. package/src/controls/TranscriptManager.js +7 -7
  18. package/src/core/AudioDescriptionManager.js +701 -0
  19. package/src/core/Player.js +4776 -4921
  20. package/src/core/SignLanguageManager.js +1134 -0
  21. package/src/utils/DOMUtils.js +153 -114
  22. package/src/utils/MenuFactory.js +374 -0
  23. package/dist/dev/vidply.TranscriptManager-GZKY44ON.js +0 -1744
  24. package/dist/dev/vidply.TranscriptManager-GZKY44ON.js.map +0 -7
  25. package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js +0 -1744
  26. package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js.map +0 -7
  27. package/dist/dev/vidply.chunk-5663PYKK.js +0 -1631
  28. package/dist/dev/vidply.chunk-5663PYKK.js.map +0 -7
  29. package/dist/dev/vidply.chunk-SRM7VNHG.js.map +0 -7
  30. package/dist/dev/vidply.chunk-UH5MTGKF.js +0 -1630
  31. package/dist/dev/vidply.chunk-UH5MTGKF.js.map +0 -7
  32. package/dist/dev/vidply.de-RXAJM5QE.js +0 -181
  33. package/dist/dev/vidply.de-RXAJM5QE.js.map +0 -7
  34. package/dist/dev/vidply.de-THBIMP4S.js +0 -180
  35. package/dist/dev/vidply.de-THBIMP4S.js.map +0 -7
  36. package/dist/dev/vidply.es-6VWDNNNL.js +0 -180
  37. package/dist/dev/vidply.es-6VWDNNNL.js.map +0 -7
  38. package/dist/dev/vidply.es-SADVLJTQ.js +0 -181
  39. package/dist/dev/vidply.es-SADVLJTQ.js.map +0 -7
  40. package/dist/dev/vidply.fr-V3VAYBBT.js +0 -181
  41. package/dist/dev/vidply.fr-V3VAYBBT.js.map +0 -7
  42. package/dist/dev/vidply.fr-WHTWCHWT.js +0 -180
  43. package/dist/dev/vidply.fr-WHTWCHWT.js.map +0 -7
  44. package/dist/dev/vidply.ja-BFQNPOFI.js +0 -180
  45. package/dist/dev/vidply.ja-BFQNPOFI.js.map +0 -7
  46. package/dist/dev/vidply.ja-KL2TLZGJ.js +0 -181
  47. package/dist/dev/vidply.ja-KL2TLZGJ.js.map +0 -7
  48. package/dist/prod/vidply.TranscriptManager-DZ2WZU3K.min.js +0 -6
  49. package/dist/prod/vidply.TranscriptManager-E5QHGFIR.min.js +0 -6
  50. package/dist/prod/vidply.TranscriptManager-UZ6DUFB6.min.js +0 -6
  51. package/dist/prod/vidply.chunk-5DWTMWEO.min.js +0 -6
  52. package/dist/prod/vidply.chunk-IBNYTGGM.min.js +0 -6
  53. package/dist/prod/vidply.chunk-MBUR3U5L.min.js +0 -6
  54. package/dist/prod/vidply.de-HGJBCLLE.min.js +0 -6
  55. package/dist/prod/vidply.de-SWFW4HYT.min.js +0 -6
  56. package/dist/prod/vidply.es-7BJ2DJAY.min.js +0 -6
  57. package/dist/prod/vidply.es-CZEBXCZN.min.js +0 -6
  58. package/dist/prod/vidply.fr-DPVR5DFY.min.js +0 -6
  59. package/dist/prod/vidply.fr-HFOL7MWA.min.js +0 -6
  60. package/dist/prod/vidply.ja-PEBVWKVH.min.js +0 -6
  61. package/dist/prod/vidply.ja-QTVU5C25.min.js +0 -6
@@ -0,0 +1,701 @@
1
+ /**
2
+ * Audio Description Manager
3
+ * Handles audio-described video source switching and caption track swapping
4
+ */
5
+
6
+ import { CaptionManager } from '../controls/CaptionManager.js';
7
+
8
+ export class AudioDescriptionManager {
9
+ constructor(player) {
10
+ this.player = player;
11
+
12
+ // State
13
+ this.enabled = false;
14
+ this.desiredState = false;
15
+
16
+ // Sources
17
+ this.src = player.options.audioDescriptionSrc;
18
+ this.sourceElement = null;
19
+ this.originalSource = null;
20
+ this.captionTracks = [];
21
+ }
22
+
23
+ /**
24
+ * Initialize audio description from source elements
25
+ * Called during player initialization
26
+ */
27
+ initFromSourceElements(sourceElements, trackElements) {
28
+ // Check for source elements with audio description attributes
29
+ for (const sourceEl of sourceElements) {
30
+ const descSrc = sourceEl.getAttribute('data-desc-src');
31
+ const origSrc = sourceEl.getAttribute('data-orig-src');
32
+
33
+ if (descSrc || origSrc) {
34
+ if (!this.sourceElement) {
35
+ this.sourceElement = sourceEl;
36
+ }
37
+
38
+ if (origSrc) {
39
+ if (!this.originalSource) {
40
+ this.originalSource = origSrc;
41
+ }
42
+ if (!this.player.originalSrc) {
43
+ this.player.originalSrc = origSrc;
44
+ }
45
+ } else {
46
+ const currentSrcAttr = sourceEl.getAttribute('src');
47
+ if (!this.originalSource && currentSrcAttr) {
48
+ this.originalSource = currentSrcAttr;
49
+ }
50
+ if (!this.player.originalSrc && currentSrcAttr) {
51
+ this.player.originalSrc = currentSrcAttr;
52
+ }
53
+ }
54
+
55
+ if (descSrc && !this.src) {
56
+ this.src = descSrc;
57
+ }
58
+ }
59
+ }
60
+
61
+ // Check for text tracks with audio description versions
62
+ trackElements.forEach(trackEl => {
63
+ const trackKind = trackEl.getAttribute('kind');
64
+ const trackDescSrc = trackEl.getAttribute('data-desc-src');
65
+
66
+ if ((trackKind === 'captions' || trackKind === 'subtitles' ||
67
+ trackKind === 'chapters' || trackKind === 'descriptions') && trackDescSrc) {
68
+ this.captionTracks.push({
69
+ trackElement: trackEl,
70
+ originalSrc: trackEl.getAttribute('src'),
71
+ describedSrc: trackDescSrc,
72
+ originalTrackSrc: trackEl.getAttribute('data-orig-src') || trackEl.getAttribute('src'),
73
+ explicit: true
74
+ });
75
+ this.player.log(`Found explicit described ${trackKind} track: ${trackEl.getAttribute('src')} -> ${trackDescSrc}`);
76
+ }
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Check if audio description is available
82
+ */
83
+ isAvailable() {
84
+ const hasSourceElementsWithDesc = this.player.sourceElements.some(
85
+ el => el.getAttribute('data-desc-src')
86
+ );
87
+ return !!(this.src || hasSourceElementsWithDesc || this.captionTracks.length > 0);
88
+ }
89
+
90
+ /**
91
+ * Enable audio description
92
+ */
93
+ async enable() {
94
+ const hasSourceElementsWithDesc = this.player.sourceElements.some(
95
+ el => el.getAttribute('data-desc-src')
96
+ );
97
+ const hasTracksWithDesc = this.captionTracks.length > 0;
98
+
99
+ if (!this.src && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
100
+ console.warn('VidPly: No audio description source, source elements, or tracks provided');
101
+ return;
102
+ }
103
+
104
+ this.desiredState = true;
105
+
106
+ // Store current state for restoration
107
+ const currentTime = this.player.state.currentTime;
108
+ const wasPlaying = this.player.state.playing;
109
+ const posterValue = this.player.element.poster ||
110
+ this.player.element.getAttribute('poster') ||
111
+ this.player.options.poster;
112
+ const shouldKeepPoster = currentTime < 0.1 && !wasPlaying;
113
+
114
+ // Get current caption text for synchronization
115
+ const currentCaptionText = this._getCurrentCaptionText();
116
+
117
+ // Switch to audio-described version
118
+ if (this.sourceElement) {
119
+ await this._enableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText);
120
+ } else {
121
+ await this._enableWithDirectSrc(currentTime, wasPlaying, posterValue, shouldKeepPoster);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Disable audio description
127
+ */
128
+ async disable() {
129
+ if (!this.player.originalSrc) {
130
+ return;
131
+ }
132
+
133
+ this.desiredState = false;
134
+
135
+ // Store current state
136
+ const currentTime = this.player.state.currentTime;
137
+ const wasPlaying = this.player.state.playing;
138
+ const posterValue = this.player.element.poster ||
139
+ this.player.element.getAttribute('poster') ||
140
+ this.player.options.poster;
141
+ const shouldKeepPoster = currentTime < 0.1 && !wasPlaying;
142
+
143
+ // Get current caption for sync
144
+ const currentCaptionText = this._getCurrentCaptionText();
145
+
146
+ if (this.sourceElement) {
147
+ await this._disableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText);
148
+ } else {
149
+ await this._disableWithDirectSrc(currentTime, wasPlaying, posterValue);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Toggle audio description
155
+ */
156
+ async toggle() {
157
+ const descriptionTrack = this.player.findTextTrack('descriptions');
158
+ const hasAudioDescriptionSrc = this.isAvailable();
159
+
160
+ if (descriptionTrack && !hasAudioDescriptionSrc) {
161
+ // Toggle description track playback
162
+ if (descriptionTrack.mode === 'showing') {
163
+ descriptionTrack.mode = 'hidden';
164
+ this.enabled = false;
165
+ this.player.emit('audiodescriptiondisabled');
166
+ } else {
167
+ descriptionTrack.mode = 'showing';
168
+ this.enabled = true;
169
+ this.player.emit('audiodescriptionenabled');
170
+ }
171
+ } else if (descriptionTrack && hasAudioDescriptionSrc) {
172
+ // Toggle both
173
+ if (this.enabled) {
174
+ this.desiredState = false;
175
+ await this.disable();
176
+ } else {
177
+ descriptionTrack.mode = 'showing';
178
+ this.desiredState = true;
179
+ await this.enable();
180
+ }
181
+ } else if (hasAudioDescriptionSrc) {
182
+ // Toggle source
183
+ if (this.enabled) {
184
+ this.desiredState = false;
185
+ await this.disable();
186
+ } else {
187
+ this.desiredState = true;
188
+ await this.enable();
189
+ }
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Get current caption text for synchronization
195
+ */
196
+ _getCurrentCaptionText() {
197
+ if (this.player.captionManager &&
198
+ this.player.captionManager.currentTrack &&
199
+ this.player.captionManager.currentCue) {
200
+ return this.player.captionManager.currentCue.text;
201
+ }
202
+ return null;
203
+ }
204
+
205
+ /**
206
+ * Validate that a track URL exists
207
+ */
208
+ async _validateTrackExists(url) {
209
+ try {
210
+ const response = await fetch(url, { method: 'HEAD' });
211
+ return response.ok;
212
+ } catch {
213
+ return false;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Swap caption tracks to described versions
219
+ */
220
+ async _swapCaptionTracks(toDescribed = true) {
221
+ if (this.captionTracks.length === 0) return [];
222
+
223
+ const swappedTracks = [];
224
+
225
+ const validationPromises = this.captionTracks.map(async (trackInfo) => {
226
+ if (trackInfo.trackElement && trackInfo.describedSrc) {
227
+ if (trackInfo.explicit === true) {
228
+ try {
229
+ const exists = await this._validateTrackExists(
230
+ toDescribed ? trackInfo.describedSrc : trackInfo.originalSrc
231
+ );
232
+ return { trackInfo, exists };
233
+ } catch {
234
+ return { trackInfo, exists: false };
235
+ }
236
+ }
237
+ }
238
+ return { trackInfo, exists: false };
239
+ });
240
+
241
+ const validationResults = await Promise.all(validationPromises);
242
+ const tracksToSwap = validationResults.filter(result => result.exists);
243
+
244
+ if (tracksToSwap.length > 0) {
245
+ // Store track modes before removing
246
+ const trackModes = new Map();
247
+ tracksToSwap.forEach(({ trackInfo }) => {
248
+ const textTrack = trackInfo.trackElement.track;
249
+ if (textTrack) {
250
+ trackModes.set(trackInfo, {
251
+ wasShowing: textTrack.mode === 'showing',
252
+ wasHidden: textTrack.mode === 'hidden'
253
+ });
254
+ } else {
255
+ trackModes.set(trackInfo, { wasShowing: false, wasHidden: false });
256
+ }
257
+ });
258
+
259
+ // Store track info and remove
260
+ const tracksToReadd = tracksToSwap.map(({ trackInfo }) => {
261
+ const attributes = {};
262
+ Array.from(trackInfo.trackElement.attributes).forEach(attr => {
263
+ attributes[attr.name] = attr.value;
264
+ });
265
+
266
+ const result = {
267
+ trackInfo,
268
+ oldSrc: trackInfo.trackElement.getAttribute('src'),
269
+ parent: trackInfo.trackElement.parentNode,
270
+ nextSibling: trackInfo.trackElement.nextSibling,
271
+ attributes
272
+ };
273
+
274
+ trackInfo.trackElement.remove();
275
+ return result;
276
+ });
277
+
278
+ // Force browser to process removal
279
+ this.player.element.load();
280
+
281
+ // Re-add tracks with new src
282
+ await new Promise(resolve => {
283
+ setTimeout(() => {
284
+ tracksToReadd.forEach(({ trackInfo, parent, nextSibling, attributes }) => {
285
+ swappedTracks.push(trackInfo);
286
+
287
+ const newTrackElement = document.createElement('track');
288
+ const newSrc = toDescribed ? trackInfo.describedSrc : trackInfo.originalSrc;
289
+ newTrackElement.setAttribute('src', newSrc);
290
+
291
+ Object.keys(attributes).forEach(attrName => {
292
+ if (attrName !== 'src' && attrName !== 'data-desc-src') {
293
+ newTrackElement.setAttribute(attrName, attributes[attrName]);
294
+ }
295
+ });
296
+
297
+ // Use the player's video element as parent if the original parent is null
298
+ const targetParent = parent || this.player.element;
299
+
300
+ if (nextSibling && nextSibling.parentNode) {
301
+ targetParent.insertBefore(newTrackElement, nextSibling);
302
+ } else {
303
+ targetParent.appendChild(newTrackElement);
304
+ }
305
+
306
+ trackInfo.trackElement = newTrackElement;
307
+ });
308
+
309
+ this.player.invalidateTrackCache();
310
+
311
+ // Setup new tracks
312
+ const setupNewTracks = () => {
313
+ this.player.setManagedTimeout(() => {
314
+ swappedTracks.forEach((trackInfo) => {
315
+ const newTextTrack = trackInfo.trackElement.track;
316
+ if (newTextTrack) {
317
+ const modeInfo = trackModes.get(trackInfo) || { wasShowing: false, wasHidden: false };
318
+ newTextTrack.mode = 'hidden';
319
+
320
+ const restoreMode = () => {
321
+ if (modeInfo.wasShowing || modeInfo.wasHidden) {
322
+ newTextTrack.mode = 'hidden';
323
+ } else {
324
+ newTextTrack.mode = 'disabled';
325
+ }
326
+ };
327
+
328
+ if (newTextTrack.readyState >= 2) {
329
+ restoreMode();
330
+ } else {
331
+ newTextTrack.addEventListener('load', restoreMode, { once: true });
332
+ newTextTrack.addEventListener('error', restoreMode, { once: true });
333
+ }
334
+ }
335
+ });
336
+ }, 300);
337
+ };
338
+
339
+ if (this.player.element.readyState >= 1) {
340
+ setTimeout(setupNewTracks, 200);
341
+ } else {
342
+ this.player.element.addEventListener('loadedmetadata', setupNewTracks, { once: true });
343
+ setTimeout(setupNewTracks, 2000);
344
+ }
345
+
346
+ resolve();
347
+ }, 100);
348
+ });
349
+ }
350
+
351
+ return swappedTracks;
352
+ }
353
+
354
+ /**
355
+ * Update source elements to described versions
356
+ */
357
+ _updateSourceElements(toDescribed = true) {
358
+ const sourceElements = this.player.sourceElements;
359
+ const sourcesToUpdate = [];
360
+
361
+ sourceElements.forEach((sourceEl) => {
362
+ const descSrcAttr = sourceEl.getAttribute('data-desc-src');
363
+ const currentSrc = sourceEl.getAttribute('src');
364
+
365
+ if (descSrcAttr) {
366
+ const type = sourceEl.getAttribute('type');
367
+ let origSrc = sourceEl.getAttribute('data-orig-src') || currentSrc;
368
+
369
+ sourcesToUpdate.push({
370
+ src: toDescribed ? descSrcAttr : origSrc,
371
+ type,
372
+ origSrc,
373
+ descSrc: descSrcAttr
374
+ });
375
+ } else {
376
+ sourcesToUpdate.push({
377
+ src: sourceEl.getAttribute('src'),
378
+ type: sourceEl.getAttribute('type'),
379
+ origSrc: null,
380
+ descSrc: null
381
+ });
382
+ }
383
+ });
384
+
385
+ // Remove src attribute if present
386
+ if (this.player.element.hasAttribute('src')) {
387
+ this.player.element.removeAttribute('src');
388
+ }
389
+
390
+ // Remove all source elements
391
+ sourceElements.forEach(sourceEl => sourceEl.remove());
392
+
393
+ // Re-add with updated src
394
+ sourcesToUpdate.forEach(sourceInfo => {
395
+ const newSource = document.createElement('source');
396
+ newSource.setAttribute('src', sourceInfo.src);
397
+ if (sourceInfo.type) {
398
+ newSource.setAttribute('type', sourceInfo.type);
399
+ }
400
+ if (sourceInfo.origSrc) {
401
+ newSource.setAttribute('data-orig-src', sourceInfo.origSrc);
402
+ }
403
+ if (sourceInfo.descSrc) {
404
+ newSource.setAttribute('data-desc-src', sourceInfo.descSrc);
405
+ }
406
+
407
+ const firstTrack = this.player.element.querySelector('track');
408
+ if (firstTrack) {
409
+ this.player.element.insertBefore(newSource, firstTrack);
410
+ } else {
411
+ this.player.element.appendChild(newSource);
412
+ }
413
+ });
414
+
415
+ this.player._sourceElementsDirty = true;
416
+ this.player._sourceElementsCache = null;
417
+ }
418
+
419
+ /**
420
+ * Wait for media to be ready
421
+ */
422
+ async _waitForMediaReady(needSeek = false) {
423
+ // Wait for metadata
424
+ await new Promise((resolve) => {
425
+ if (this.player.element.readyState >= 1) {
426
+ resolve();
427
+ } else {
428
+ const onLoad = () => {
429
+ this.player.element.removeEventListener('loadedmetadata', onLoad);
430
+ resolve();
431
+ };
432
+ this.player.element.addEventListener('loadedmetadata', onLoad);
433
+ }
434
+ });
435
+
436
+ // Wait for tracks
437
+ await new Promise(resolve => setTimeout(resolve, 300));
438
+
439
+ // Wait for playback if needed
440
+ if (needSeek) {
441
+ await new Promise((resolve) => {
442
+ if (this.player.element.readyState >= 3) {
443
+ resolve();
444
+ } else {
445
+ const onCanPlay = () => {
446
+ this.player.element.removeEventListener('canplay', onCanPlay);
447
+ this.player.element.removeEventListener('canplaythrough', onCanPlay);
448
+ resolve();
449
+ };
450
+ this.player.element.addEventListener('canplay', onCanPlay, { once: true });
451
+ this.player.element.addEventListener('canplaythrough', onCanPlay, { once: true });
452
+ setTimeout(() => {
453
+ this.player.element.removeEventListener('canplay', onCanPlay);
454
+ this.player.element.removeEventListener('canplaythrough', onCanPlay);
455
+ resolve();
456
+ }, 3000);
457
+ }
458
+ });
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Restore playback state after source change
464
+ */
465
+ async _restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText) {
466
+ // Try to find matching caption for sync
467
+ let syncTime = currentTime;
468
+ if (currentCaptionText && this.player.captionManager && this.player.captionManager.tracks.length > 0) {
469
+ await new Promise(resolve => setTimeout(resolve, 500));
470
+ const matchingTime = this.player.findMatchingCaptionTime(
471
+ currentCaptionText,
472
+ this.player.captionManager.tracks
473
+ );
474
+ if (matchingTime !== null) {
475
+ syncTime = matchingTime;
476
+ if (this.player.options.debug) {
477
+ this.player.log(`[VidPly] Syncing via caption: ${currentTime}s -> ${syncTime}s`);
478
+ }
479
+ }
480
+ }
481
+
482
+ // Seek
483
+ if (syncTime > 0) {
484
+ this.player.seek(syncTime);
485
+ await new Promise(resolve => setTimeout(resolve, 100));
486
+ }
487
+
488
+ // Play/pause
489
+ if (wasPlaying) {
490
+ await this.player.play();
491
+ this.player.setManagedTimeout(() => {
492
+ this.player.hidePosterOverlay();
493
+ }, 100);
494
+ } else {
495
+ this.player.pause();
496
+ if (!shouldKeepPoster) {
497
+ this.player.hidePosterOverlay();
498
+ }
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Enable with source element method
504
+ */
505
+ async _enableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText) {
506
+ // Swap caption tracks
507
+ await this._swapCaptionTracks(true);
508
+
509
+ // Update source elements
510
+ this._updateSourceElements(true);
511
+
512
+ // Preserve poster
513
+ if (posterValue && this.player.element.tagName === 'VIDEO') {
514
+ this.player.element.poster = posterValue;
515
+ }
516
+
517
+ // Reload
518
+ this.player.element.load();
519
+
520
+ // Wait for ready
521
+ await this._waitForMediaReady(currentTime > 0 || wasPlaying);
522
+
523
+ // Restore playback
524
+ await this._restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText);
525
+
526
+ // Update state
527
+ if (!this.desiredState) return;
528
+ this.enabled = true;
529
+ this.player.state.audioDescriptionEnabled = true;
530
+ this.player.emit('audiodescriptionenabled');
531
+
532
+ // Reload transcript if visible
533
+ this._reloadTranscript();
534
+ }
535
+
536
+ /**
537
+ * Enable with direct src method
538
+ */
539
+ async _enableWithDirectSrc(currentTime, wasPlaying, posterValue, shouldKeepPoster) {
540
+ // Swap caption tracks
541
+ await this._swapCaptionTracks(true);
542
+
543
+ // Set poster
544
+ if (posterValue && this.player.element.tagName === 'VIDEO') {
545
+ this.player.element.poster = posterValue;
546
+ }
547
+
548
+ // Set src
549
+ this.player.element.src = this.src;
550
+
551
+ // Wait and restore
552
+ await this._waitForMediaReady(currentTime > 0 || wasPlaying);
553
+
554
+ if (currentTime > 0) {
555
+ this.player.seek(currentTime);
556
+ await new Promise(resolve => setTimeout(resolve, 100));
557
+ }
558
+
559
+ if (wasPlaying) {
560
+ await this.player.play();
561
+ } else {
562
+ this.player.pause();
563
+ if (!shouldKeepPoster) {
564
+ this.player.hidePosterOverlay();
565
+ }
566
+ }
567
+
568
+ if (!this.desiredState) return;
569
+ this.enabled = true;
570
+ this.player.state.audioDescriptionEnabled = true;
571
+ this.player.emit('audiodescriptionenabled');
572
+
573
+ // Reload transcript if visible
574
+ this._reloadTranscript();
575
+ }
576
+
577
+ /**
578
+ * Disable with source element method
579
+ */
580
+ async _disableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText) {
581
+ // Swap caption tracks back
582
+ await this._swapCaptionTracks(false);
583
+
584
+ // Update source elements
585
+ this._updateSourceElements(false);
586
+
587
+ // Preserve poster
588
+ if (posterValue && this.player.element.tagName === 'VIDEO') {
589
+ this.player.element.poster = posterValue;
590
+ }
591
+
592
+ // Reload
593
+ this.player.element.load();
594
+ this.player.invalidateTrackCache();
595
+
596
+ // Wait for ready
597
+ await this._waitForMediaReady(currentTime > 0 || wasPlaying);
598
+
599
+ // Restore playback
600
+ await this._restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText);
601
+
602
+ // Reinitialize caption manager
603
+ if (this.player.captionManager) {
604
+ this.player.captionManager.destroy();
605
+ this.player.captionManager = new CaptionManager(this.player);
606
+ }
607
+
608
+ // Update state
609
+ if (this.desiredState) return;
610
+ this.enabled = false;
611
+ this.player.state.audioDescriptionEnabled = false;
612
+ this.player.emit('audiodescriptiondisabled');
613
+
614
+ // Reload transcript if visible
615
+ this._reloadTranscript();
616
+ }
617
+
618
+ /**
619
+ * Disable with direct src method
620
+ */
621
+ async _disableWithDirectSrc(currentTime, wasPlaying, posterValue) {
622
+ // Swap caption tracks back
623
+ await this._swapCaptionTracks(false);
624
+
625
+ // Set poster
626
+ if (posterValue && this.player.element.tagName === 'VIDEO') {
627
+ this.player.element.poster = posterValue;
628
+ }
629
+
630
+ // Restore original src
631
+ const originalSrcToUse = this.originalSource || this.player.originalSrc;
632
+ this.player.element.src = originalSrcToUse;
633
+ this.player.element.load();
634
+
635
+ // Wait and restore
636
+ await this._waitForMediaReady(currentTime > 0 || wasPlaying);
637
+
638
+ if (currentTime > 0) {
639
+ this.player.seek(currentTime);
640
+ }
641
+
642
+ if (wasPlaying) {
643
+ await this.player.play();
644
+ }
645
+
646
+ if (this.desiredState) return;
647
+ this.enabled = false;
648
+ this.player.state.audioDescriptionEnabled = false;
649
+ this.player.emit('audiodescriptiondisabled');
650
+
651
+ // Reload transcript if visible
652
+ this._reloadTranscript();
653
+ }
654
+
655
+ /**
656
+ * Reload transcript after audio description state change
657
+ */
658
+ _reloadTranscript() {
659
+ if (this.player.transcriptManager && this.player.transcriptManager.isVisible) {
660
+ // Wait for tracks to load after source swap
661
+ this.player.setManagedTimeout(() => {
662
+ if (this.player.transcriptManager && this.player.transcriptManager.loadTranscriptData) {
663
+ this.player.transcriptManager.loadTranscriptData();
664
+ }
665
+ }, 800);
666
+ }
667
+ }
668
+
669
+ /**
670
+ * Update sources (called when playlist changes)
671
+ */
672
+ updateSources(audioDescriptionSrc) {
673
+ this.src = audioDescriptionSrc || null;
674
+ // Reset state for new playlist item
675
+ this.enabled = false;
676
+ this.desiredState = false;
677
+ this.sourceElement = null;
678
+ this.originalSource = null;
679
+ this.captionTracks = [];
680
+ }
681
+
682
+ /**
683
+ * Reinitialize from current player elements (called after playlist loads new tracks)
684
+ */
685
+ reinitialize() {
686
+ this.player.invalidateTrackCache();
687
+ this.initFromSourceElements(this.player.sourceElements, this.player.trackElements);
688
+ }
689
+
690
+ /**
691
+ * Cleanup
692
+ */
693
+ destroy() {
694
+ this.enabled = false;
695
+ this.desiredState = false;
696
+ this.captionTracks = [];
697
+ this.sourceElement = null;
698
+ this.originalSource = null;
699
+ }
700
+ }
701
+