UNPKG

unified-video-framework

Version:

Cross-platform video player framework supporting iOS, Android, Web, Smart TVs (Samsung/LG), Roku, and more

213 lines 7.51 kB
import { DEFAULT_SKIP_LABELS } from './types/ChapterTypes.js'; export class SkipButtonController { constructor(playerContainer, config, onSkip, onButtonShown, onButtonHidden) { this.playerContainer = playerContainer; this.config = config; this.onSkip = onSkip; this.onButtonShown = onButtonShown; this.onButtonHidden = onButtonHidden; this.skipButton = null; this.currentSegment = null; this.autoSkipTimeout = null; this.hideTimeout = null; this.countdownInterval = null; this.state = { visible: false, segment: null, position: config.skipButtonPosition || 'bottom-right' }; } showSkipButton(segment, currentTime) { if (!this.config.userPreferences?.showSkipButtons) { return; } if (segment.showSkipButton === false) { return; } this.currentSegment = segment; this.state.segment = segment; if (!this.skipButton) { this.skipButton = this.createSkipButton(); this.playerContainer.appendChild(this.skipButton); } this.updateSkipButton(segment); this.showButton(); this.handleAutoSkip(segment, currentTime); this.handleAutoHide(); this.onButtonShown(segment); } hideSkipButton(reason = 'manual') { if (!this.skipButton || !this.state.visible) { return; } this.hideButton(); this.clearTimeouts(); if (this.currentSegment) { this.onButtonHidden(this.currentSegment, reason); } this.state.visible = false; this.state.segment = null; this.currentSegment = null; } updatePosition(position) { this.state.position = position; if (this.skipButton) { this.applyPositionStyles(this.skipButton, position); } } isVisible() { return this.state.visible; } getState() { return { ...this.state }; } destroy() { this.clearTimeouts(); if (this.skipButton) { this.skipButton.remove(); this.skipButton = null; } this.currentSegment = null; this.state.visible = false; this.state.segment = null; } createSkipButton() { const button = document.createElement('button'); button.className = 'uvf-skip-button'; button.setAttribute('type', 'button'); button.setAttribute('aria-label', 'Skip segment'); this.applyPositionStyles(button, this.state.position); button.addEventListener('click', () => { if (this.currentSegment) { this.onSkip(this.currentSegment); this.hideSkipButton('user-action'); } }); if (this.config.customStyles?.skipButton) { Object.assign(button.style, this.config.customStyles.skipButton); } return button; } updateSkipButton(segment) { if (!this.skipButton) return; const skipLabel = segment.skipLabel || DEFAULT_SKIP_LABELS[segment.type]; this.skipButton.textContent = skipLabel; this.skipButton.setAttribute('aria-label', `${skipLabel} - ${segment.title || segment.type}`); this.skipButton.className = `uvf-skip-button uvf-skip-${segment.type}`; this.skipButton.classList.add(`uvf-skip-button-${this.state.position}`); } applyPositionStyles(button, position) { button.classList.remove('uvf-skip-button-bottom-right', 'uvf-skip-button-bottom-left', 'uvf-skip-button-top-right', 'uvf-skip-button-top-left'); button.classList.add(`uvf-skip-button-${position}`); const styles = { position: 'absolute', zIndex: '1000' }; switch (position) { case 'bottom-right': Object.assign(styles, { bottom: '100px', right: '30px' }); break; case 'bottom-left': Object.assign(styles, { bottom: '100px', left: '30px' }); break; case 'top-right': Object.assign(styles, { top: '30px', right: '30px' }); break; case 'top-left': Object.assign(styles, { top: '30px', left: '30px' }); break; } Object.assign(button.style, styles); } showButton() { if (!this.skipButton) return; this.skipButton.classList.add('visible'); this.state.visible = true; } hideButton() { if (!this.skipButton) return; this.skipButton.classList.remove('visible'); this.skipButton.classList.remove('auto-skip', 'countdown'); } handleAutoSkip(segment, currentTime) { if (!segment.autoSkip || !segment.autoSkipDelay) { return; } const preferences = this.config.userPreferences; const shouldAutoSkip = ((segment.type === 'intro' && preferences?.autoSkipIntro) || (segment.type === 'recap' && preferences?.autoSkipRecap) || (segment.type === 'credits' && preferences?.autoSkipCredits)); if (!shouldAutoSkip) { return; } this.skipButton?.classList.add('auto-skip'); this.startAutoSkipCountdown(segment, segment.autoSkipDelay); } startAutoSkipCountdown(segment, delay) { if (!this.skipButton) return; let remainingTime = delay; this.state.autoSkipCountdown = remainingTime; const originalText = this.skipButton.textContent || ''; this.skipButton.classList.add('countdown'); this.countdownInterval = setInterval(() => { remainingTime -= 1; this.state.autoSkipCountdown = remainingTime; if (this.skipButton) { this.skipButton.textContent = `${originalText} (${remainingTime})`; } if (remainingTime <= 0) { this.clearTimeouts(); if (this.currentSegment) { this.onSkip(this.currentSegment); this.hideSkipButton('timeout'); } } }, 1000); this.autoSkipTimeout = setTimeout(() => { if (this.currentSegment) { this.onSkip(this.currentSegment); this.hideSkipButton('timeout'); } }, delay * 1000); } handleAutoHide() { if (!this.config.autoHide || !this.config.autoHideDelay) { return; } this.hideTimeout = setTimeout(() => { this.hideSkipButton('timeout'); }, this.config.autoHideDelay); } clearTimeouts() { if (this.autoSkipTimeout) { clearTimeout(this.autoSkipTimeout); this.autoSkipTimeout = null; } if (this.hideTimeout) { clearTimeout(this.hideTimeout); this.hideTimeout = null; } if (this.countdownInterval) { clearInterval(this.countdownInterval); this.countdownInterval = null; } this.state.autoSkipCountdown = undefined; } } //# sourceMappingURL=SkipButtonController.js.map