@@ -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 ( 'hid e' ) ) ) {
const isActive = slide && slide . classList . contains ( 'activ e' ) ;
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,108 +2340,31 @@ 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 ] ;
const videoBackdrop = currentSlide . querySelector ( '.video-backdrop' ) ;
if ( p && typeof p . pauseVideo === 'function' ) {
if ( videoBackdrop ) {
p . pauseVideo ( ) ;
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 ) ;
}
// 2. Pause all other HTML5 videos
document . querySelectorAll ( 'video' ) . forEach ( video => {
if ( ! video . closest ( ` .slide[data-item-id=" ${ currentItemId } "] ` ) ) {
video . pause ( ) ;
}
}
} ) ;
// Update mute button visibility
const muteButton = document . querySelector ( '.mute-button' ) ;
// 3. Play and Reset current video
if ( muteButton ) {
const videoBackdrop = currentSlide . querySelector ( '. video-b ackdrop') ;
muteButton . style . display = videoB ackdrop ? 'block' : 'none ' ;
// 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 ;
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 ( videoBackdrop && ! STATE . slideshow . isPaused ) {
if ( videoBackdrop . tagName === 'VIDEO' ) {
videoBackdrop . currentTime = 0 ;
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 ) ;
if ( enableAnimations ) {
if ( enableAnimations ) {
@@ -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 = videoB ackdrop ;
const hasVideo = currentSlide . querySelector ( '. video-b ackdrop' ) ||
( 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 ) ;
} ,
/**
/**