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