vidply 1.0.2 → 1.0.4

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.
@@ -2,249 +2,247 @@
2
2
  * Caption/Subtitle Manager
3
3
  */
4
4
 
5
- import { DOMUtils } from '../utils/DOMUtils.js';
6
- import { i18n } from '../i18n/i18n.js';
5
+ import {DOMUtils} from '../utils/DOMUtils.js';
6
+ import {i18n} from '../i18n/i18n.js';
7
7
 
8
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();
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();
26
17
  }
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
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
+ }
61
38
  });
62
-
63
- // Disable all tracks initially
64
- track.mode = 'hidden';
65
- }
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);
66
46
  }
67
- }
68
47
 
69
- attachEvents() {
70
- this.player.on('timeupdate', () => {
71
- this.updateCaptions();
72
- });
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
+ }
73
68
 
74
- this.player.on('captionschange', () => {
75
- this.updateStyles();
76
- });
77
- }
69
+ attachEvents() {
70
+ this.player.on('timeupdate', () => {
71
+ this.updateCaptions();
72
+ });
78
73
 
79
- enable(trackIndex = 0) {
80
- if (this.tracks.length === 0) {
81
- return;
74
+ this.player.on('captionschange', () => {
75
+ this.updateStyles();
76
+ });
82
77
  }
83
78
 
84
- // Disable current track
85
- if (this.currentTrack) {
86
- this.currentTrack.track.mode = 'hidden';
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
+ }
87
112
  }
88
113
 
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.element.style.display = 'block';
111
-
112
- this.player.emit('captionsenabled', selectedTrack);
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');
113
125
  }
114
- }
115
126
 
116
- disable() {
117
- if (this.currentTrack) {
118
- this.currentTrack.track.mode = 'hidden';
119
- this.currentTrack = null;
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
+ }
120
164
  }
121
165
 
122
- this.element.style.display = 'none';
123
- this.element.innerHTML = '';
124
- this.currentCue = null;
125
- this.player.state.captionsEnabled = false;
126
- this.player.emit('captionsdisabled');
127
- }
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>');
128
175
 
129
- updateCaptions() {
130
- if (!this.currentTrack) {
131
- return;
176
+ return text;
132
177
  }
133
-
134
- if (!this.currentTrack.track.activeCues) {
135
- return;
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
+ );
136
191
  }
137
192
 
138
- const activeCues = this.currentTrack.track.activeCues;
139
-
140
- if (activeCues.length > 0) {
141
- const cue = activeCues[0];
142
-
143
- // Only update if the cue has changed
144
- if (this.currentCue !== cue) {
145
- this.currentCue = cue;
146
-
147
- // Parse and display cue text
148
- let text = cue.text;
149
-
150
- // Handle VTT formatting
151
- text = this.parseVTTFormatting(text);
152
-
153
- this.element.innerHTML = DOMUtils.sanitizeHTML(text);
154
-
155
- // Make sure it's visible when there's content
156
- this.element.style.display = 'block';
157
-
158
- this.player.emit('captionchange', cue);
159
- }
160
- } else if (this.currentCue) {
161
- // Clear caption
162
- this.element.innerHTML = '';
163
- this.element.style.display = 'none';
164
- this.currentCue = null;
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;
165
199
  }
166
- }
167
-
168
- parseVTTFormatting(text) {
169
- // Basic VTT tag support
170
- text = text.replace(/<c[^>]*>(.*?)<\/c>/g, '<span class="caption-class">$1</span>');
171
- text = text.replace(/<b>(.*?)<\/b>/g, '<strong>$1</strong>');
172
- text = text.replace(/<i>(.*?)<\/i>/g, '<em>$1</em>');
173
- text = text.replace(/<u>(.*?)<\/u>/g, '<u>$1</u>');
174
-
175
- // Voice tags
176
- text = text.replace(/<v\s+([^>]+)>(.*?)<\/v>/g, '<span class="caption-voice" data-voice="$1">$2</span>');
177
-
178
- return text;
179
- }
180
-
181
- updateStyles() {
182
- if (!this.element) return;
183
-
184
- const options = this.player.options;
185
-
186
- this.element.style.fontSize = options.captionsFontSize;
187
- this.element.style.fontFamily = options.captionsFontFamily;
188
- this.element.style.color = options.captionsColor;
189
- this.element.style.backgroundColor = this.hexToRgba(
190
- options.captionsBackgroundColor,
191
- options.captionsOpacity
192
- );
193
- }
194
-
195
- hexToRgba(hex, alpha) {
196
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
197
- if (result) {
198
- return `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${alpha})`;
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');
199
222
  }
200
- return hex;
201
- }
202
-
203
- setCaptionStyle(property, value) {
204
- switch (property) {
205
- case 'fontSize':
206
- this.player.options.captionsFontSize = value;
207
- break;
208
- case 'fontFamily':
209
- this.player.options.captionsFontFamily = value;
210
- break;
211
- case 'color':
212
- this.player.options.captionsColor = value;
213
- break;
214
- case 'backgroundColor':
215
- this.player.options.captionsBackgroundColor = value;
216
- break;
217
- case 'opacity':
218
- this.player.options.captionsOpacity = value;
219
- break;
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
+ }));
220
231
  }
221
-
222
- this.updateStyles();
223
- this.player.emit('captionschange');
224
- }
225
-
226
- getAvailableTracks() {
227
- return this.tracks.map((t, index) => ({
228
- index,
229
- language: t.language,
230
- label: t.label || t.language,
231
- kind: t.kind
232
- }));
233
- }
234
-
235
- switchTrack(trackIndex) {
236
- if (trackIndex >= 0 && trackIndex < this.tracks.length) {
237
- this.disable();
238
- this.enable(trackIndex);
232
+
233
+ switchTrack(trackIndex) {
234
+ if (trackIndex >= 0 && trackIndex < this.tracks.length) {
235
+ this.disable();
236
+ this.enable(trackIndex);
237
+ }
239
238
  }
240
- }
241
239
 
242
- destroy() {
243
- this.disable();
244
-
245
- if (this.element && this.element.parentNode) {
246
- this.element.parentNode.removeChild(this.element);
240
+ destroy() {
241
+ this.disable();
242
+
243
+ if (this.element && this.element.parentNode) {
244
+ this.element.parentNode.removeChild(this.element);
245
+ }
247
246
  }
248
- }
249
247
  }
250
248