feat: Major Upgrade to Video.js v8 — Chapters Functionality, Fixes and Improvements

This commit is contained in:
Yiannis Christodoulou
2025-10-20 15:30:00 +03:00
committed by GitHub
parent b39072c8ae
commit a5e6e7b9ca
362 changed files with 62326 additions and 238721 deletions

View File

@@ -0,0 +1,235 @@
export class AutoplayHandler {
constructor(player, mediaData, userPreferences) {
this.player = player;
this.mediaData = mediaData;
this.userPreferences = userPreferences;
this.isFirefox = this.detectFirefox();
}
detectFirefox() {
return (
typeof navigator !== 'undefined' &&
navigator.userAgent &&
navigator.userAgent.toLowerCase().indexOf('firefox') > -1
);
}
hasUserInteracted() {
// Firefox-specific user interaction detection
if (this.isFirefox) {
return (
// Check if user has explicitly interacted
sessionStorage.getItem('userInteracted') === 'true' ||
// Firefox-specific: Check if document has been clicked/touched
sessionStorage.getItem('firefoxUserGesture') === 'true' ||
// More reliable focus check for Firefox
(document.hasFocus() && document.visibilityState === 'visible') ||
// Check if any user event has been registered
this.checkFirefoxUserGesture()
);
}
// Original detection for other browsers
return (
document.hasFocus() ||
document.visibilityState === 'visible' ||
sessionStorage.getItem('userInteracted') === 'true'
);
}
checkFirefoxUserGesture() {
// Firefox requires actual user gesture for autoplay
// This checks if we've detected any user interaction events
try {
const hasGesture = document.createElement('video').play();
return hasGesture && typeof hasGesture.then === 'function';
} catch {
return false;
}
}
async handleAutoplay() {
// Don't attempt autoplay if already playing or loading
if (!this.player.paused() || this.player.seeking()) {
return;
}
// Firefox-specific delay to ensure player is ready
if (this.isFirefox) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
// Define variables outside try block so they're accessible in catch
const userInteracted = this.hasUserInteracted();
const savedMuteState = this.userPreferences.getPreference('muted');
try {
// Firefox-specific: Always start muted if no user interaction
if (this.isFirefox && !userInteracted) {
this.player.muted(true);
} else if (!this.mediaData.urlMuted && userInteracted && savedMuteState !== true) {
this.player.muted(false);
}
// First attempt: try to play with current mute state
const playPromise = this.player.play();
// Firefox-specific promise handling
if (this.isFirefox && playPromise && typeof playPromise.then === 'function') {
await playPromise;
} else if (playPromise) {
await playPromise;
}
} catch (error) {
// Firefox-specific error handling
if (this.isFirefox) {
await this.handleFirefoxAutoplayError(error, userInteracted, savedMuteState);
} else {
// Fallback to muted autoplay unless user explicitly wants to stay unmuted
if (!this.player.muted()) {
try {
this.player.muted(true);
await this.player.play();
// Only try to restore sound if user hasn't explicitly saved mute=true
if (savedMuteState !== true) {
this.restoreSound(userInteracted);
}
} catch {
// console.error('❌ Even muted autoplay was blocked:', mutedError.message);
}
}
}
}
}
async handleFirefoxAutoplayError(error, userInteracted, savedMuteState) {
// Firefox requires muted autoplay in most cases
if (!this.player.muted()) {
try {
this.player.muted(true);
// Add a small delay for Firefox
await new Promise((resolve) => setTimeout(resolve, 50));
const mutedPlayPromise = this.player.play();
if (mutedPlayPromise && typeof mutedPlayPromise.then === 'function') {
await mutedPlayPromise;
}
// Only try to restore sound if user hasn't explicitly saved mute=true
if (savedMuteState !== true) {
this.restoreSound(userInteracted);
}
} catch {
// Even muted autoplay failed - set up user interaction listeners
this.setupFirefoxInteractionListeners();
}
} else {
// Already muted but still failed - set up interaction listeners
this.setupFirefoxInteractionListeners();
}
}
setupFirefoxInteractionListeners() {
if (!this.isFirefox) return;
const enablePlayback = async () => {
try {
sessionStorage.setItem('firefoxUserGesture', 'true');
sessionStorage.setItem('userInteracted', 'true');
if (this.player && !this.player.isDisposed() && this.player.paused()) {
const playPromise = this.player.play();
if (playPromise && typeof playPromise.then === 'function') {
await playPromise;
}
}
// Remove listeners after successful interaction
document.removeEventListener('click', enablePlayback);
document.removeEventListener('keydown', enablePlayback);
document.removeEventListener('touchstart', enablePlayback);
} catch {
// Interaction still didn't work, keep listeners active
}
};
// Set up interaction listeners for Firefox
document.addEventListener('click', enablePlayback, { once: true });
document.addEventListener('keydown', enablePlayback, { once: true });
document.addEventListener('touchstart', enablePlayback, { once: true });
// Show Firefox-specific notification
if (this.player && !this.player.isDisposed()) {
this.player.trigger('notify', '🦊 Firefox: Click to enable playback');
}
}
restoreSound(userInteracted) {
const restoreSound = () => {
if (this.player && !this.player.isDisposed()) {
this.player.muted(false);
this.player.trigger('notify', '🔊 Sound enabled!');
}
};
// Firefox-specific sound restoration
if (this.isFirefox) {
// Firefox needs more time and user interaction verification
if (userInteracted || sessionStorage.getItem('firefoxUserGesture') === 'true') {
setTimeout(restoreSound, 200); // Longer delay for Firefox
} else {
// Show Firefox-specific notification
setTimeout(() => {
if (this.player && !this.player.isDisposed()) {
this.player.trigger('notify', '🦊 Firefox: Click to enable sound');
}
}, 1500); // Longer delay for Firefox notification
// Set up Firefox-specific interaction listeners
const enableSound = () => {
restoreSound();
// Mark Firefox user interaction
sessionStorage.setItem('userInteracted', 'true');
sessionStorage.setItem('firefoxUserGesture', 'true');
// Remove listeners
document.removeEventListener('click', enableSound);
document.removeEventListener('keydown', enableSound);
document.removeEventListener('touchstart', enableSound);
};
document.addEventListener('click', enableSound, { once: true });
document.addEventListener('keydown', enableSound, { once: true });
document.addEventListener('touchstart', enableSound, { once: true });
}
} else {
// Original behavior for other browsers
if (userInteracted) {
setTimeout(restoreSound, 100);
} else {
// Show notification for manual interaction
setTimeout(() => {
if (this.player && !this.player.isDisposed()) {
this.player.trigger('notify', '🔇 Click anywhere to enable sound');
}
}, 1000);
// Set up interaction listeners
const enableSound = () => {
restoreSound();
// Mark user interaction for future videos
sessionStorage.setItem('userInteracted', 'true');
// Remove listeners
document.removeEventListener('click', enableSound);
document.removeEventListener('keydown', enableSound);
document.removeEventListener('touchstart', enableSound);
};
document.addEventListener('click', enableSound, { once: true });
document.addEventListener('keydown', enableSound, { once: true });
document.addEventListener('touchstart', enableSound, { once: true });
}
}
}
}