Enhance video playback logic with improved state handling and retry mechanism for YouTube players
This commit is contained in:
@@ -1748,7 +1748,14 @@ const SlideCreator = {
|
|||||||
const slide = document.querySelector(`.slide[data-item-id="${itemId}"]`);
|
const slide = document.querySelector(`.slide[data-item-id="${itemId}"]`);
|
||||||
const isVideoPlayerOpen = document.querySelector('.videoPlayerContainer') || document.querySelector('.youtubePlayerContainer');
|
const isVideoPlayerOpen = document.querySelector('.videoPlayerContainer') || document.querySelector('.youtubePlayerContainer');
|
||||||
|
|
||||||
if (slide && slide.classList.contains('active') && !document.hidden && !STATE.slideshow.isPaused && (!isVideoPlayerOpen || isVideoPlayerOpen.classList.contains('hide'))) {
|
const isActive = slide && slide.classList.contains('active');
|
||||||
|
const isHidden = document.hidden;
|
||||||
|
const isPaused = STATE.slideshow.isPaused;
|
||||||
|
const isPlayerOpen = isVideoPlayerOpen && !isVideoPlayerOpen.classList.contains('hide');
|
||||||
|
console.log(`[MBE-READY] onReady for ${itemId}: active=${isActive}, hidden=${isHidden}, paused=${isPaused}, playerOpen=${!!isPlayerOpen}`);
|
||||||
|
|
||||||
|
if (isActive && !isHidden && !isPaused && !isPlayerOpen) {
|
||||||
|
console.log(`[MBE-READY] → Playing video for ${itemId}`);
|
||||||
event.target.playVideo();
|
event.target.playVideo();
|
||||||
|
|
||||||
// Check if it actually started playing after a short delay (handling autoplay blocks)
|
// Check if it actually started playing after a short delay (handling autoplay blocks)
|
||||||
@@ -1781,6 +1788,8 @@ const SlideCreator = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'onStateChange': (event) => {
|
'onStateChange': (event) => {
|
||||||
|
const stateNames = {[-1]: 'UNSTARTED', 0: 'ENDED', 1: 'PLAYING', 2: 'PAUSED', 3: 'BUFFERING', 5: 'CUED'};
|
||||||
|
console.log(`[MBE-STATE] ${itemId}: ${stateNames[event.data] || event.data}`);
|
||||||
if (event.data === YT.PlayerState.ENDED) {
|
if (event.data === YT.PlayerState.ENDED) {
|
||||||
SlideshowManager.nextSlide();
|
SlideshowManager.nextSlide();
|
||||||
}
|
}
|
||||||
@@ -2331,39 +2340,14 @@ const SlideshowManager = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Manage Video Playback: Stop others, Play current
|
// Manage Video Playback: Stop others, Play current
|
||||||
|
this.pauseOtherVideos(currentItemId);
|
||||||
|
|
||||||
// 1. Pause all other YouTube players
|
if (!STATE.slideshow.isPaused) {
|
||||||
if (STATE.slideshow.videoPlayers) {
|
this.playCurrentVideo(currentSlide, currentItemId);
|
||||||
Object.keys(STATE.slideshow.videoPlayers).forEach(id => {
|
} else {
|
||||||
if (id !== currentItemId) {
|
// Check if new slide has video — Option B: un-pause for video slides
|
||||||
const p = STATE.slideshow.videoPlayers[id];
|
|
||||||
if (p && typeof p.pauseVideo === 'function') {
|
|
||||||
p.pauseVideo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Pause all other HTML5 videos
|
|
||||||
document.querySelectorAll('video').forEach(video => {
|
|
||||||
if (!video.closest(`.slide[data-item-id="${currentItemId}"]`)) {
|
|
||||||
video.pause();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. Play and Reset current video
|
|
||||||
const videoBackdrop = currentSlide.querySelector('.video-backdrop');
|
const videoBackdrop = currentSlide.querySelector('.video-backdrop');
|
||||||
|
if (videoBackdrop) {
|
||||||
// Update mute button visibility
|
|
||||||
const muteButton = document.querySelector('.mute-button');
|
|
||||||
if (muteButton) {
|
|
||||||
const hasVideo = !!videoBackdrop;
|
|
||||||
muteButton.style.display = hasVideo ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option B: If paused and new slide has video, un-pause for video playback.
|
|
||||||
// If paused and new slide has only images, stay paused.
|
|
||||||
if (STATE.slideshow.isPaused && videoBackdrop) {
|
|
||||||
STATE.slideshow.isPaused = false;
|
STATE.slideshow.isPaused = false;
|
||||||
const pauseButton = document.querySelector('.pause-button');
|
const pauseButton = document.querySelector('.pause-button');
|
||||||
if (pauseButton) {
|
if (pauseButton) {
|
||||||
@@ -2372,65 +2356,13 @@ const SlideshowManager = {
|
|||||||
pauseButton.setAttribute('aria-label', pauseLabel);
|
pauseButton.setAttribute('aria-label', pauseLabel);
|
||||||
pauseButton.setAttribute('title', pauseLabel);
|
pauseButton.setAttribute('title', pauseLabel);
|
||||||
}
|
}
|
||||||
|
this.playCurrentVideo(currentSlide, currentItemId);
|
||||||
}
|
}
|
||||||
|
// Update mute button visibility
|
||||||
if (videoBackdrop && !STATE.slideshow.isPaused) {
|
const muteButton = document.querySelector('.mute-button');
|
||||||
if (videoBackdrop.tagName === 'VIDEO') {
|
if (muteButton) {
|
||||||
videoBackdrop.currentTime = 0;
|
muteButton.style.display = videoBackdrop ? 'block' : 'none';
|
||||||
|
|
||||||
videoBackdrop.muted = STATE.slideshow.isMuted;
|
|
||||||
if (!STATE.slideshow.isMuted) {
|
|
||||||
videoBackdrop.volume = 0.4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
videoBackdrop.play().catch(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (videoBackdrop.paused && currentSlide.classList.contains('active')) {
|
|
||||||
console.warn(`Autoplay blocked for ${currentItemId}, attempting muted fallback`);
|
|
||||||
videoBackdrop.muted = true;
|
|
||||||
videoBackdrop.play().catch(err => console.error("Muted fallback failed", err));
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
} else if (STATE.slideshow.videoPlayers && STATE.slideshow.videoPlayers[currentItemId]) {
|
|
||||||
const player = STATE.slideshow.videoPlayers[currentItemId];
|
|
||||||
if (player && typeof player.loadVideoById === 'function' && player._videoId) {
|
|
||||||
// Use loadVideoById to enforce start and end times
|
|
||||||
player.loadVideoById({
|
|
||||||
videoId: player._videoId,
|
|
||||||
startSeconds: player._startTime || 0,
|
|
||||||
endSeconds: player._endTime
|
|
||||||
});
|
|
||||||
|
|
||||||
if (STATE.slideshow.isMuted) {
|
|
||||||
player.mute();
|
|
||||||
} else {
|
|
||||||
player.unMute();
|
|
||||||
player.setVolume(40);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if playback successfully started, otherwise fallback to muted
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!currentSlide.classList.contains('active')) return;
|
|
||||||
if (player.getPlayerState &&
|
|
||||||
player.getPlayerState() !== YT.PlayerState.PLAYING &&
|
|
||||||
player.getPlayerState() !== YT.PlayerState.BUFFERING) {
|
|
||||||
console.log("YouTube loadVideoById didn't start playback, retrying muted...");
|
|
||||||
player.mute();
|
|
||||||
player.playVideo();
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
} else if (player && typeof player.seekTo === 'function') {
|
|
||||||
// Fallback if loadVideoById is not available or videoId missing
|
|
||||||
const startTime = player._startTime || 0;
|
|
||||||
player.seekTo(startTime);
|
|
||||||
player.playVideo();
|
|
||||||
}
|
|
||||||
// If player exists but is NOT ready yet (no loadVideoById), do nothing here.
|
|
||||||
// The onReady handler will fire later, see the slide is active, and auto-play.
|
|
||||||
}
|
|
||||||
// If videoBackdrop is a YouTube div but player hasn't been created yet,
|
|
||||||
// do nothing — onReady will handle it when the player becomes ready.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const enableAnimations = MediaBarEnhancedSettingsManager.getSetting('slideAnimations', CONFIG.slideAnimationEnabled);
|
const enableAnimations = MediaBarEnhancedSettingsManager.getSetting('slideAnimations', CONFIG.slideAnimationEnabled);
|
||||||
@@ -2466,7 +2398,8 @@ const SlideshowManager = {
|
|||||||
this.updateDots();
|
this.updateDots();
|
||||||
|
|
||||||
// Only restart interval if we are NOT waiting for a video to end
|
// Only restart interval if we are NOT waiting for a video to end
|
||||||
const hasVideo = videoBackdrop;
|
const hasVideo = currentSlide.querySelector('.video-backdrop') ||
|
||||||
|
(STATE.slideshow.videoPlayers && STATE.slideshow.videoPlayers[currentItemId]);
|
||||||
if (STATE.slideshow.slideInterval && !STATE.slideshow.isPaused) {
|
if (STATE.slideshow.slideInterval && !STATE.slideshow.isPaused) {
|
||||||
if (CONFIG.waitForTrailerToEnd && hasVideo) {
|
if (CONFIG.waitForTrailerToEnd && hasVideo) {
|
||||||
STATE.slideshow.slideInterval.stop();
|
STATE.slideshow.slideInterval.stop();
|
||||||
@@ -2780,6 +2713,146 @@ const SlideshowManager = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays the video backdrop on the given slide and updates mute button visibility.
|
||||||
|
* Includes a retry mechanism for YouTube players that aren't ready yet.
|
||||||
|
* @param {Element} slide - The slide DOM element
|
||||||
|
* @param {string} itemId - The item ID of the slide
|
||||||
|
*/
|
||||||
|
playCurrentVideo(slide, itemId) {
|
||||||
|
// Find video element — check class (covers both original div and iframe with class restored by onReady)
|
||||||
|
const videoBackdrop = slide.querySelector('.video-backdrop');
|
||||||
|
const ytPlayer = STATE.slideshow.videoPlayers && STATE.slideshow.videoPlayers[itemId];
|
||||||
|
const hasAnyVideo = !!(videoBackdrop || ytPlayer);
|
||||||
|
|
||||||
|
console.log(`[MBE-PLAY] playCurrentVideo for ${itemId}: videoBackdrop=${videoBackdrop?.tagName || 'null'}, ytPlayer=${!!ytPlayer}, ytReady=${ytPlayer && typeof ytPlayer.loadVideoById === 'function'}`);
|
||||||
|
|
||||||
|
// Update mute button visibility
|
||||||
|
const muteButton = document.querySelector('.mute-button');
|
||||||
|
if (muteButton) {
|
||||||
|
muteButton.style.display = hasAnyVideo ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasAnyVideo) {
|
||||||
|
console.log(`[MBE-PLAY] No video found for ${itemId}, skipping`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML5 <video> element
|
||||||
|
if (videoBackdrop && videoBackdrop.tagName === 'VIDEO') {
|
||||||
|
console.log(`[MBE-PLAY] Playing HTML5 video for ${itemId}`);
|
||||||
|
videoBackdrop.currentTime = 0;
|
||||||
|
videoBackdrop.muted = STATE.slideshow.isMuted;
|
||||||
|
if (!STATE.slideshow.isMuted) videoBackdrop.volume = 0.4;
|
||||||
|
|
||||||
|
videoBackdrop.play().catch(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (videoBackdrop.paused && slide.classList.contains('active')) {
|
||||||
|
console.warn(`[MBE-PLAY] Autoplay blocked for ${itemId}, muted fallback`);
|
||||||
|
videoBackdrop.muted = true;
|
||||||
|
videoBackdrop.play().catch(err => console.error('[MBE-PLAY] Muted fallback failed', err));
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// YouTube player — try to play now if ready
|
||||||
|
if (ytPlayer && typeof ytPlayer.loadVideoById === 'function' && ytPlayer._videoId) {
|
||||||
|
console.log(`[MBE-PLAY] YouTube player READY for ${itemId}, calling loadVideoById`);
|
||||||
|
ytPlayer.loadVideoById({
|
||||||
|
videoId: ytPlayer._videoId,
|
||||||
|
startSeconds: ytPlayer._startTime || 0,
|
||||||
|
endSeconds: ytPlayer._endTime
|
||||||
|
});
|
||||||
|
|
||||||
|
if (STATE.slideshow.isMuted) {
|
||||||
|
ytPlayer.mute();
|
||||||
|
} else {
|
||||||
|
ytPlayer.unMute();
|
||||||
|
ytPlayer.setVolume(40);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pause slideshow timer for video if configured
|
||||||
|
if (CONFIG.waitForTrailerToEnd && STATE.slideshow.slideInterval) {
|
||||||
|
STATE.slideshow.slideInterval.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1s check: if still not playing, force muted retry
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!slide.classList.contains('active')) return;
|
||||||
|
try {
|
||||||
|
const state = ytPlayer.getPlayerState();
|
||||||
|
if (state !== YT.PlayerState.PLAYING && state !== YT.PlayerState.BUFFERING) {
|
||||||
|
console.warn(`[MBE-PLAY] loadVideoById didn't start for ${itemId} (state=${state}), muted retry`);
|
||||||
|
ytPlayer.mute();
|
||||||
|
ytPlayer.playVideo();
|
||||||
|
}
|
||||||
|
} catch (e) { console.warn('[MBE-PLAY] Error checking player state:', e); }
|
||||||
|
}, 1500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// YouTube player NOT ready yet (onReady hasn't fired).
|
||||||
|
// onReady will handle it IF the slide is still active when it fires.
|
||||||
|
// But as safety net: retry every 500ms for up to 6 seconds.
|
||||||
|
console.log(`[MBE-PLAY] YouTube player NOT READY for ${itemId}, starting retry loop (onReady will also attempt)`);
|
||||||
|
let retryCount = 0;
|
||||||
|
const maxRetries = 12; // 12 × 500ms = 6 seconds
|
||||||
|
const retryTimer = setInterval(() => {
|
||||||
|
retryCount++;
|
||||||
|
|
||||||
|
// Abort if slide changed or paused
|
||||||
|
if (!slide.classList.contains('active') || STATE.slideshow.isPaused) {
|
||||||
|
console.log(`[MBE-PLAY] Retry aborted for ${itemId} (slide inactive or paused)`);
|
||||||
|
clearInterval(retryTimer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const p = STATE.slideshow.videoPlayers && STATE.slideshow.videoPlayers[itemId];
|
||||||
|
|
||||||
|
// Check if player is now playing (onReady may have started it)
|
||||||
|
if (p && typeof p.getPlayerState === 'function') {
|
||||||
|
try {
|
||||||
|
const state = p.getPlayerState();
|
||||||
|
if (state === YT.PlayerState.PLAYING || state === YT.PlayerState.BUFFERING) {
|
||||||
|
console.log(`[MBE-PLAY] Player for ${itemId} is already playing (started by onReady), stopping retry`);
|
||||||
|
clearInterval(retryTimer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) { /* player not fully ready yet */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if player is now ready
|
||||||
|
if (p && typeof p.loadVideoById === 'function' && p._videoId) {
|
||||||
|
console.log(`[MBE-PLAY] Retry #${retryCount}: Player for ${itemId} now READY, calling loadVideoById`);
|
||||||
|
clearInterval(retryTimer);
|
||||||
|
|
||||||
|
p.loadVideoById({
|
||||||
|
videoId: p._videoId,
|
||||||
|
startSeconds: p._startTime || 0,
|
||||||
|
endSeconds: p._endTime
|
||||||
|
});
|
||||||
|
|
||||||
|
if (STATE.slideshow.isMuted) {
|
||||||
|
p.mute();
|
||||||
|
} else {
|
||||||
|
p.unMute();
|
||||||
|
p.setVolume(40);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CONFIG.waitForTrailerToEnd && STATE.slideshow.slideInterval) {
|
||||||
|
STATE.slideshow.slideInterval.stop();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retryCount >= maxRetries) {
|
||||||
|
console.warn(`[MBE-PLAY] Gave up retrying for ${itemId} after ${maxRetries * 500}ms`);
|
||||||
|
clearInterval(retryTimer);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user