vidply 1.0.5 → 1.0.7
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 +608 -593
- package/dist/vidply.css +2422 -1807
- package/dist/vidply.esm.js +1480 -93
- package/dist/vidply.esm.js.map +3 -3
- package/dist/vidply.esm.min.js +3 -3
- package/dist/vidply.esm.min.meta.json +48 -25
- package/dist/vidply.js +1480 -93
- package/dist/vidply.js.map +3 -3
- package/dist/vidply.min.css +1 -1
- package/dist/vidply.min.js +3 -3
- package/dist/vidply.min.meta.json +48 -25
- package/package.json +2 -2
- package/src/controls/CaptionManager.js +278 -248
- package/src/controls/ControlBar.js +2033 -2026
- package/src/controls/KeyboardManager.js +233 -233
- package/src/controls/SettingsDialog.js +417 -417
- package/src/controls/TranscriptManager.js +1803 -728
- package/src/core/Player.js +1616 -1134
- package/src/i18n/i18n.js +66 -66
- package/src/i18n/translations.js +616 -561
- package/src/icons/Icons.js +187 -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 +2422 -1807
- package/src/utils/DOMUtils.js +154 -154
- package/src/utils/EventEmitter.js +53 -53
- package/src/utils/StorageManager.js +156 -0
- package/src/utils/TimeUtils.js +87 -87
|
@@ -1,233 +1,233 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Keyboard Accessibility Manager
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export class KeyboardManager {
|
|
6
|
-
constructor(player) {
|
|
7
|
-
this.player = player;
|
|
8
|
-
this.shortcuts = player.options.keyboardShortcuts;
|
|
9
|
-
|
|
10
|
-
this.init();
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
init() {
|
|
14
|
-
this.attachEvents();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
attachEvents() {
|
|
18
|
-
// Listen for keyboard events on the player container
|
|
19
|
-
this.player.container.addEventListener('keydown', (e) => {
|
|
20
|
-
this.handleKeydown(e);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// Make player container focusable
|
|
24
|
-
if (!this.player.container.hasAttribute('tabindex')) {
|
|
25
|
-
this.player.container.setAttribute('tabindex', '0');
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
handleKeydown(e) {
|
|
30
|
-
// Don't handle if target is an input element
|
|
31
|
-
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const key = e.key;
|
|
36
|
-
let handled = false;
|
|
37
|
-
|
|
38
|
-
// Check each shortcut category
|
|
39
|
-
for (const [action, keys] of Object.entries(this.shortcuts)) {
|
|
40
|
-
if (keys.includes(key)) {
|
|
41
|
-
handled = this.executeAction(action, e);
|
|
42
|
-
if (handled) {
|
|
43
|
-
e.preventDefault();
|
|
44
|
-
e.stopPropagation();
|
|
45
|
-
this.announceAction(action);
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Log unhandled keys for debugging (in development)
|
|
52
|
-
if (!handled && this.player.options.debug) {
|
|
53
|
-
console.log('[VidPly] Unhandled key:', e.key, 'code:', e.code, 'shiftKey:', e.shiftKey);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
executeAction(action, event) {
|
|
58
|
-
switch (action) {
|
|
59
|
-
case 'play-pause':
|
|
60
|
-
this.player.toggle();
|
|
61
|
-
return true;
|
|
62
|
-
|
|
63
|
-
case 'volume-up':
|
|
64
|
-
this.player.setVolume(Math.min(1, this.player.state.volume + 0.1));
|
|
65
|
-
return true;
|
|
66
|
-
|
|
67
|
-
case 'volume-down':
|
|
68
|
-
this.player.setVolume(Math.max(0, this.player.state.volume - 0.1));
|
|
69
|
-
return true;
|
|
70
|
-
|
|
71
|
-
case 'seek-forward':
|
|
72
|
-
this.player.seekForward();
|
|
73
|
-
return true;
|
|
74
|
-
|
|
75
|
-
case 'seek-backward':
|
|
76
|
-
this.player.seekBackward();
|
|
77
|
-
return true;
|
|
78
|
-
|
|
79
|
-
case 'mute':
|
|
80
|
-
this.player.toggleMute();
|
|
81
|
-
return true;
|
|
82
|
-
|
|
83
|
-
case 'fullscreen':
|
|
84
|
-
this.player.toggleFullscreen();
|
|
85
|
-
return true;
|
|
86
|
-
|
|
87
|
-
case 'captions':
|
|
88
|
-
// If only one caption track, toggle on/off
|
|
89
|
-
// If multiple tracks, open caption menu
|
|
90
|
-
if (this.player.captionManager && this.player.captionManager.tracks.length > 1) {
|
|
91
|
-
// Get captions button from control bar
|
|
92
|
-
const captionsButton = this.player.controlBar && this.player.controlBar.controls.captions;
|
|
93
|
-
if (captionsButton) {
|
|
94
|
-
this.player.controlBar.showCaptionsMenu(captionsButton);
|
|
95
|
-
} else {
|
|
96
|
-
// Fallback to toggle if button doesn't exist
|
|
97
|
-
this.player.toggleCaptions();
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
this.player.toggleCaptions();
|
|
101
|
-
}
|
|
102
|
-
return true;
|
|
103
|
-
|
|
104
|
-
case 'caption-style-menu':
|
|
105
|
-
// Open caption style menu
|
|
106
|
-
if (this.player.controlBar && this.player.controlBar.controls.captionStyle) {
|
|
107
|
-
this.player.controlBar.showCaptionStyleMenu(this.player.controlBar.controls.captionStyle);
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
return false;
|
|
111
|
-
|
|
112
|
-
case 'speed-up':
|
|
113
|
-
this.player.setPlaybackSpeed(
|
|
114
|
-
Math.min(2, this.player.state.playbackSpeed + 0.25)
|
|
115
|
-
);
|
|
116
|
-
return true;
|
|
117
|
-
|
|
118
|
-
case 'speed-down':
|
|
119
|
-
this.player.setPlaybackSpeed(
|
|
120
|
-
Math.max(0.25, this.player.state.playbackSpeed - 0.25)
|
|
121
|
-
);
|
|
122
|
-
return true;
|
|
123
|
-
|
|
124
|
-
case 'speed-menu':
|
|
125
|
-
// Open speed menu
|
|
126
|
-
if (this.player.controlBar && this.player.controlBar.controls.speed) {
|
|
127
|
-
this.player.controlBar.showSpeedMenu(this.player.controlBar.controls.speed);
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
return false;
|
|
131
|
-
|
|
132
|
-
case 'quality-menu':
|
|
133
|
-
// Open quality menu
|
|
134
|
-
if (this.player.controlBar && this.player.controlBar.controls.quality) {
|
|
135
|
-
this.player.controlBar.showQualityMenu(this.player.controlBar.controls.quality);
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
return false;
|
|
139
|
-
|
|
140
|
-
case 'chapters-menu':
|
|
141
|
-
// Open chapters menu
|
|
142
|
-
if (this.player.controlBar && this.player.controlBar.controls.chapters) {
|
|
143
|
-
this.player.controlBar.showChaptersMenu(this.player.controlBar.controls.chapters);
|
|
144
|
-
return true;
|
|
145
|
-
}
|
|
146
|
-
return false;
|
|
147
|
-
|
|
148
|
-
case 'transcript-toggle':
|
|
149
|
-
// Toggle transcript
|
|
150
|
-
if (this.player.transcriptManager) {
|
|
151
|
-
this.player.transcriptManager.toggleTranscript();
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
return false;
|
|
155
|
-
|
|
156
|
-
default:
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
announceAction(action) {
|
|
162
|
-
if (!this.player.options.screenReaderAnnouncements) return;
|
|
163
|
-
|
|
164
|
-
let message = '';
|
|
165
|
-
|
|
166
|
-
switch (action) {
|
|
167
|
-
case 'play-pause':
|
|
168
|
-
message = this.player.state.playing ? 'Playing' : 'Paused';
|
|
169
|
-
break;
|
|
170
|
-
case 'volume-up':
|
|
171
|
-
message = `Volume ${Math.round(this.player.state.volume * 100)}%`;
|
|
172
|
-
break;
|
|
173
|
-
case 'volume-down':
|
|
174
|
-
message = `Volume ${Math.round(this.player.state.volume * 100)}%`;
|
|
175
|
-
break;
|
|
176
|
-
case 'mute':
|
|
177
|
-
message = this.player.state.muted ? 'Muted' : 'Unmuted';
|
|
178
|
-
break;
|
|
179
|
-
case 'fullscreen':
|
|
180
|
-
message = this.player.state.fullscreen ? 'Fullscreen' : 'Exit fullscreen';
|
|
181
|
-
break;
|
|
182
|
-
case 'captions':
|
|
183
|
-
message = this.player.state.captionsEnabled ? 'Captions on' : 'Captions off';
|
|
184
|
-
break;
|
|
185
|
-
case 'speed-up':
|
|
186
|
-
case 'speed-down':
|
|
187
|
-
message = `Speed ${this.player.state.playbackSpeed}x`;
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (message) {
|
|
192
|
-
this.announce(message);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
announce(message, priority = 'polite') {
|
|
197
|
-
// Create or get announcement element
|
|
198
|
-
let announcer = document.getElementById('vidply-announcer');
|
|
199
|
-
|
|
200
|
-
if (!announcer) {
|
|
201
|
-
announcer = document.createElement('div');
|
|
202
|
-
announcer.id = 'vidply-announcer';
|
|
203
|
-
announcer.className = 'vidply-sr-only';
|
|
204
|
-
announcer.setAttribute('aria-live', priority);
|
|
205
|
-
announcer.setAttribute('aria-atomic', 'true');
|
|
206
|
-
announcer.style.cssText = `
|
|
207
|
-
position: absolute;
|
|
208
|
-
left: -10000px;
|
|
209
|
-
width: 1px;
|
|
210
|
-
height: 1px;
|
|
211
|
-
overflow: hidden;
|
|
212
|
-
`;
|
|
213
|
-
document.body.appendChild(announcer);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Clear and set new message
|
|
217
|
-
announcer.textContent = '';
|
|
218
|
-
setTimeout(() => {
|
|
219
|
-
announcer.textContent = message;
|
|
220
|
-
}, 100);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
updateShortcut(action, keys) {
|
|
224
|
-
if (Array.isArray(keys)) {
|
|
225
|
-
this.shortcuts[action] = keys;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
destroy() {
|
|
230
|
-
// Event listeners are automatically removed when the container is destroyed
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard Accessibility Manager
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class KeyboardManager {
|
|
6
|
+
constructor(player) {
|
|
7
|
+
this.player = player;
|
|
8
|
+
this.shortcuts = player.options.keyboardShortcuts;
|
|
9
|
+
|
|
10
|
+
this.init();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
init() {
|
|
14
|
+
this.attachEvents();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
attachEvents() {
|
|
18
|
+
// Listen for keyboard events on the player container
|
|
19
|
+
this.player.container.addEventListener('keydown', (e) => {
|
|
20
|
+
this.handleKeydown(e);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Make player container focusable
|
|
24
|
+
if (!this.player.container.hasAttribute('tabindex')) {
|
|
25
|
+
this.player.container.setAttribute('tabindex', '0');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
handleKeydown(e) {
|
|
30
|
+
// Don't handle if target is an input element
|
|
31
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const key = e.key;
|
|
36
|
+
let handled = false;
|
|
37
|
+
|
|
38
|
+
// Check each shortcut category
|
|
39
|
+
for (const [action, keys] of Object.entries(this.shortcuts)) {
|
|
40
|
+
if (keys.includes(key)) {
|
|
41
|
+
handled = this.executeAction(action, e);
|
|
42
|
+
if (handled) {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
e.stopPropagation();
|
|
45
|
+
this.announceAction(action);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Log unhandled keys for debugging (in development)
|
|
52
|
+
if (!handled && this.player.options.debug) {
|
|
53
|
+
console.log('[VidPly] Unhandled key:', e.key, 'code:', e.code, 'shiftKey:', e.shiftKey);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
executeAction(action, event) {
|
|
58
|
+
switch (action) {
|
|
59
|
+
case 'play-pause':
|
|
60
|
+
this.player.toggle();
|
|
61
|
+
return true;
|
|
62
|
+
|
|
63
|
+
case 'volume-up':
|
|
64
|
+
this.player.setVolume(Math.min(1, this.player.state.volume + 0.1));
|
|
65
|
+
return true;
|
|
66
|
+
|
|
67
|
+
case 'volume-down':
|
|
68
|
+
this.player.setVolume(Math.max(0, this.player.state.volume - 0.1));
|
|
69
|
+
return true;
|
|
70
|
+
|
|
71
|
+
case 'seek-forward':
|
|
72
|
+
this.player.seekForward();
|
|
73
|
+
return true;
|
|
74
|
+
|
|
75
|
+
case 'seek-backward':
|
|
76
|
+
this.player.seekBackward();
|
|
77
|
+
return true;
|
|
78
|
+
|
|
79
|
+
case 'mute':
|
|
80
|
+
this.player.toggleMute();
|
|
81
|
+
return true;
|
|
82
|
+
|
|
83
|
+
case 'fullscreen':
|
|
84
|
+
this.player.toggleFullscreen();
|
|
85
|
+
return true;
|
|
86
|
+
|
|
87
|
+
case 'captions':
|
|
88
|
+
// If only one caption track, toggle on/off
|
|
89
|
+
// If multiple tracks, open caption menu
|
|
90
|
+
if (this.player.captionManager && this.player.captionManager.tracks.length > 1) {
|
|
91
|
+
// Get captions button from control bar
|
|
92
|
+
const captionsButton = this.player.controlBar && this.player.controlBar.controls.captions;
|
|
93
|
+
if (captionsButton) {
|
|
94
|
+
this.player.controlBar.showCaptionsMenu(captionsButton);
|
|
95
|
+
} else {
|
|
96
|
+
// Fallback to toggle if button doesn't exist
|
|
97
|
+
this.player.toggleCaptions();
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
this.player.toggleCaptions();
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
|
|
104
|
+
case 'caption-style-menu':
|
|
105
|
+
// Open caption style menu
|
|
106
|
+
if (this.player.controlBar && this.player.controlBar.controls.captionStyle) {
|
|
107
|
+
this.player.controlBar.showCaptionStyleMenu(this.player.controlBar.controls.captionStyle);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
|
|
112
|
+
case 'speed-up':
|
|
113
|
+
this.player.setPlaybackSpeed(
|
|
114
|
+
Math.min(2, this.player.state.playbackSpeed + 0.25)
|
|
115
|
+
);
|
|
116
|
+
return true;
|
|
117
|
+
|
|
118
|
+
case 'speed-down':
|
|
119
|
+
this.player.setPlaybackSpeed(
|
|
120
|
+
Math.max(0.25, this.player.state.playbackSpeed - 0.25)
|
|
121
|
+
);
|
|
122
|
+
return true;
|
|
123
|
+
|
|
124
|
+
case 'speed-menu':
|
|
125
|
+
// Open speed menu
|
|
126
|
+
if (this.player.controlBar && this.player.controlBar.controls.speed) {
|
|
127
|
+
this.player.controlBar.showSpeedMenu(this.player.controlBar.controls.speed);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
|
|
132
|
+
case 'quality-menu':
|
|
133
|
+
// Open quality menu
|
|
134
|
+
if (this.player.controlBar && this.player.controlBar.controls.quality) {
|
|
135
|
+
this.player.controlBar.showQualityMenu(this.player.controlBar.controls.quality);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
|
|
140
|
+
case 'chapters-menu':
|
|
141
|
+
// Open chapters menu
|
|
142
|
+
if (this.player.controlBar && this.player.controlBar.controls.chapters) {
|
|
143
|
+
this.player.controlBar.showChaptersMenu(this.player.controlBar.controls.chapters);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
|
|
148
|
+
case 'transcript-toggle':
|
|
149
|
+
// Toggle transcript
|
|
150
|
+
if (this.player.transcriptManager) {
|
|
151
|
+
this.player.transcriptManager.toggleTranscript();
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
|
|
156
|
+
default:
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
announceAction(action) {
|
|
162
|
+
if (!this.player.options.screenReaderAnnouncements) return;
|
|
163
|
+
|
|
164
|
+
let message = '';
|
|
165
|
+
|
|
166
|
+
switch (action) {
|
|
167
|
+
case 'play-pause':
|
|
168
|
+
message = this.player.state.playing ? 'Playing' : 'Paused';
|
|
169
|
+
break;
|
|
170
|
+
case 'volume-up':
|
|
171
|
+
message = `Volume ${Math.round(this.player.state.volume * 100)}%`;
|
|
172
|
+
break;
|
|
173
|
+
case 'volume-down':
|
|
174
|
+
message = `Volume ${Math.round(this.player.state.volume * 100)}%`;
|
|
175
|
+
break;
|
|
176
|
+
case 'mute':
|
|
177
|
+
message = this.player.state.muted ? 'Muted' : 'Unmuted';
|
|
178
|
+
break;
|
|
179
|
+
case 'fullscreen':
|
|
180
|
+
message = this.player.state.fullscreen ? 'Fullscreen' : 'Exit fullscreen';
|
|
181
|
+
break;
|
|
182
|
+
case 'captions':
|
|
183
|
+
message = this.player.state.captionsEnabled ? 'Captions on' : 'Captions off';
|
|
184
|
+
break;
|
|
185
|
+
case 'speed-up':
|
|
186
|
+
case 'speed-down':
|
|
187
|
+
message = `Speed ${this.player.state.playbackSpeed}x`;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (message) {
|
|
192
|
+
this.announce(message);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
announce(message, priority = 'polite') {
|
|
197
|
+
// Create or get announcement element
|
|
198
|
+
let announcer = document.getElementById('vidply-announcer');
|
|
199
|
+
|
|
200
|
+
if (!announcer) {
|
|
201
|
+
announcer = document.createElement('div');
|
|
202
|
+
announcer.id = 'vidply-announcer';
|
|
203
|
+
announcer.className = 'vidply-sr-only';
|
|
204
|
+
announcer.setAttribute('aria-live', priority);
|
|
205
|
+
announcer.setAttribute('aria-atomic', 'true');
|
|
206
|
+
announcer.style.cssText = `
|
|
207
|
+
position: absolute;
|
|
208
|
+
left: -10000px;
|
|
209
|
+
width: 1px;
|
|
210
|
+
height: 1px;
|
|
211
|
+
overflow: hidden;
|
|
212
|
+
`;
|
|
213
|
+
document.body.appendChild(announcer);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Clear and set new message
|
|
217
|
+
announcer.textContent = '';
|
|
218
|
+
setTimeout(() => {
|
|
219
|
+
announcer.textContent = message;
|
|
220
|
+
}, 100);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
updateShortcut(action, keys) {
|
|
224
|
+
if (Array.isArray(keys)) {
|
|
225
|
+
this.shortcuts[action] = keys;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
destroy() {
|
|
230
|
+
// Event listeners are automatically removed when the container is destroyed
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|