@@ -1744,18 +1744,18 @@ const SlideCreator = {
event . target . setPlaybackQuality ( quality ) ;
event . target . setPlaybackQuality ( quality ) ;
}
}
// Only play if this is the active slide AND the slideshow is visible
// Only play if this is the active slide and not paused
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' ) ;
// Check _pendingPlay flag (set by playCurrentVideo when player wasn't ready yet)
const isActive = slide && slide . classList . contains ( 'active' ) ;
const container = document . getElementById ( ` youtube-player- ${ itemId } ` ) ;
const isHidden = document . hidden ;
const hasPendingPlay = container && container . _pendingPlay ;
const isPaused = STATE . slideshow . isPaused ;
if ( container ) container . _pendingPlay = false ;
const isPlayerOpen = isVideoPlayerOpen && ! isVideoPlayerOpen . classList . contains ( 'hide' ) ;
console . log ( ` [MBE-READY] onReady for ${ itemId } : active= ${ isActive } , hidden= ${ isHidden } , paused= ${ isPaused } , playerOpen= ${ ! ! isPlayerOpen } ` ) ;
const isActiveAndVisible = slide && slide . classList . contains ( 'a ctive' ) && ! document . h idden && ( ! isVideoPlayerOpen || isVideoPlayerOpen . classList . contains ( 'hide' ) ) ;
if ( isA ctive && ! isH idden && ! isPaused && ! isPlayerOpen ) {
console . log ( ` [MBE-READY] → Playing video for ${ itemId } ` ) ;
if ( ( isActiveAndVisible || hasPendingPlay ) && ! STATE . slideshow . isPaused ) {
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)
@@ -1788,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 ( ) ;
}
}
@@ -2340,31 +2342,26 @@ const SlideshowManager = {
// Manage Video Playback: Stop others, Play current
// Manage Video Playback: Stop others, Play current
this . pauseOtherVideos ( currentItemId ) ;
this . pauseOtherVideos ( currentItemId ) ;
// Check for video backdrop (also check by YouTube player ID since YT replaces div with iframe)
const hasVideoBackdrop = ! ! ( currentSlide . querySelector ( '.video-backdrop' ) ||
currentSlide . querySelector ( ` #youtube-player- ${ currentItemId } ` ) ||
( STATE . slideshow . videoPlayers && STATE . slideshow . videoPlayers [ currentItemId ] ) ) ;
// 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 && hasVideoBackdrop ) {
STATE . slideshow . isPaused = false ;
const pauseButton = document . querySelector ( '.pause-button' ) ;
if ( pauseButton ) {
pauseButton . innerHTML = '<i class="material-icons">pause</i>' ;
const pauseLabel = LocalizationUtils . getLocalizedString ( 'ButtonPause' , 'Pause' ) ;
pauseButton . setAttribute ( 'aria-label' , pauseLabel ) ;
pauseButton . setAttribute ( 'title' , pauseLabel ) ;
}
}
if ( ! STATE . slideshow . isPaused ) {
if ( ! STATE . slideshow . isPaused ) {
this . playCurrentVideo ( currentSlide , currentItemId ) ;
this . playCurrentVideo ( currentSlide , currentItemId ) ;
} else {
} else {
// Still update mute button visibility based on video presence
// Check if new slide has video — Option B: un-pause for video slides
const videoBackdrop = currentSlide . querySelector ( '.video-backdrop' ) ;
if ( videoBackdrop ) {
STATE . slideshow . isPaused = false ;
const pauseButton = document . querySelector ( '.pause-button' ) ;
if ( pauseButton ) {
pauseButton . innerHTML = '<i class="material-icons">pause</i>' ;
const pauseLabel = LocalizationUtils . getLocalizedString ( 'ButtonPause' , 'Pause' ) ;
pauseButton . setAttribute ( 'aria-label' , pauseLabel ) ;
pauseButton . setAttribute ( 'title' , pauseLabel ) ;
}
this . playCurrentVideo ( currentSlide , currentItemId ) ;
}
// Update mute button visibility
const muteButton = document . querySelector ( '.mute-button' ) ;
const muteButton = document . querySelector ( '.mute-button' ) ;
if ( muteButton ) {
if ( muteButton ) {
muteButton . style . display = hasV ideoBackdrop ? 'block' : 'none' ;
muteButton . style . display = v ideoBackdrop ? 'block' : 'none' ;
}
}
}
}
@@ -2402,7 +2399,6 @@ const SlideshowManager = {
// 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 = currentSlide . querySelector ( '.video-backdrop' ) ||
const hasVideo = currentSlide . querySelector ( '.video-backdrop' ) ||
currentSlide . querySelector ( ` #youtube-player- ${ currentItemId } ` ) ||
( STATE . slideshow . videoPlayers && STATE . slideshow . videoPlayers [ currentItemId ] ) ;
( 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 ) {
@@ -2665,9 +2661,7 @@ const SlideshowManager = {
// 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 currentItemId = STATE . slideshow . itemIds [ STATE . slideshow . currentSlideIndex ] ;
const currentItemId = STATE . slideshow . itemIds [ STATE . slideshow . currentSlideIndex ] ;
const currentSlide = document . querySelector ( ` .slide[data-item-id=" ${ currentItemId } "] ` ) ;
const currentSlide = document . querySelector ( ` .slide[data-item-id=" ${ currentItemId } "] ` ) ;
const hasVideo = currentSlide && ( currentSlide . querySelector ( '.video-backdrop' ) ||
const hasVideo = currentSlide && currentSlide . querySelector ( '.video-backdrop' ) ;
currentSlide . querySelector ( ` #youtube-player- ${ currentItemId } ` ) ||
( STATE . slideshow . videoPlayers && STATE . slideshow . videoPlayers [ currentItemId ] ) ) ;
if ( ! CONFIG . waitForTrailerToEnd || ! hasVideo ) {
if ( ! CONFIG . waitForTrailerToEnd || ! hasVideo ) {
STATE . slideshow . slideInterval . start ( ) ;
STATE . slideshow . slideInterval . start ( ) ;
@@ -2720,30 +2714,33 @@ const SlideshowManager = {
} ,
} ,
/**
/**
* Plays the video backdrop on the given slide and updates mute button visibility
* 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 {Element} slide - The slide DOM element
* @param {string} itemId - The item ID of the slide
* @param {string} itemId - The item ID of the slide
* @returns {boolean} Whether a video was found and playback attempted
*/
*/
playCurrentVideo ( slide , itemId ) {
playCurrentVideo ( slide , itemId ) {
// Try finding by class first, then fall back to YouTube player container by ID
// Find video element — check class (covers both original div and iframe with class restored by onReady)
le t videoBackdrop = slide . querySelector ( '.video-backdrop' ) ;
cons t videoBackdrop = slide . querySelector ( '.video-backdrop' ) ;
if ( ! videoBackdrop ) {
const ytPlayer = STATE . slideshow . videoPlayers && STATE . slideshow . videoPlayers [ itemId ] ;
// YouTube API replaces the div with an iframe, which may not have the class yet
const hasAnyVideo = ! ! ( videoBackdrop || ytPlayer ) ;
videoBackdrop = slide . querySelector ( ` #youtube-player- ${ itemId } ` ) ;
}
console . log ( ` [MBE-PLAY] playCurrentVideo for ${ itemId } : videoBackdrop= ${ videoBackdrop ? . tagName || 'null' } , ytPlayer= ${ ! ! ytPlayer } , ytReady= ${ ytPlayer && typeof ytPlayer . loadVideoById === 'function' } ` ) ;
// Also check if a player exists in the registry even if no DOM element found
const hasRegisteredPlayer = ! ! ( STATE . slideshow . videoPlayers && STATE . slideshow . videoPlayers [ itemId ] ) ;
// Update mute button visibility
// Update mute button visibility
const muteButton = document . querySelector ( '.mute-button' ) ;
const muteButton = document . querySelector ( '.mute-button' ) ;
if ( muteButton ) {
if ( muteButton ) {
muteButton . style . display = ( videoBackdrop || hasRegisteredPlayer ) ? 'block' : 'none' ;
muteButton . style . display = hasAnyVideo ? 'block' : 'none' ;
}
}
if ( ! videoBackdrop && ! hasRegisteredPlayer ) return false ;
if ( ! hasAnyVideo ) {
console . log ( ` [MBE-PLAY] No video found for ${ itemId } , skipping ` ) ;
return ;
}
// HTML5 <video> element
if ( videoBackdrop && videoBackdrop . tagName === 'VIDEO' ) {
if ( videoBackdrop && videoBackdrop . tagName === 'VIDEO' ) {
console . log ( ` [MBE-PLAY] Playing HTML5 video for ${ itemId } ` ) ;
videoBackdrop . currentTime = 0 ;
videoBackdrop . currentTime = 0 ;
videoBackdrop . muted = STATE . slideshow . isMuted ;
videoBackdrop . muted = STATE . slideshow . isMuted ;
if ( ! STATE . slideshow . isMuted ) videoBackdrop . volume = 0.4 ;
if ( ! STATE . slideshow . isMuted ) videoBackdrop . volume = 0.4 ;
@@ -2751,109 +2748,113 @@ const SlideshowManager = {
videoBackdrop . play ( ) . catch ( ( ) => {
videoBackdrop . play ( ) . catch ( ( ) => {
setTimeout ( ( ) => {
setTimeout ( ( ) => {
if ( videoBackdrop . paused && slide . classList . contains ( 'active' ) ) {
if ( videoBackdrop . paused && slide . classList . contains ( 'active' ) ) {
console . warn ( ` Autoplay blocked for ${ itemId } , attempting muted fallback ` ) ;
console . warn ( ` [MBE-PLAY] Autoplay blocked for ${ itemId } , muted fallback ` ) ;
videoBackdrop . muted = true ;
videoBackdrop . muted = true ;
videoBackdrop . play ( ) . catch ( err => console . error ( " Muted fallback failed" , err ) ) ;
videoBackdrop . play ( ) . catch ( err => console . error ( '[MBE-PLAY] Muted fallback failed' , err ) ) ;
}
}
} , 1000 ) ;
} , 1000 ) ;
} ) ;
} ) ;
return true ;
return ;
}
}
// YouTube player
// YouTube player — try to play now if ready
const player = STATE . slideshow . videoPlayers && STATE . slideshow . video Players [ itemId ] ;
if ( ytPlayer && typeof ytPlayer . loadVideoById === 'function' && yt Player. _videoId ) {
const playerIsReady = player && typeof player . loadVideoById === 'function' && player . _videoId ;
console . log ( ` [MBE-PLAY] YouTube player READY for ${ itemId } , calling loadVideoById ` ) ;
ytPlayer . loadVideoById ( {
if ( playerIsReady ) {
videoId : ytPlayer . _videoId ,
player . loadVideoById ( {
startSeconds : ytPlayer . _startTime || 0 ,
videoId : p layer. _videoId ,
endSeconds : ytP layer. _endTime
startSeconds : player . _startTime || 0 ,
endSeconds : player . _endTime
} ) ;
} ) ;
if ( STATE . slideshow . isMuted ) {
if ( STATE . slideshow . isMuted ) {
p layer. mute ( ) ;
ytP layer. mute ( ) ;
} else {
} else {
p layer. unMute ( ) ;
ytP layer. unMute ( ) ;
p layer. setVolume ( 40 ) ;
ytP layer. 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 ( ( ) => {
setTimeout ( ( ) => {
if ( ! slide . classList . contains ( 'active' ) ) return ;
if ( ! slide . classList . contains ( 'active' ) ) return ;
try {
if ( p layer. getPlayerState &&
const state = ytP layer . getPlayerState ( ) ;
player . get PlayerState( ) !== YT . PlayerState . PLAYING &&
if ( state !== YT . PlayerState . PLAYING && state !== YT . PlayerState . BUFFERING ) {
player . getPlayerState ( ) !== YT . PlayerState . BUFFERING ) {
console . warn ( ` [MBE-PLAY] loadVideoById didn't start for ${ itemId } (state= ${ state } ), muted retry ` ) ;
console . log ( "YouTube loadVideoById didn't start playback, retrying muted..." ) ;
ytPlayer . mute ( ) ;
p layer. mute ( ) ;
ytP layer . playVideo ( ) ;
player . playVideo ( ) ;
}
}
} catch ( e ) { console . warn ( '[MBE-PLAY] Error checking player state:' , e ) ; }
} , 10 00 ) ;
} , 15 00 ) ;
return true ;
return ;
}
}
// YouTube player exists but NOT fully ready yet.
// YouTube player NOT ready yet (onReady hasn't fired).
// new YT.Player() returns an object immediately, but API methods like
// onReady will handle it IF the slide is still active when it fires.
// loadVideoById and _videoId aren't available until onReady fire s.
// But as safety net: retry every 500ms for up to 6 second s.
// onReady may have ALREADY fired (dur ing p reload), so we can't rely on
console . log ( ` [MBE-PLAY] YouTube player NOT READY for ${ itemId } , start ing retry loop (onReady will also attempt) ` ) ;
// _pendingPlay alone. Instead, poll for readiness.
let retryCount = 0 ;
const ytContainer = videoBackdrop || slide . querySelector ( ` [id^="youtube-player-"] ` ) ;
const maxRetries = 12 ; // 12 × 500ms = 6 seconds
if ( ytContainer && ( ytContainer . tagName === 'IFRAME' || ( ytContainer . id && ytContainer . id . startsWith ( 'youtube-player-' ) ) ) ) {
const retryTimer = setInterval ( ( ) => {
console . log ( ` YouTube player for ${ itemId } not ready yet, polling for readiness... ` ) ;
retryCount ++ ;
// Also set _pendingPlay as fallback in case onReady hasn't fired yet
// Abort if slide changed or paused
ytContainer . _pendingPlay = true ;
if ( ! slide . classList . contains ( 'active' ) || STATE . slideshow . isPaused ) {
console . log ( ` [MBE-PLAY] Retry aborted for ${ itemId } (slide inactive or paused) ` ) ;
clearInterval ( retryTimer ) ;
return ;
}
let attempts = 0 ;
const p = STATE . slideshow . videoPlayers && STATE . slideshow . videoPlayers [ itemId ] ;
const maxAttempts = 25 ; // 25 * 200ms = 5 seconds max
const pollInterval = setInterval ( ( ) => {
attempts ++ ;
// Stop if slide is no longer active (user navigated away )
// Check if player is now playing (onReady may have started it )
if ( ! slide . classList . contains ( 'active' ) ) {
if ( p && typeof p . getPlayerState === 'function' ) {
clearInterval ( pollInterval ) ;
try {
return ;
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 ` ) ;
const p = STATE . slideshow . videoPlayers && STATE . slideshow . videoPlayers [ itemId ] ;
clearInterval ( retryTimer ) ;
if ( p && typeof p . loadVideoById === 'function' && p . _videoId ) {
return ;
clearInterval ( pollInterval ) ;
console . log ( ` YouTube player for ${ itemId } now ready (after ${ attempts * 200 } ms), starting playback ` ) ;
if ( STATE . slideshow . isPaused ) return ;
p . loadVideoById ( {
videoId : p . _videoId ,
startSeconds : p . _startTime || 0 ,
endSeconds : p . _endTime
} ) ;
if ( STATE . slideshow . isMuted ) {
p . mute ( ) ;
} else {
p . unMute ( ) ;
p . setVolume ( 40 ) ;
}
}
} catch ( e ) { /* player not fully ready yet */ }
}
// Pause slideshow timer when video starts if configured
// Check if player is now ready
if ( CONFIG . waitForTrailerToEnd && STATE . sl ideshow . slideInterval ) {
if ( p && typeof p . loadVideoById === 'function' && p . _v ideoId ) {
STATE . slideshow . slideInterval . stop ( ) ;
console . log ( ` [MBE-PLAY] Retry # ${ retryCount } : Player for ${ itemId } now READY, calling loadVideoById ` ) ;
}
clearInterval ( retryTimer ) ;
return ;
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 ( attempts >= maxAttempts ) {
if ( CONFIG . waitForTrailerToEnd && STATE . slideshow . slideInterval ) {
clear Interval( pollInterval ) ;
STATE . slideshow . slide Interval. stop ( ) ;
console . warn ( ` YouTube player for ${ itemId } failed to become ready after ${ maxAttempts * 200 } ms ` ) ;
}
}
} , 200 ) ;
return ;
}
return true ;
if ( retryCount >= maxRetries ) {
}
console . warn ( ` [MBE-PLAY] Gave up retrying for ${ itemId } after ${ maxRetries * 500 } ms ` ) ;
clearInterval ( retryTimer ) ;
return false ;
}
} , 500 ) ;
} ,
} ,
/**
/**
* Stops all video playback (YouTube and HTML5)
* Stops all video playback (YouTube and HTML5)
* Used when navigating away from the home screen
* Used when navigating away from the home screen