@@ -1719,6 +1719,13 @@ const SlideCreator = {
if ( iframe ) {
if ( iframe ) {
iframe . setAttribute ( 'tabindex' , '-1' ) ;
iframe . setAttribute ( 'tabindex' , '-1' ) ;
iframe . setAttribute ( 'inert' , '' ) ;
iframe . setAttribute ( 'inert' , '' ) ;
// Preserve video-backdrop class on the iframe (YT API replaces the original div)
iframe . classList . add ( 'backdrop' , 'video-backdrop' ) ;
if ( CONFIG . fullWidthVideo ) {
iframe . classList . add ( 'video-backdrop-full' ) ;
} else {
iframe . classList . add ( 'video-backdrop-default' ) ;
}
}
}
// Store start/end time and videoId for later use
// Store start/end time and videoId for later use
@@ -1737,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)
@@ -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 ( ) ;
}
}
@@ -2333,11 +2342,12 @@ const SlideshowManager = {
// Manage Video Playback: Stop others, Play current
// Manage Video Playback: Stop others, Play current
this . pauseOtherVideos ( currentItemId ) ;
this . pauseOtherVideos ( currentItemId ) ;
const hasVideoBackdrop = ! ! currentSlide . querySelector ( '.video-backdrop' ) ;
if ( ! STATE . slideshow . isPaused ) {
this . playCurrentVideo ( currentSlide , currentItemId ) ;
// If paused and new slide has video, un-pause for video playback.
} else {
// If paused and new slide has only images, stay paused.
// Check if new slide has video — Option B: un-pause for video slides
if ( STATE . slideshow . isPaused && hasV ideoB ackdrop) {
const videoBackdrop = currentSlide . querySelector ( '.v ideo-b ackdrop' ) ;
if ( 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 ) {
@@ -2346,15 +2356,12 @@ const SlideshowManager = {
pauseButton . setAttribute ( 'aria-label' , pauseLabel ) ;
pauseButton . setAttribute ( 'aria-label' , pauseLabel ) ;
pauseButton . setAttribute ( 'title' , pauseLabel ) ;
pauseButton . setAttribute ( 'title' , pauseLabel ) ;
}
}
}
if ( ! STATE . slideshow . isPaused ) {
this . playCurrentVideo ( currentSlide , currentItemId ) ;
this . playCurrentVideo ( currentSlide , currentItemId ) ;
} else {
}
// Still u pdate mute button visibility based on video presence
// U pdate 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' ;
}
}
}
}
@@ -2391,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 = currentSlide . querySelector ( '.video-backdrop' ) ;
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 ( ) ;
@@ -2706,23 +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 ) {
// Find video element — check class (covers both original div and iframe with class restored by onReady)
const videoBackdrop = slide . querySelector ( '.video-backdrop' ) ;
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
// Update mute button visibility
const muteButton = document . querySelector ( '.mute-button' ) ;
const muteButton = document . querySelector ( '.mute-button' ) ;
if ( muteButton ) {
if ( muteButton ) {
muteButton . style . display = videoBackdrop ? 'block' : 'none' ;
muteButton . style . display = hasAnyVideo ? 'block' : 'none' ;
}
}
if ( ! videoBackdrop ) return false ;
if ( ! hasAnyVideo ) {
console . log ( ` [MBE-PLAY] No video found for ${ itemId } , skipping ` ) ;
return ;
}
if ( videoBackdrop . tagName === 'VIDEO' ) {
// HTML5 <video> element
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 ;
@@ -2730,61 +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 ) {
if ( player && typeof player . loadVideoById === 'function' && player . _v ideoId) {
console . log ( ` [MBE-PLAY] YouTube player READY for ${ itemId } , calling loadV ideoBy Id ` ) ;
p layer. loadVideoById ( {
ytP layer. loadVideoById ( {
videoId : p layer. _videoId ,
videoId : ytP layer. _videoId ,
startSeconds : p layer. _startTime || 0 ,
startSeconds : ytP layer. _startTime || 0 ,
endSeconds : p layer. _endTime
endSeconds : ytP layer. _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 ( ) ;
}
}
} , 1000 ) ;
} catch ( e ) { console . warn ( '[MBE-PLAY] Error checking player state:' , e ) ; }
return true ;
} , 1500 ) ;
} else if ( player && typeof player . seekTo === 'function' ) {
return ;
// Fallback if loadVideoById is not available or videoId missing but player object exists
const startTime = player . _startTime || 0 ;
player . seekTo ( startTime ) ;
player . playVideo ( ) ;
return true ;
}
}
// YouTube player not ready yet (still loading from preload) — mark for auto-play when onReady fires
// YouTube player NOT ready yet (onReady hasn't fired).
if ( videoBackdrop && videoBackdrop . id && videoBackdrop . id . startsWith ( 'youtube-player-' ) && ! player ) {
// onReady will handle it IF the slide is still active when it fires.
console . log ( ` YouTube player for ${ itemId } not ready yet, marking _pendingPlay ` ) ;
// But as safety net: retry every 500ms for up to 6 seconds.
videoBackdrop . _pendingPlay = true ;
console . log ( ` [MBE-PLAY] YouTube player NOT READY for ${ itemId } , starting retry loop (onReady will also attempt) ` ) ;
return true ;
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 ;
}
}
return false ;
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 ) ;
} ,
} ,
/**
/**
* 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