diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js index 472a116..87e008b 100644 --- a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js +++ b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js @@ -1748,7 +1748,14 @@ const SlideCreator = { const slide = document.querySelector(`.slide[data-item-id="${itemId}"]`); 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(); // Check if it actually started playing after a short delay (handling autoplay blocks) @@ -1781,6 +1788,8 @@ const SlideCreator = { } }, '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) { SlideshowManager.nextSlide(); } @@ -2331,108 +2340,31 @@ const SlideshowManager = { }); // Manage Video Playback: Stop others, Play current + this.pauseOtherVideos(currentItemId); - // 1. Pause all other YouTube players - if (STATE.slideshow.videoPlayers) { - Object.keys(STATE.slideshow.videoPlayers).forEach(id => { - if (id !== currentItemId) { - const p = STATE.slideshow.videoPlayers[id]; - if (p && typeof p.pauseVideo === 'function') { - p.pauseVideo(); - } + if (!STATE.slideshow.isPaused) { + this.playCurrentVideo(currentSlide, currentItemId); + } else { + // 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 = 'pause'; + const pauseLabel = LocalizationUtils.getLocalizedString('ButtonPause', 'Pause'); + pauseButton.setAttribute('aria-label', pauseLabel); + pauseButton.setAttribute('title', pauseLabel); } - }); - } - - // 2. Pause all other HTML5 videos - document.querySelectorAll('video').forEach(video => { - if (!video.closest(`.slide[data-item-id="${currentItemId}"]`)) { - video.pause(); + this.playCurrentVideo(currentSlide, currentItemId); } - }); - - // 3. Play and Reset current video - const videoBackdrop = currentSlide.querySelector('.video-backdrop'); - - // 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 = 'pause'; - const pauseLabel = LocalizationUtils.getLocalizedString('ButtonPause', 'Pause'); - pauseButton.setAttribute('aria-label', pauseLabel); - pauseButton.setAttribute('title', pauseLabel); + // Update mute button visibility + const muteButton = document.querySelector('.mute-button'); + if (muteButton) { + muteButton.style.display = videoBackdrop ? 'block' : 'none'; } } - 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); if (enableAnimations) { @@ -2466,7 +2398,8 @@ const SlideshowManager = { this.updateDots(); // 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 (CONFIG.waitForTrailerToEnd && hasVideo) { 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