vidply 1.0.4 → 1.0.6
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 +22 -22
- package/README.md +593 -517
- package/dist/vidply.css +1807 -1807
- package/dist/vidply.esm.js +268 -182
- package/dist/vidply.esm.js.map +3 -3
- package/dist/vidply.esm.min.js +6 -6
- package/dist/vidply.esm.min.meta.json +40 -34
- package/dist/vidply.js +268 -182
- package/dist/vidply.js.map +3 -3
- package/dist/vidply.min.js +6 -6
- package/dist/vidply.min.meta.json +40 -34
- package/package.json +57 -57
- package/src/controls/CaptionManager.js +248 -248
- package/src/controls/ControlBar.js +4 -4
- package/src/controls/KeyboardManager.js +233 -233
- package/src/controls/SettingsDialog.js +417 -417
- package/src/controls/TranscriptManager.js +728 -728
- package/src/core/Player.js +1186 -1134
- package/src/i18n/i18n.js +66 -66
- package/src/i18n/translations.js +561 -511
- package/src/icons/Icons.js +183 -183
- package/src/index.js +95 -95
- package/src/renderers/HLSRenderer.js +302 -302
- package/src/renderers/HTML5Renderer.js +298 -298
- package/src/renderers/VimeoRenderer.js +257 -257
- package/src/renderers/YouTubeRenderer.js +274 -274
- package/src/styles/vidply.css +1807 -1807
- package/src/utils/DOMUtils.js +154 -154
- package/src/utils/EventEmitter.js +53 -53
- package/src/utils/TimeUtils.js +87 -82
|
@@ -1,248 +1,248 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Caption/Subtitle Manager
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {DOMUtils} from '../utils/DOMUtils.js';
|
|
6
|
-
import {i18n} from '../i18n/i18n.js';
|
|
7
|
-
|
|
8
|
-
export class CaptionManager {
|
|
9
|
-
constructor(player) {
|
|
10
|
-
this.player = player;
|
|
11
|
-
this.element = null;
|
|
12
|
-
this.tracks = [];
|
|
13
|
-
this.currentTrack = null;
|
|
14
|
-
this.currentCue = null;
|
|
15
|
-
|
|
16
|
-
this.init();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
init() {
|
|
20
|
-
this.createElement();
|
|
21
|
-
this.loadTracks();
|
|
22
|
-
this.attachEvents();
|
|
23
|
-
|
|
24
|
-
if (this.player.options.captionsDefault && this.tracks.length > 0) {
|
|
25
|
-
this.enable();
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
createElement() {
|
|
30
|
-
this.element = DOMUtils.createElement('div', {
|
|
31
|
-
className: `${this.player.options.classPrefix}-captions`,
|
|
32
|
-
attributes: {
|
|
33
|
-
'aria-live': 'polite',
|
|
34
|
-
'aria-atomic': 'true',
|
|
35
|
-
'role': 'region',
|
|
36
|
-
'aria-label': i18n.t('player.captions')
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Apply caption styles
|
|
41
|
-
this.updateStyles();
|
|
42
|
-
|
|
43
|
-
// Append to videoWrapper if it exists, otherwise to container
|
|
44
|
-
const target = this.player.videoWrapper || this.player.container;
|
|
45
|
-
target.appendChild(this.element);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
loadTracks() {
|
|
49
|
-
const textTracks = this.player.element.textTracks;
|
|
50
|
-
|
|
51
|
-
for (let i = 0; i < textTracks.length; i++) {
|
|
52
|
-
const track = textTracks[i];
|
|
53
|
-
|
|
54
|
-
if (track.kind === 'subtitles' || track.kind === 'captions') {
|
|
55
|
-
this.tracks.push({
|
|
56
|
-
track: track,
|
|
57
|
-
language: track.language,
|
|
58
|
-
label: track.label,
|
|
59
|
-
kind: track.kind,
|
|
60
|
-
index: i
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// Disable all tracks initially
|
|
64
|
-
track.mode = 'hidden';
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
attachEvents() {
|
|
70
|
-
this.player.on('timeupdate', () => {
|
|
71
|
-
this.updateCaptions();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
this.player.on('captionschange', () => {
|
|
75
|
-
this.updateStyles();
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
enable(trackIndex = 0) {
|
|
80
|
-
if (this.tracks.length === 0) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Disable current track
|
|
85
|
-
if (this.currentTrack) {
|
|
86
|
-
this.currentTrack.track.mode = 'hidden';
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Enable selected track
|
|
90
|
-
const selectedTrack = this.tracks[trackIndex];
|
|
91
|
-
|
|
92
|
-
if (selectedTrack) {
|
|
93
|
-
// Set to 'hidden' not 'showing' to prevent browser from displaying native captions
|
|
94
|
-
// We'll handle the display ourselves
|
|
95
|
-
selectedTrack.track.mode = 'hidden';
|
|
96
|
-
this.currentTrack = selectedTrack;
|
|
97
|
-
this.player.state.captionsEnabled = true;
|
|
98
|
-
|
|
99
|
-
// Remove any existing cuechange listener
|
|
100
|
-
if (this.cueChangeHandler) {
|
|
101
|
-
selectedTrack.track.removeEventListener('cuechange', this.cueChangeHandler);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Add event listener for cue changes
|
|
105
|
-
this.cueChangeHandler = () => {
|
|
106
|
-
this.updateCaptions();
|
|
107
|
-
};
|
|
108
|
-
selectedTrack.track.addEventListener('cuechange', this.cueChangeHandler);
|
|
109
|
-
|
|
110
|
-
this.player.emit('captionsenabled', selectedTrack);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
disable() {
|
|
115
|
-
if (this.currentTrack) {
|
|
116
|
-
this.currentTrack.track.mode = 'hidden';
|
|
117
|
-
this.currentTrack = null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
this.element.style.display = 'none';
|
|
121
|
-
this.element.innerHTML = '';
|
|
122
|
-
this.currentCue = null;
|
|
123
|
-
this.player.state.captionsEnabled = false;
|
|
124
|
-
this.player.emit('captionsdisabled');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
updateCaptions() {
|
|
128
|
-
if (!this.currentTrack) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!this.currentTrack.track.activeCues) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const activeCues = this.currentTrack.track.activeCues;
|
|
137
|
-
|
|
138
|
-
if (activeCues.length > 0) {
|
|
139
|
-
const cue = activeCues[0];
|
|
140
|
-
|
|
141
|
-
// Only update if the cue has changed
|
|
142
|
-
if (this.currentCue !== cue) {
|
|
143
|
-
this.currentCue = cue;
|
|
144
|
-
|
|
145
|
-
// Parse and display cue text
|
|
146
|
-
let text = cue.text;
|
|
147
|
-
|
|
148
|
-
// Handle VTT formatting
|
|
149
|
-
text = this.parseVTTFormatting(text);
|
|
150
|
-
|
|
151
|
-
this.element.innerHTML = DOMUtils.sanitizeHTML(text);
|
|
152
|
-
|
|
153
|
-
// Make sure it's visible when there's content
|
|
154
|
-
this.element.style.display = 'block';
|
|
155
|
-
|
|
156
|
-
this.player.emit('captionchange', cue);
|
|
157
|
-
}
|
|
158
|
-
} else if (this.currentCue) {
|
|
159
|
-
// Clear caption
|
|
160
|
-
this.element.innerHTML = '';
|
|
161
|
-
this.element.style.display = 'none';
|
|
162
|
-
this.currentCue = null;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
parseVTTFormatting(text) {
|
|
167
|
-
// Basic VTT tag support
|
|
168
|
-
text = text.replace(/<c[^>]*>(.*?)<\/c>/g, '<span class="caption-class">$1</span>');
|
|
169
|
-
text = text.replace(/<b>(.*?)<\/b>/g, '<strong>$1</strong>');
|
|
170
|
-
text = text.replace(/<i>(.*?)<\/i>/g, '<em>$1</em>');
|
|
171
|
-
text = text.replace(/<u>(.*?)<\/u>/g, '<u>$1</u>');
|
|
172
|
-
|
|
173
|
-
// Voice tags
|
|
174
|
-
text = text.replace(/<v\s+([^>]+)>(.*?)<\/v>/g, '<span class="caption-voice" data-voice="$1">$2</span>');
|
|
175
|
-
|
|
176
|
-
return text;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
updateStyles() {
|
|
180
|
-
if (!this.element) return;
|
|
181
|
-
|
|
182
|
-
const options = this.player.options;
|
|
183
|
-
|
|
184
|
-
this.element.style.fontSize = options.captionsFontSize;
|
|
185
|
-
this.element.style.fontFamily = options.captionsFontFamily;
|
|
186
|
-
this.element.style.color = options.captionsColor;
|
|
187
|
-
this.element.style.backgroundColor = this.hexToRgba(
|
|
188
|
-
options.captionsBackgroundColor,
|
|
189
|
-
options.captionsOpacity
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
hexToRgba(hex, alpha) {
|
|
194
|
-
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
195
|
-
if (result) {
|
|
196
|
-
return `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${alpha})`;
|
|
197
|
-
}
|
|
198
|
-
return hex;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
setCaptionStyle(property, value) {
|
|
202
|
-
switch (property) {
|
|
203
|
-
case 'fontSize':
|
|
204
|
-
this.player.options.captionsFontSize = value;
|
|
205
|
-
break;
|
|
206
|
-
case 'fontFamily':
|
|
207
|
-
this.player.options.captionsFontFamily = value;
|
|
208
|
-
break;
|
|
209
|
-
case 'color':
|
|
210
|
-
this.player.options.captionsColor = value;
|
|
211
|
-
break;
|
|
212
|
-
case 'backgroundColor':
|
|
213
|
-
this.player.options.captionsBackgroundColor = value;
|
|
214
|
-
break;
|
|
215
|
-
case 'opacity':
|
|
216
|
-
this.player.options.captionsOpacity = value;
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
this.updateStyles();
|
|
221
|
-
this.player.emit('captionschange');
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
getAvailableTracks() {
|
|
225
|
-
return this.tracks.map((t, index) => ({
|
|
226
|
-
index,
|
|
227
|
-
language: t.language,
|
|
228
|
-
label: t.label || t.language,
|
|
229
|
-
kind: t.kind
|
|
230
|
-
}));
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
switchTrack(trackIndex) {
|
|
234
|
-
if (trackIndex >= 0 && trackIndex < this.tracks.length) {
|
|
235
|
-
this.disable();
|
|
236
|
-
this.enable(trackIndex);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
destroy() {
|
|
241
|
-
this.disable();
|
|
242
|
-
|
|
243
|
-
if (this.element && this.element.parentNode) {
|
|
244
|
-
this.element.parentNode.removeChild(this.element);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Caption/Subtitle Manager
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {DOMUtils} from '../utils/DOMUtils.js';
|
|
6
|
+
import {i18n} from '../i18n/i18n.js';
|
|
7
|
+
|
|
8
|
+
export class CaptionManager {
|
|
9
|
+
constructor(player) {
|
|
10
|
+
this.player = player;
|
|
11
|
+
this.element = null;
|
|
12
|
+
this.tracks = [];
|
|
13
|
+
this.currentTrack = null;
|
|
14
|
+
this.currentCue = null;
|
|
15
|
+
|
|
16
|
+
this.init();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
init() {
|
|
20
|
+
this.createElement();
|
|
21
|
+
this.loadTracks();
|
|
22
|
+
this.attachEvents();
|
|
23
|
+
|
|
24
|
+
if (this.player.options.captionsDefault && this.tracks.length > 0) {
|
|
25
|
+
this.enable();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
createElement() {
|
|
30
|
+
this.element = DOMUtils.createElement('div', {
|
|
31
|
+
className: `${this.player.options.classPrefix}-captions`,
|
|
32
|
+
attributes: {
|
|
33
|
+
'aria-live': 'polite',
|
|
34
|
+
'aria-atomic': 'true',
|
|
35
|
+
'role': 'region',
|
|
36
|
+
'aria-label': i18n.t('player.captions')
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Apply caption styles
|
|
41
|
+
this.updateStyles();
|
|
42
|
+
|
|
43
|
+
// Append to videoWrapper if it exists, otherwise to container
|
|
44
|
+
const target = this.player.videoWrapper || this.player.container;
|
|
45
|
+
target.appendChild(this.element);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
loadTracks() {
|
|
49
|
+
const textTracks = this.player.element.textTracks;
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < textTracks.length; i++) {
|
|
52
|
+
const track = textTracks[i];
|
|
53
|
+
|
|
54
|
+
if (track.kind === 'subtitles' || track.kind === 'captions') {
|
|
55
|
+
this.tracks.push({
|
|
56
|
+
track: track,
|
|
57
|
+
language: track.language,
|
|
58
|
+
label: track.label,
|
|
59
|
+
kind: track.kind,
|
|
60
|
+
index: i
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Disable all tracks initially
|
|
64
|
+
track.mode = 'hidden';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
attachEvents() {
|
|
70
|
+
this.player.on('timeupdate', () => {
|
|
71
|
+
this.updateCaptions();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
this.player.on('captionschange', () => {
|
|
75
|
+
this.updateStyles();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
enable(trackIndex = 0) {
|
|
80
|
+
if (this.tracks.length === 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Disable current track
|
|
85
|
+
if (this.currentTrack) {
|
|
86
|
+
this.currentTrack.track.mode = 'hidden';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Enable selected track
|
|
90
|
+
const selectedTrack = this.tracks[trackIndex];
|
|
91
|
+
|
|
92
|
+
if (selectedTrack) {
|
|
93
|
+
// Set to 'hidden' not 'showing' to prevent browser from displaying native captions
|
|
94
|
+
// We'll handle the display ourselves
|
|
95
|
+
selectedTrack.track.mode = 'hidden';
|
|
96
|
+
this.currentTrack = selectedTrack;
|
|
97
|
+
this.player.state.captionsEnabled = true;
|
|
98
|
+
|
|
99
|
+
// Remove any existing cuechange listener
|
|
100
|
+
if (this.cueChangeHandler) {
|
|
101
|
+
selectedTrack.track.removeEventListener('cuechange', this.cueChangeHandler);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Add event listener for cue changes
|
|
105
|
+
this.cueChangeHandler = () => {
|
|
106
|
+
this.updateCaptions();
|
|
107
|
+
};
|
|
108
|
+
selectedTrack.track.addEventListener('cuechange', this.cueChangeHandler);
|
|
109
|
+
|
|
110
|
+
this.player.emit('captionsenabled', selectedTrack);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
disable() {
|
|
115
|
+
if (this.currentTrack) {
|
|
116
|
+
this.currentTrack.track.mode = 'hidden';
|
|
117
|
+
this.currentTrack = null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.element.style.display = 'none';
|
|
121
|
+
this.element.innerHTML = '';
|
|
122
|
+
this.currentCue = null;
|
|
123
|
+
this.player.state.captionsEnabled = false;
|
|
124
|
+
this.player.emit('captionsdisabled');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
updateCaptions() {
|
|
128
|
+
if (!this.currentTrack) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!this.currentTrack.track.activeCues) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const activeCues = this.currentTrack.track.activeCues;
|
|
137
|
+
|
|
138
|
+
if (activeCues.length > 0) {
|
|
139
|
+
const cue = activeCues[0];
|
|
140
|
+
|
|
141
|
+
// Only update if the cue has changed
|
|
142
|
+
if (this.currentCue !== cue) {
|
|
143
|
+
this.currentCue = cue;
|
|
144
|
+
|
|
145
|
+
// Parse and display cue text
|
|
146
|
+
let text = cue.text;
|
|
147
|
+
|
|
148
|
+
// Handle VTT formatting
|
|
149
|
+
text = this.parseVTTFormatting(text);
|
|
150
|
+
|
|
151
|
+
this.element.innerHTML = DOMUtils.sanitizeHTML(text);
|
|
152
|
+
|
|
153
|
+
// Make sure it's visible when there's content
|
|
154
|
+
this.element.style.display = 'block';
|
|
155
|
+
|
|
156
|
+
this.player.emit('captionchange', cue);
|
|
157
|
+
}
|
|
158
|
+
} else if (this.currentCue) {
|
|
159
|
+
// Clear caption
|
|
160
|
+
this.element.innerHTML = '';
|
|
161
|
+
this.element.style.display = 'none';
|
|
162
|
+
this.currentCue = null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
parseVTTFormatting(text) {
|
|
167
|
+
// Basic VTT tag support
|
|
168
|
+
text = text.replace(/<c[^>]*>(.*?)<\/c>/g, '<span class="caption-class">$1</span>');
|
|
169
|
+
text = text.replace(/<b>(.*?)<\/b>/g, '<strong>$1</strong>');
|
|
170
|
+
text = text.replace(/<i>(.*?)<\/i>/g, '<em>$1</em>');
|
|
171
|
+
text = text.replace(/<u>(.*?)<\/u>/g, '<u>$1</u>');
|
|
172
|
+
|
|
173
|
+
// Voice tags
|
|
174
|
+
text = text.replace(/<v\s+([^>]+)>(.*?)<\/v>/g, '<span class="caption-voice" data-voice="$1">$2</span>');
|
|
175
|
+
|
|
176
|
+
return text;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
updateStyles() {
|
|
180
|
+
if (!this.element) return;
|
|
181
|
+
|
|
182
|
+
const options = this.player.options;
|
|
183
|
+
|
|
184
|
+
this.element.style.fontSize = options.captionsFontSize;
|
|
185
|
+
this.element.style.fontFamily = options.captionsFontFamily;
|
|
186
|
+
this.element.style.color = options.captionsColor;
|
|
187
|
+
this.element.style.backgroundColor = this.hexToRgba(
|
|
188
|
+
options.captionsBackgroundColor,
|
|
189
|
+
options.captionsOpacity
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
hexToRgba(hex, alpha) {
|
|
194
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
195
|
+
if (result) {
|
|
196
|
+
return `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${alpha})`;
|
|
197
|
+
}
|
|
198
|
+
return hex;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
setCaptionStyle(property, value) {
|
|
202
|
+
switch (property) {
|
|
203
|
+
case 'fontSize':
|
|
204
|
+
this.player.options.captionsFontSize = value;
|
|
205
|
+
break;
|
|
206
|
+
case 'fontFamily':
|
|
207
|
+
this.player.options.captionsFontFamily = value;
|
|
208
|
+
break;
|
|
209
|
+
case 'color':
|
|
210
|
+
this.player.options.captionsColor = value;
|
|
211
|
+
break;
|
|
212
|
+
case 'backgroundColor':
|
|
213
|
+
this.player.options.captionsBackgroundColor = value;
|
|
214
|
+
break;
|
|
215
|
+
case 'opacity':
|
|
216
|
+
this.player.options.captionsOpacity = value;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.updateStyles();
|
|
221
|
+
this.player.emit('captionschange');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
getAvailableTracks() {
|
|
225
|
+
return this.tracks.map((t, index) => ({
|
|
226
|
+
index,
|
|
227
|
+
language: t.language,
|
|
228
|
+
label: t.label || t.language,
|
|
229
|
+
kind: t.kind
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
switchTrack(trackIndex) {
|
|
234
|
+
if (trackIndex >= 0 && trackIndex < this.tracks.length) {
|
|
235
|
+
this.disable();
|
|
236
|
+
this.enable(trackIndex);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
destroy() {
|
|
241
|
+
this.disable();
|
|
242
|
+
|
|
243
|
+
if (this.element && this.element.parentNode) {
|
|
244
|
+
this.element.parentNode.removeChild(this.element);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
@@ -701,7 +701,7 @@ export class ControlBar {
|
|
|
701
701
|
className: `${this.player.options.classPrefix}-time`,
|
|
702
702
|
attributes: {
|
|
703
703
|
'role': 'group',
|
|
704
|
-
'aria-label': '
|
|
704
|
+
'aria-label': i18n.t('time.display')
|
|
705
705
|
}
|
|
706
706
|
});
|
|
707
707
|
|
|
@@ -709,7 +709,7 @@ export class ControlBar {
|
|
|
709
709
|
this.controls.currentTimeDisplay = DOMUtils.createElement('span', {
|
|
710
710
|
className: `${this.player.options.classPrefix}-current-time`,
|
|
711
711
|
attributes: {
|
|
712
|
-
'aria-label': '0
|
|
712
|
+
'aria-label': i18n.t('time.seconds', { count: 0 })
|
|
713
713
|
}
|
|
714
714
|
});
|
|
715
715
|
|
|
@@ -734,7 +734,7 @@ export class ControlBar {
|
|
|
734
734
|
this.controls.durationDisplay = DOMUtils.createElement('span', {
|
|
735
735
|
className: `${this.player.options.classPrefix}-duration`,
|
|
736
736
|
attributes: {
|
|
737
|
-
'aria-label': '
|
|
737
|
+
'aria-label': i18n.t('time.durationPrefix') + i18n.t('time.seconds', { count: 0 })
|
|
738
738
|
}
|
|
739
739
|
});
|
|
740
740
|
|
|
@@ -1835,7 +1835,7 @@ export class ControlBar {
|
|
|
1835
1835
|
// Update visual text (hidden from screen readers)
|
|
1836
1836
|
this.controls.durationVisual.textContent = TimeUtils.formatTime(duration);
|
|
1837
1837
|
// Update aria-label with human-readable format
|
|
1838
|
-
this.controls.durationDisplay.setAttribute('aria-label', '
|
|
1838
|
+
this.controls.durationDisplay.setAttribute('aria-label', i18n.t('time.durationPrefix') + TimeUtils.formatDuration(duration));
|
|
1839
1839
|
}
|
|
1840
1840
|
}
|
|
1841
1841
|
|