Improve autoplay handling for Firefox compatibility

Adds Firefox-specific detection and logic to the AutoplayHandler to better handle autoplay restrictions in Firefox. This includes user gesture detection, delayed playback attempts, custom error handling, and user notifications to prompt interaction for enabling playback and sound. The changes ensure a more reliable autoplay experience across browsers, especially addressing Firefox's stricter autoplay policies.
This commit is contained in:
Yiannis Christodoulou 2025-10-11 00:17:02 +03:00
parent 1b83ff23b4
commit 4642cae94b

View File

@ -3,9 +3,33 @@ export class AutoplayHandler {
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' ||
@ -13,25 +37,54 @@ export class AutoplayHandler {
);
}
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 {
// Respect user's saved mute preference, but try unmuted if user interacted and hasn't explicitly muted
if (!this.mediaData.urlMuted && userInteracted && savedMuteState !== true) {
// 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
await this.player.play();
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 {
@ -42,12 +95,76 @@ export class AutoplayHandler {
if (savedMuteState !== true) {
this.restoreSound(userInteracted);
}
} catch (mutedError) {
} 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 = () => {
@ -57,7 +174,37 @@ export class AutoplayHandler {
}
};
// Try to restore sound immediately if user has interacted
// 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 {
@ -85,3 +232,4 @@ export class AutoplayHandler {
}
}
}
}