unified-video-framework
Version:
Cross-platform video player framework supporting iOS, Android, Web, Smart TVs (Samsung/LG), Roku, and more
173 lines • 6.3 kB
JavaScript
export class ChapterManager {
constructor(config = {}) {
this.chapters = [];
this.segments = [];
this.currentChapter = null;
this.activeSegments = new Set();
this.lastProcessedTime = -1;
this.eventHandlers = new Map();
this.config = { ...config };
this.chapters = config.chapters || [];
this.segments = config.segments || [];
this.eventHandlers.set('chapterchange', []);
this.eventHandlers.set('segmententered', []);
this.eventHandlers.set('segmentexited', []);
this.eventHandlers.set('segmentskipped', []);
}
updateConfig(config) {
this.config = { ...this.config, ...config };
this.chapters = config.chapters || this.chapters;
this.segments = config.segments || this.segments;
}
async loadChapterData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch chapter data: ${response.status}`);
}
const data = await response.json();
if (data.chapters) {
this.chapters = data.chapters;
}
if (data.segments) {
this.segments = data.segments;
}
}
catch (error) {
console.error('Error loading chapter data:', error);
throw error;
}
}
async initialize() {
if (this.config.dataUrl) {
await this.loadChapterData(this.config.dataUrl);
}
this.chapters.sort((a, b) => a.startTime - b.startTime);
this.segments.sort((a, b) => a.startTime - b.startTime);
}
processTimeUpdate(currentTime) {
if (Math.abs(currentTime - this.lastProcessedTime) < 0.1) {
return;
}
this.lastProcessedTime = currentTime;
this.processChapterChange(currentTime);
this.processSegments(currentTime);
}
processChapterChange(currentTime) {
const newChapter = this.getCurrentChapter(currentTime);
if (newChapter !== this.currentChapter) {
this.currentChapter = newChapter;
this.emit('chapterchange', newChapter);
if (this.config.onChapterChange) {
this.config.onChapterChange(newChapter);
}
}
}
processSegments(currentTime) {
const currentSegments = this.segments.filter(segment => currentTime >= segment.startTime && currentTime <= segment.endTime);
for (const segment of currentSegments) {
if (!this.activeSegments.has(segment.id)) {
this.activeSegments.add(segment.id);
this.emit('segmententered', segment);
if (this.config.onSegmentEntered) {
this.config.onSegmentEntered(segment);
}
if (this.config.autoSkip && segment.action === 'skip') {
this.skipSegment(segment);
}
}
}
const currentSegmentIds = new Set(currentSegments.map(s => s.id));
for (const activeSegmentId of this.activeSegments) {
if (!currentSegmentIds.has(activeSegmentId)) {
const segment = this.segments.find(s => s.id === activeSegmentId);
if (segment) {
this.activeSegments.delete(activeSegmentId);
this.emit('segmentexited', segment);
if (this.config.onSegmentExited) {
this.config.onSegmentExited(segment);
}
}
}
}
}
getCurrentChapter(currentTime) {
return this.chapters.find(chapter => currentTime >= chapter.startTime && currentTime <= chapter.endTime) || null;
}
getChapters() {
return [...this.chapters];
}
getSegments() {
return [...this.segments];
}
getSegmentsAtTime(currentTime) {
return this.segments.filter(segment => currentTime >= segment.startTime && currentTime <= segment.endTime);
}
skipSegment(segment) {
this.emit('segmentskipped', segment);
if (this.config.onSegmentSkipped) {
this.config.onSegmentSkipped(segment);
}
}
seekToChapter(chapterId) {
const chapter = this.chapters.find(c => c.id === chapterId);
if (chapter) {
return chapter;
}
return null;
}
getNextChapter(currentTime) {
return this.chapters.find(chapter => chapter.startTime > currentTime) || null;
}
getPreviousChapter(currentTime) {
const previousChapters = this.chapters
.filter(chapter => chapter.startTime < currentTime)
.sort((a, b) => b.startTime - a.startTime);
return previousChapters[0] || null;
}
on(event, handler) {
const handlers = this.eventHandlers.get(event) || [];
handlers.push(handler);
this.eventHandlers.set(event, handlers);
}
off(event, handler) {
const handlers = this.eventHandlers.get(event) || [];
if (handler) {
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
}
else {
handlers.length = 0;
}
this.eventHandlers.set(event, handlers);
}
emit(event, data) {
const handlers = this.eventHandlers.get(event) || [];
handlers.forEach(handler => {
try {
handler(data);
}
catch (error) {
console.error(`Error in chapter manager event handler for ${event}:`, error);
}
});
}
reset() {
this.currentChapter = null;
this.activeSegments.clear();
this.lastProcessedTime = -1;
}
destroy() {
this.reset();
this.eventHandlers.clear();
}
getCurrentChapterInfo() {
return this.currentChapter;
}
isEnabled() {
return this.config.enabled !== false;
}
}
//# sourceMappingURL=chapter-manager.js.map