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
JavaScript
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