myetv-player 1.2.0 → 1.4.0
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/css/myetv-player.css +242 -168
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +638 -203
- package/dist/myetv-player.min.js +548 -170
- package/package.json +35 -16
- package/plugins/twitch/myetv-player-twitch-plugin.js +125 -11
- package/plugins/vimeo/myetv-player-vimeo.js +80 -49
- package/plugins/youtube/README.md +5 -2
- package/plugins/youtube/myetv-player-youtube-plugin.js +766 -6
- package/.github/workflows/codeql.yml +0 -100
- package/.github/workflows/npm-publish.yml +0 -30
- package/SECURITY.md +0 -50
- package/build.js +0 -195
- package/scss/README.md +0 -161
- package/scss/_audio-player.scss +0 -21
- package/scss/_base.scss +0 -116
- package/scss/_controls.scss +0 -204
- package/scss/_loading.scss +0 -111
- package/scss/_menus.scss +0 -432
- package/scss/_mixins.scss +0 -112
- package/scss/_poster.scss +0 -8
- package/scss/_progress-bar.scss +0 -319
- package/scss/_resolution.scss +0 -68
- package/scss/_responsive.scss +0 -1368
- package/scss/_themes.scss +0 -30
- package/scss/_title-overlay.scss +0 -60
- package/scss/_tooltips.scss +0 -7
- package/scss/_variables.scss +0 -49
- package/scss/_video.scss +0 -221
- package/scss/_volume.scss +0 -122
- package/scss/_watermark.scss +0 -128
- package/scss/myetv-player.scss +0 -51
- package/scss/package.json +0 -16
- package/src/README.md +0 -560
- package/src/chapters.js +0 -521
- package/src/controls.js +0 -1242
- package/src/core.js +0 -1922
- package/src/events.js +0 -537
- package/src/fullscreen.js +0 -82
- package/src/i18n.js +0 -374
- package/src/playlist.js +0 -177
- package/src/plugins.js +0 -384
- package/src/quality.js +0 -963
- package/src/streaming.js +0 -346
- package/src/subtitles.js +0 -524
- package/src/utils.js +0 -65
- package/src/watermark.js +0 -246
package/src/chapters.js
DELETED
|
@@ -1,521 +0,0 @@
|
|
|
1
|
-
/* Chapters Module for MYETV Video Player
|
|
2
|
-
* Chapter markers with tooltips and thumbnails on timeline
|
|
3
|
-
* Created by https://www.myetv.tv https://oskarcosimo.com
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Initialize chapter markers system
|
|
8
|
-
* Chapters can be defined in initialization options as:
|
|
9
|
-
* - JSON array: chapters: [{time: 0, title: "Intro", image: "url"}, ...]
|
|
10
|
-
* - String format: chapters: "0:00:00|Intro|image.jpg,0:02:30|Chapter 2|image2.jpg"
|
|
11
|
-
*/
|
|
12
|
-
initializeChapters() {
|
|
13
|
-
if (!this.options.chapters || !Array.isArray(this.options.chapters) && typeof this.options.chapters !== 'string') {
|
|
14
|
-
if (this.options.debug) console.log('📚 No chapters defined');
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Parse chapters from different formats
|
|
19
|
-
this.chapters = this.parseChapters(this.options.chapters);
|
|
20
|
-
|
|
21
|
-
if (this.chapters.length === 0) {
|
|
22
|
-
if (this.options.debug) console.warn('📚 Chapters defined but empty after parsing');
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Sort chapters by time
|
|
27
|
-
this.chapters.sort((a, b) => a.time - b.time);
|
|
28
|
-
|
|
29
|
-
if (this.options.debug) console.log('📚 Chapters initialized:', this.chapters);
|
|
30
|
-
|
|
31
|
-
// Create chapter markers on the progress bar
|
|
32
|
-
this.createChapterMarkers();
|
|
33
|
-
|
|
34
|
-
// Create chapter tooltip
|
|
35
|
-
this.createChapterTooltip();
|
|
36
|
-
|
|
37
|
-
// Bind chapter events
|
|
38
|
-
this.bindChapterEvents();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Parse chapters from various input formats
|
|
43
|
-
* @param {Array|String} chaptersInput - Chapters data
|
|
44
|
-
* @returns {Array} Normalized chapters array
|
|
45
|
-
*/
|
|
46
|
-
parseChapters(chaptersInput) {
|
|
47
|
-
// If already array of objects, validate and return
|
|
48
|
-
if (Array.isArray(chaptersInput)) {
|
|
49
|
-
return chaptersInput.map(chapter => this.normalizeChapter(chapter)).filter(c => c !== null);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// If string format, parse it
|
|
53
|
-
// Format: "time|title|image,time|title|image,..."
|
|
54
|
-
// Example: "0:00:00|Introduction|intro.jpg,0:02:30|Chapter 2|chapter2.jpg"
|
|
55
|
-
if (typeof chaptersInput === 'string') {
|
|
56
|
-
const chapterStrings = chaptersInput.split(',').map(s => s.trim());
|
|
57
|
-
const parsedChapters = [];
|
|
58
|
-
|
|
59
|
-
for (const chapterStr of chapterStrings) {
|
|
60
|
-
const parts = chapterStr.split('|').map(p => p.trim());
|
|
61
|
-
if (parts.length < 2) {
|
|
62
|
-
if (this.options.debug) console.warn('📚 Invalid chapter format:', chapterStr);
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const chapter = {
|
|
67
|
-
time: this.parseTimeToSeconds(parts[0]),
|
|
68
|
-
title: parts[1],
|
|
69
|
-
image: parts[2] || null
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const normalized = this.normalizeChapter(chapter);
|
|
73
|
-
if (normalized) {
|
|
74
|
-
parsedChapters.push(normalized);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return parsedChapters;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (this.options.debug) console.warn('📚 Invalid chapters format');
|
|
82
|
-
return [];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Normalize and validate a single chapter object
|
|
87
|
-
* @param {Object} chapter - Chapter object
|
|
88
|
-
* @returns {Object|null} Normalized chapter or null if invalid
|
|
89
|
-
*/
|
|
90
|
-
normalizeChapter(chapter) {
|
|
91
|
-
if (!chapter || typeof chapter !== 'object') {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Ensure required fields
|
|
96
|
-
if (!chapter.hasOwnProperty('time') || !chapter.hasOwnProperty('title')) {
|
|
97
|
-
if (this.options.debug) console.warn('📚 Chapter missing required fields:', chapter);
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Convert time to seconds if string
|
|
102
|
-
let timeInSeconds = chapter.time;
|
|
103
|
-
if (typeof timeInSeconds === 'string') {
|
|
104
|
-
timeInSeconds = this.parseTimeToSeconds(timeInSeconds);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (typeof timeInSeconds !== 'number' || timeInSeconds < 0) {
|
|
108
|
-
if (this.options.debug) console.warn('📚 Invalid chapter time:', chapter.time);
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
time: timeInSeconds,
|
|
114
|
-
title: String(chapter.title),
|
|
115
|
-
image: chapter.image || null,
|
|
116
|
-
color: chapter.color || null // Optional custom color
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Parse time string to seconds
|
|
122
|
-
* Supports formats: "HH:MM:SS", "MM:SS", "SS"
|
|
123
|
-
* @param {String} timeStr - Time string
|
|
124
|
-
* @returns {Number} Time in seconds
|
|
125
|
-
*/
|
|
126
|
-
parseTimeToSeconds(timeStr) {
|
|
127
|
-
if (typeof timeStr === 'number') {
|
|
128
|
-
return timeStr;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const parts = String(timeStr).split(':').map(p => parseInt(p.trim(), 10));
|
|
132
|
-
|
|
133
|
-
if (parts.length === 3) {
|
|
134
|
-
// HH:MM:SS
|
|
135
|
-
return parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
136
|
-
} else if (parts.length === 2) {
|
|
137
|
-
// MM:SS
|
|
138
|
-
return parts[0] * 60 + parts[1];
|
|
139
|
-
} else if (parts.length === 1) {
|
|
140
|
-
// SS
|
|
141
|
-
return parts[0];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return 0;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Create visual chapter markers on the progress bar
|
|
149
|
-
*/
|
|
150
|
-
createChapterMarkers() {
|
|
151
|
-
if (!this.progressContainer || !this.video || !this.chapters) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Create container for chapter markers
|
|
156
|
-
const markersContainer = document.createElement('div');
|
|
157
|
-
markersContainer.className = 'chapter-markers-container';
|
|
158
|
-
|
|
159
|
-
this.chapters.forEach((chapter, index) => {
|
|
160
|
-
const marker = document.createElement('div');
|
|
161
|
-
marker.className = 'chapter-marker';
|
|
162
|
-
marker.setAttribute('data-chapter-index', index);
|
|
163
|
-
marker.setAttribute('data-chapter-time', chapter.time);
|
|
164
|
-
marker.setAttribute('data-chapter-title', chapter.title);
|
|
165
|
-
|
|
166
|
-
// Set custom color if provided
|
|
167
|
-
if (chapter.color) {
|
|
168
|
-
marker.style.backgroundColor = chapter.color;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
markersContainer.appendChild(marker);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Insert markers container into progress container
|
|
175
|
-
this.progressContainer.appendChild(markersContainer);
|
|
176
|
-
this.chapterMarkersContainer = markersContainer;
|
|
177
|
-
|
|
178
|
-
// Update marker positions when video duration is known
|
|
179
|
-
if (this.video.duration && !isNaN(this.video.duration)) {
|
|
180
|
-
this.updateChapterMarkerPositions();
|
|
181
|
-
} else {
|
|
182
|
-
// Wait for metadata to be loaded
|
|
183
|
-
const loadedMetadataHandler = () => {
|
|
184
|
-
this.updateChapterMarkerPositions();
|
|
185
|
-
this.video.removeEventListener('loadedmetadata', loadedMetadataHandler);
|
|
186
|
-
};
|
|
187
|
-
this.video.addEventListener('loadedmetadata', loadedMetadataHandler);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (this.options.debug) console.log('📚 Chapter markers created on timeline');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Update chapter marker positions based on video duration
|
|
195
|
-
*/
|
|
196
|
-
updateChapterMarkerPositions() {
|
|
197
|
-
if (!this.video || !this.video.duration || !this.chapterMarkersContainer) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const markers = this.chapterMarkersContainer.querySelectorAll('.chapter-marker');
|
|
202
|
-
const duration = this.video.duration;
|
|
203
|
-
|
|
204
|
-
markers.forEach((marker, index) => {
|
|
205
|
-
if (this.chapters[index]) {
|
|
206
|
-
const percentage = (this.chapters[index].time / duration) * 100;
|
|
207
|
-
marker.style.left = percentage + '%';
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
if (this.options.debug) console.log('📚 Chapter marker positions updated');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Create chapter tooltip
|
|
216
|
-
*/
|
|
217
|
-
createChapterTooltip() {
|
|
218
|
-
if (!this.progressContainer) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const tooltip = document.createElement('div');
|
|
223
|
-
tooltip.className = 'chapter-tooltip';
|
|
224
|
-
tooltip.style.opacity = '0';
|
|
225
|
-
tooltip.style.visibility = 'hidden';
|
|
226
|
-
|
|
227
|
-
// Tooltip content structure
|
|
228
|
-
tooltip.innerHTML = `
|
|
229
|
-
<div class="chapter-tooltip-image"></div>
|
|
230
|
-
<div class="chapter-tooltip-title"></div>
|
|
231
|
-
<div class="chapter-tooltip-time"></div>
|
|
232
|
-
`;
|
|
233
|
-
|
|
234
|
-
this.progressContainer.appendChild(tooltip);
|
|
235
|
-
this.chapterTooltip = tooltip;
|
|
236
|
-
|
|
237
|
-
if (this.options.debug) console.log('📚 Chapter tooltip created');
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Bind chapter-related events
|
|
242
|
-
*/
|
|
243
|
-
bindChapterEvents() {
|
|
244
|
-
if (!this.chapterMarkersContainer || !this.chapterTooltip) {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Hover on chapter markers
|
|
249
|
-
const markers = this.chapterMarkersContainer.querySelectorAll('.chapter-marker');
|
|
250
|
-
|
|
251
|
-
markers.forEach((marker, index) => {
|
|
252
|
-
marker.addEventListener('mouseenter', (e) => {
|
|
253
|
-
this.showChapterTooltip(index, e);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
marker.addEventListener('mousemove', (e) => {
|
|
257
|
-
this.updateChapterTooltipPosition(e);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
marker.addEventListener('mouseleave', () => {
|
|
261
|
-
this.hideChapterTooltip();
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// Click to jump to chapter
|
|
265
|
-
marker.addEventListener('click', (e) => {
|
|
266
|
-
e.stopPropagation();
|
|
267
|
-
this.jumpToChapter(index);
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// Update active chapter during playback
|
|
272
|
-
if (this.video) {
|
|
273
|
-
this.video.addEventListener('timeupdate', () => {
|
|
274
|
-
this.updateActiveChapter();
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (this.options.debug) console.log('📚 Chapter events bound');
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Show chapter tooltip
|
|
283
|
-
* @param {Number} chapterIndex - Index of the chapter
|
|
284
|
-
* @param {MouseEvent} e - Mouse event
|
|
285
|
-
*/
|
|
286
|
-
showChapterTooltip(chapterIndex, e) {
|
|
287
|
-
if (!this.chapterTooltip || !this.chapters[chapterIndex]) {
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const chapter = this.chapters[chapterIndex];
|
|
292
|
-
|
|
293
|
-
// Update tooltip content
|
|
294
|
-
const imageEl = this.chapterTooltip.querySelector('.chapter-tooltip-image');
|
|
295
|
-
const titleEl = this.chapterTooltip.querySelector('.chapter-tooltip-title');
|
|
296
|
-
const timeEl = this.chapterTooltip.querySelector('.chapter-tooltip-time');
|
|
297
|
-
|
|
298
|
-
// Set image
|
|
299
|
-
if (chapter.image) {
|
|
300
|
-
imageEl.style.display = 'block';
|
|
301
|
-
imageEl.style.backgroundImage = `url(${chapter.image})`;
|
|
302
|
-
} else {
|
|
303
|
-
imageEl.style.display = 'none';
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Set title
|
|
307
|
-
titleEl.textContent = chapter.title;
|
|
308
|
-
|
|
309
|
-
// Set time
|
|
310
|
-
timeEl.textContent = this.formatTime(chapter.time);
|
|
311
|
-
|
|
312
|
-
// Show tooltip
|
|
313
|
-
this.chapterTooltip.style.opacity = '1';
|
|
314
|
-
this.chapterTooltip.style.visibility = 'visible';
|
|
315
|
-
|
|
316
|
-
// Position tooltip
|
|
317
|
-
this.updateChapterTooltipPosition(e);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Update chapter tooltip position
|
|
322
|
-
* @param {MouseEvent} e - Mouse event
|
|
323
|
-
*/
|
|
324
|
-
updateChapterTooltipPosition(e) {
|
|
325
|
-
if (!this.chapterTooltip || !this.progressContainer) {
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const rect = this.progressContainer.getBoundingClientRect();
|
|
330
|
-
const tooltipRect = this.chapterTooltip.getBoundingClientRect();
|
|
331
|
-
const mouseX = e.clientX - rect.left;
|
|
332
|
-
|
|
333
|
-
// Calculate position
|
|
334
|
-
let leftPosition = mouseX;
|
|
335
|
-
const tooltipWidth = tooltipRect.width || 200;
|
|
336
|
-
const containerWidth = rect.width;
|
|
337
|
-
|
|
338
|
-
// Keep tooltip within bounds
|
|
339
|
-
leftPosition = Math.max(tooltipWidth / 2, Math.min(containerWidth - tooltipWidth / 2, mouseX));
|
|
340
|
-
|
|
341
|
-
this.chapterTooltip.style.left = leftPosition + 'px';
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Hide chapter tooltip
|
|
346
|
-
*/
|
|
347
|
-
hideChapterTooltip() {
|
|
348
|
-
if (!this.chapterTooltip) {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
this.chapterTooltip.style.opacity = '0';
|
|
353
|
-
this.chapterTooltip.style.visibility = 'hidden';
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Jump to specific chapter
|
|
358
|
-
* @param {Number} chapterIndex - Index of the chapter
|
|
359
|
-
*/
|
|
360
|
-
jumpToChapter(chapterIndex) {
|
|
361
|
-
if (!this.video || !this.chapters[chapterIndex]) {
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const chapter = this.chapters[chapterIndex];
|
|
366
|
-
this.video.currentTime = chapter.time;
|
|
367
|
-
|
|
368
|
-
if (this.options.debug) console.log(`📚 Jumped to chapter: ${chapter.title} at ${chapter.time}s`);
|
|
369
|
-
|
|
370
|
-
// Trigger custom event
|
|
371
|
-
this.triggerEvent('chapterchange', {
|
|
372
|
-
chapterIndex: chapterIndex,
|
|
373
|
-
chapter: chapter,
|
|
374
|
-
currentTime: this.video.currentTime
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Update active chapter marker during playback
|
|
380
|
-
*/
|
|
381
|
-
updateActiveChapter() {
|
|
382
|
-
if (!this.video || !this.chapterMarkersContainer || !this.chapters) {
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const currentTime = this.video.currentTime;
|
|
387
|
-
const markers = this.chapterMarkersContainer.querySelectorAll('.chapter-marker');
|
|
388
|
-
|
|
389
|
-
// Find current chapter
|
|
390
|
-
let currentChapterIndex = -1;
|
|
391
|
-
for (let i = this.chapters.length - 1; i >= 0; i--) {
|
|
392
|
-
if (currentTime >= this.chapters[i].time) {
|
|
393
|
-
currentChapterIndex = i;
|
|
394
|
-
break;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Update active state
|
|
399
|
-
markers.forEach((marker, index) => {
|
|
400
|
-
if (index === currentChapterIndex) {
|
|
401
|
-
marker.classList.add('active');
|
|
402
|
-
} else {
|
|
403
|
-
marker.classList.remove('active');
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Get current chapter info
|
|
410
|
-
* @returns {Object|null} Current chapter object or null
|
|
411
|
-
*/
|
|
412
|
-
getCurrentChapter() {
|
|
413
|
-
if (!this.video || !this.chapters || this.chapters.length === 0) {
|
|
414
|
-
return null;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const currentTime = this.video.currentTime;
|
|
418
|
-
|
|
419
|
-
for (let i = this.chapters.length - 1; i >= 0; i--) {
|
|
420
|
-
if (currentTime >= this.chapters[i].time) {
|
|
421
|
-
return {
|
|
422
|
-
index: i,
|
|
423
|
-
chapter: this.chapters[i]
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
return null;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Get all chapters
|
|
433
|
-
* @returns {Array} Array of chapter objects
|
|
434
|
-
*/
|
|
435
|
-
getChapters() {
|
|
436
|
-
return this.chapters || [];
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Get chapter by index
|
|
441
|
-
* @param {Number} index - Chapter index
|
|
442
|
-
* @returns {Object|null} Chapter object or null
|
|
443
|
-
*/
|
|
444
|
-
getChapterByIndex(index) {
|
|
445
|
-
if (!this.chapters || index < 0 || index >= this.chapters.length) {
|
|
446
|
-
return null;
|
|
447
|
-
}
|
|
448
|
-
return this.chapters[index];
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Navigate to next chapter
|
|
453
|
-
* @returns {Boolean} True if navigated successfully
|
|
454
|
-
*/
|
|
455
|
-
nextChapter() {
|
|
456
|
-
const current = this.getCurrentChapter();
|
|
457
|
-
if (!current || current.index >= this.chapters.length - 1) {
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
this.jumpToChapter(current.index + 1);
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Navigate to previous chapter
|
|
467
|
-
* @returns {Boolean} True if navigated successfully
|
|
468
|
-
*/
|
|
469
|
-
previousChapter() {
|
|
470
|
-
const current = this.getCurrentChapter();
|
|
471
|
-
if (!current) {
|
|
472
|
-
return false;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// If we're more than 3 seconds into current chapter, go to its start
|
|
476
|
-
if (this.video && this.video.currentTime - current.chapter.time > 3) {
|
|
477
|
-
this.jumpToChapter(current.index);
|
|
478
|
-
return true;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Otherwise go to previous chapter
|
|
482
|
-
if (current.index > 0) {
|
|
483
|
-
this.jumpToChapter(current.index - 1);
|
|
484
|
-
return true;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
return false;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Set chapters dynamically
|
|
492
|
-
* @param {Array|String} chapters - Chapters data
|
|
493
|
-
*/
|
|
494
|
-
setChapters(chapters) {
|
|
495
|
-
// Remove existing chapter markers
|
|
496
|
-
if (this.chapterMarkersContainer) {
|
|
497
|
-
this.chapterMarkersContainer.remove();
|
|
498
|
-
this.chapterMarkersContainer = null;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
if (this.chapterTooltip) {
|
|
502
|
-
this.chapterTooltip.remove();
|
|
503
|
-
this.chapterTooltip = null;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Set new chapters
|
|
507
|
-
this.options.chapters = chapters;
|
|
508
|
-
this.chapters = [];
|
|
509
|
-
|
|
510
|
-
// Re-initialize chapters
|
|
511
|
-
this.initializeChapters();
|
|
512
|
-
|
|
513
|
-
if (this.options.debug) console.log('📚 Chapters updated dynamically');
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Clear all chapters
|
|
518
|
-
*/
|
|
519
|
-
clearChapters() {
|
|
520
|
-
this.setChapters(null);
|
|
521
|
-
}
|