UNPKG

unified-video-framework

Version:

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

284 lines 11.8 kB
export class CreditsButtonController { constructor(playerContainer, config, callbacks) { this.playerContainer = playerContainer; this.config = config; this.callbacks = callbacks; this.buttonsContainer = null; this.watchCreditsButton = null; this.nextEpisodeButton = null; this.currentSegment = null; this.autoRedirectTimeout = null; this.countdownInterval = null; this.isVisible = false; this.userWatchingCredits = false; this.DEFAULT_WATCH_CREDITS_LABEL = 'Watch Credits'; this.DEFAULT_NEXT_EPISODE_LABEL = 'Play Next'; this.DEFAULT_AUTO_REDIRECT_DELAY = 10; } showCreditsButtons(segment, currentTime) { if (!segment.nextEpisodeUrl) { return; } this.currentSegment = segment; this.userWatchingCredits = false; if (!this.buttonsContainer) { this.createCreditsButtons(); } this.updateButtonLabels(segment); this.showButtons(); const delay = segment.autoSkipDelay || this.DEFAULT_AUTO_REDIRECT_DELAY; this.startAutoRedirectCountdown(segment, delay); this.callbacks.onButtonsShown(segment); } hideCreditsButtons(reason = 'manual') { if (!this.buttonsContainer || !this.isVisible) { return; } if (this.currentSegment) { this.callbacks.onButtonsHidden(this.currentSegment, reason); } this.hideButtons(); this.clearTimeouts(); this.isVisible = false; this.currentSegment = null; } isUserWatchingCredits() { return this.userWatchingCredits; } getCurrentSegment() { return this.currentSegment; } isButtonsVisible() { return this.isVisible; } destroy() { this.clearTimeouts(); if (this.buttonsContainer) { this.buttonsContainer.remove(); this.buttonsContainer = null; } this.watchCreditsButton = null; this.nextEpisodeButton = null; this.currentSegment = null; this.isVisible = false; this.userWatchingCredits = false; } createCreditsButtons() { const segment = this.currentSegment; const style = segment?.creditsButtonStyle; const layout = style?.layout || 'vertical'; const position = style?.position || 'bottom-right'; this.buttonsContainer = document.createElement('div'); this.buttonsContainer.className = 'uvf-credits-buttons'; this.buttonsContainer.setAttribute('role', 'group'); this.buttonsContainer.setAttribute('aria-label', 'Credits navigation'); const containerStyles = { position: 'absolute', display: 'flex', flexDirection: layout === 'horizontal' ? 'row' : 'column', gap: '10px', zIndex: '1000', opacity: '0', transition: 'opacity 0.3s ease-in-out' }; this.applyContainerPosition(containerStyles, position); Object.assign(this.buttonsContainer.style, containerStyles); this.watchCreditsButton = document.createElement('button'); this.watchCreditsButton.className = 'uvf-watch-credits-button'; this.watchCreditsButton.setAttribute('type', 'button'); this.watchCreditsButton.setAttribute('aria-label', 'Watch credits'); this.watchCreditsButton.addEventListener('click', () => this.handleWatchCreditsClick()); this.nextEpisodeButton = document.createElement('button'); this.nextEpisodeButton.className = 'uvf-next-episode-button'; this.nextEpisodeButton.setAttribute('type', 'button'); this.nextEpisodeButton.setAttribute('aria-label', 'Play next episode'); this.nextEpisodeButton.addEventListener('click', () => this.handleNextEpisodeClick()); this.applyButtonStyles(this.watchCreditsButton, 'watch-credits', style); this.applyButtonStyles(this.nextEpisodeButton, 'next-episode', style); this.buttonsContainer.appendChild(this.watchCreditsButton); this.buttonsContainer.appendChild(this.nextEpisodeButton); this.playerContainer.appendChild(this.buttonsContainer); } applyContainerPosition(styles, position) { 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 'bottom-center': Object.assign(styles, { bottom: '100px', left: '50%', transform: 'translateX(-50%)' }); break; case 'top-right': Object.assign(styles, { top: '30px', right: '30px' }); break; case 'top-left': Object.assign(styles, { top: '30px', left: '30px' }); break; case 'top-center': Object.assign(styles, { top: '30px', left: '50%', transform: 'translateX(-50%)' }); break; } } applyButtonStyles(button, type, customStyle) { const baseStyles = { padding: '12px 24px', fontSize: '14px', fontWeight: '600', border: 'none', borderRadius: '6px', cursor: 'pointer', transition: 'all 0.2s ease-in-out', fontFamily: 'inherit', outline: 'none', minWidth: '180px', position: 'relative' }; if (type === 'watch-credits') { Object.assign(baseStyles, { backgroundColor: customStyle?.watchCreditsBgColor || 'rgba(255, 255, 255, 0.15)', color: customStyle?.watchCreditsColor || '#ffffff', border: `2px solid ${customStyle?.watchCreditsBgColor ? 'transparent' : 'rgba(255, 255, 255, 0.3)'}` }); } else { Object.assign(baseStyles, { backgroundColor: customStyle?.nextEpisodeBgColor || '#e50914', color: customStyle?.nextEpisodeColor || '#ffffff', boxShadow: '0 2px 8px rgba(229, 9, 20, 0.4)' }); } Object.assign(button.style, baseStyles); button.addEventListener('mouseenter', () => { if (type === 'watch-credits') { button.style.backgroundColor = customStyle?.watchCreditsBgColor ? this.adjustBrightness(customStyle.watchCreditsBgColor, 20) : 'rgba(255, 255, 255, 0.25)'; } else { button.style.backgroundColor = customStyle?.nextEpisodeBgColor ? this.adjustBrightness(customStyle.nextEpisodeBgColor, 20) : '#f40612'; button.style.transform = 'scale(1.03)'; } }); button.addEventListener('mouseleave', () => { if (type === 'watch-credits') { button.style.backgroundColor = customStyle?.watchCreditsBgColor || 'rgba(255, 255, 255, 0.15)'; } else { button.style.backgroundColor = customStyle?.nextEpisodeBgColor || '#e50914'; button.style.transform = 'scale(1)'; } }); } adjustBrightness(color, percent) { const num = parseInt(color.replace('#', ''), 16); const amt = Math.round(2.55 * percent); const R = (num >> 16) + amt; const G = (num >> 8 & 0x00FF) + amt; const B = (num & 0x0000FF) + amt; return '#' + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 + (B < 255 ? B < 1 ? 0 : B : 255)) .toString(16).slice(1); } updateButtonLabels(segment) { if (!this.watchCreditsButton || !this.nextEpisodeButton) return; const watchLabel = segment.watchCreditsLabel || this.DEFAULT_WATCH_CREDITS_LABEL; const nextLabel = segment.nextEpisodeLabel || this.DEFAULT_NEXT_EPISODE_LABEL; this.watchCreditsButton.textContent = watchLabel; this.nextEpisodeButton.textContent = nextLabel; this.watchCreditsButton.setAttribute('aria-label', watchLabel); this.nextEpisodeButton.setAttribute('aria-label', nextLabel); } showButtons() { if (!this.buttonsContainer) return; this.buttonsContainer.style.opacity = '1'; this.isVisible = true; } hideButtons() { if (!this.buttonsContainer) return; this.buttonsContainer.remove(); this.buttonsContainer = null; this.watchCreditsButton = null; this.nextEpisodeButton = null; } handleWatchCreditsClick() { if (!this.currentSegment) return; this.userWatchingCredits = true; this.clearTimeouts(); this.hideCreditsButtons('user-action'); this.callbacks.onWatchCredits(this.currentSegment); } handleNextEpisodeClick() { if (!this.currentSegment || !this.currentSegment.nextEpisodeUrl) return; this.clearTimeouts(); this.callbacks.onNextEpisode(this.currentSegment, this.currentSegment.nextEpisodeUrl); this.redirectToNextEpisode(this.currentSegment.nextEpisodeUrl); } startAutoRedirectCountdown(segment, delay) { if (!this.nextEpisodeButton || !segment.nextEpisodeUrl) return; let remainingTime = delay; const originalLabel = segment.nextEpisodeLabel || this.DEFAULT_NEXT_EPISODE_LABEL; this.nextEpisodeButton.textContent = `${originalLabel} (${remainingTime})`; const progressBar = this.createProgressBar(); this.nextEpisodeButton.appendChild(progressBar); this.countdownInterval = setInterval(() => { remainingTime -= 1; if (this.nextEpisodeButton) { this.nextEpisodeButton.textContent = `${originalLabel} (${remainingTime})`; this.nextEpisodeButton.appendChild(progressBar); const progress = ((delay - remainingTime) / delay) * 100; progressBar.style.width = `${progress}%`; } if (remainingTime <= 0) { this.clearTimeouts(); this.callbacks.onAutoRedirect(segment, segment.nextEpisodeUrl); this.redirectToNextEpisode(segment.nextEpisodeUrl); } }, 1000); this.autoRedirectTimeout = setTimeout(() => { if (segment.nextEpisodeUrl) { this.callbacks.onAutoRedirect(segment, segment.nextEpisodeUrl); this.redirectToNextEpisode(segment.nextEpisodeUrl); } }, delay * 1000); } createProgressBar() { const progressBar = document.createElement('div'); progressBar.className = 'uvf-countdown-progress'; Object.assign(progressBar.style, { position: 'absolute', bottom: '0', left: '0', height: '3px', backgroundColor: 'rgba(255, 255, 255, 0.9)', width: '0%', transition: 'width 1s linear', borderRadius: '6px' }); return progressBar; } redirectToNextEpisode(url) { window.location.href = url; } clearTimeouts() { if (this.autoRedirectTimeout) { clearTimeout(this.autoRedirectTimeout); this.autoRedirectTimeout = null; } if (this.countdownInterval) { clearInterval(this.countdownInterval); this.countdownInterval = null; } } } //# sourceMappingURL=CreditsButtonController.js.map