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.
- package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js → vidply.TranscriptManager-T677KF4N.js} +4 -5
- package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js.map → vidply.TranscriptManager-T677KF4N.js.map} +2 -2
- package/dist/dev/{vidply.chunk-SRM7VNHG.js → vidply.chunk-GS2JX5RQ.js} +136 -95
- package/dist/dev/vidply.chunk-GS2JX5RQ.js.map +7 -0
- package/dist/dev/vidply.esm.js +1674 -310
- package/dist/dev/vidply.esm.js.map +4 -4
- package/dist/legacy/vidply.js +1776 -348
- package/dist/legacy/vidply.js.map +4 -4
- package/dist/legacy/vidply.min.js +1 -1
- package/dist/legacy/vidply.min.meta.json +92 -24
- package/dist/prod/vidply.TranscriptManager-WFZSW6NR.min.js +6 -0
- package/dist/prod/vidply.chunk-LGTJRPUL.min.js +6 -0
- package/dist/prod/vidply.esm.min.js +8 -8
- package/dist/vidply.esm.min.meta.json +92 -24
- package/package.json +1 -1
- package/src/controls/ControlBar.js +3 -7
- package/src/controls/TranscriptManager.js +7 -7
- package/src/core/AudioDescriptionManager.js +701 -0
- package/src/core/Player.js +4776 -4921
- package/src/core/SignLanguageManager.js +1134 -0
- package/src/utils/DOMUtils.js +153 -114
- package/src/utils/MenuFactory.js +374 -0
- package/dist/dev/vidply.TranscriptManager-GZKY44ON.js +0 -1744
- package/dist/dev/vidply.TranscriptManager-GZKY44ON.js.map +0 -7
- package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js +0 -1744
- package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js.map +0 -7
- package/dist/dev/vidply.chunk-5663PYKK.js +0 -1631
- package/dist/dev/vidply.chunk-5663PYKK.js.map +0 -7
- package/dist/dev/vidply.chunk-SRM7VNHG.js.map +0 -7
- package/dist/dev/vidply.chunk-UH5MTGKF.js +0 -1630
- package/dist/dev/vidply.chunk-UH5MTGKF.js.map +0 -7
- package/dist/dev/vidply.de-RXAJM5QE.js +0 -181
- package/dist/dev/vidply.de-RXAJM5QE.js.map +0 -7
- package/dist/dev/vidply.de-THBIMP4S.js +0 -180
- package/dist/dev/vidply.de-THBIMP4S.js.map +0 -7
- package/dist/dev/vidply.es-6VWDNNNL.js +0 -180
- package/dist/dev/vidply.es-6VWDNNNL.js.map +0 -7
- package/dist/dev/vidply.es-SADVLJTQ.js +0 -181
- package/dist/dev/vidply.es-SADVLJTQ.js.map +0 -7
- package/dist/dev/vidply.fr-V3VAYBBT.js +0 -181
- package/dist/dev/vidply.fr-V3VAYBBT.js.map +0 -7
- package/dist/dev/vidply.fr-WHTWCHWT.js +0 -180
- package/dist/dev/vidply.fr-WHTWCHWT.js.map +0 -7
- package/dist/dev/vidply.ja-BFQNPOFI.js +0 -180
- package/dist/dev/vidply.ja-BFQNPOFI.js.map +0 -7
- package/dist/dev/vidply.ja-KL2TLZGJ.js +0 -181
- package/dist/dev/vidply.ja-KL2TLZGJ.js.map +0 -7
- package/dist/prod/vidply.TranscriptManager-DZ2WZU3K.min.js +0 -6
- package/dist/prod/vidply.TranscriptManager-E5QHGFIR.min.js +0 -6
- package/dist/prod/vidply.TranscriptManager-UZ6DUFB6.min.js +0 -6
- package/dist/prod/vidply.chunk-5DWTMWEO.min.js +0 -6
- package/dist/prod/vidply.chunk-IBNYTGGM.min.js +0 -6
- package/dist/prod/vidply.chunk-MBUR3U5L.min.js +0 -6
- package/dist/prod/vidply.de-HGJBCLLE.min.js +0 -6
- package/dist/prod/vidply.de-SWFW4HYT.min.js +0 -6
- package/dist/prod/vidply.es-7BJ2DJAY.min.js +0 -6
- package/dist/prod/vidply.es-CZEBXCZN.min.js +0 -6
- package/dist/prod/vidply.fr-DPVR5DFY.min.js +0 -6
- package/dist/prod/vidply.fr-HFOL7MWA.min.js +0 -6
- package/dist/prod/vidply.ja-PEBVWKVH.min.js +0 -6
- 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
|
+
|