From 7e8d0667efbf1f952a5b90011175c8fc1c1208fe Mon Sep 17 00:00:00 2001 From: Omar Alodwan <31454776+Omar-Alodwan@users.noreply.github.com> Date: Mon, 12 Oct 2026 03:08:37 +0200 Subject: [PATCH 01/10] Keep YouTube trailer captions off the home rows Trailers whose uploader set default captions show them at the bottom of the player. The trailer fills the bar behind the home rows, so the captions are drawn over them. Add a trailerCaptions option (default off, "Trailer captions" switch in the settings panel). While it is off, the caption module is unloaded on ready and again when playback starts, since YouTube loads it on play. --- slideshowpure.js | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/slideshowpure.js b/slideshowpure.js index bb1f05b..532e749 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -73,6 +73,9 @@ const CONFIG = { // 0-100. Volume once unmuted. Trailers always start muted. trailerVolume: 50, + // true|false. Show the uploader's default captions on YouTube trailers. Off by default: the trailer fills the bar behind the home rows, so captions land on top of them. + trailerCaptions: false, + // px. At or below this width the layout is swipe-only: no arrows, no trailer, no volume. touchLayoutMaxWidth: 767, @@ -412,6 +415,17 @@ const STATE = { }, }; +// YouTube loads its caption module once playback starts, so this runs on ready and again on play. +const hideTrailerCaptions = (player) => { + if (CONFIG.trailerCaptions) return; + try { + player.unloadModule("captions"); + player.unloadModule("cc"); + } catch (err) { + // player not ready or no caption module: nothing to hide + } +}; + const loadYouTubeAPI = () => { if (STATE.slideshow.ytPromise) return STATE.slideshow.ytPromise; @@ -2923,13 +2937,18 @@ const SlideCreator = { start: startTime, }, events: { - onStateChange: (e) => + onStateChange: (e) => { + if (e.data === YT.PlayerState.PLAYING) hideTrailerCaptions(e.target); SlideshowManager.onPlayerStateChange( e, itemId, trailerContainer, - ), - onReady: (e) => e.target.mute(), + ); + }, + onReady: (e) => { + e.target.mute(); + hideTrailerCaptions(e.target); + }, onError: (e) => SlideshowManager.onPlayerError(e, itemId, trailerContainer), @@ -3956,6 +3975,7 @@ const SettingsPanel = { step: 5, format: (value) => `${value}%`, }, + { key: "trailerCaptions", label: "Trailer captions", type: "switch" }, { key: "trailerLibraries", label: "Trailers from", From 8ebd0e517fe3501b555358c1f676ee6aad535804 Mon Sep 17 00:00:00 2001 From: Omar-Alodwan <31454776+Omar-Alodwan@users.noreply.github.com> Date: Mon, 12 Oct 2026 18:29:35 +0200 Subject: [PATCH 02/10] Avoid Element.replaceChildren for older web engines Jellyfin Media Player 1.12 ships a web engine older than Chrome 86, so dotsContainer.replaceChildren() threw and the bar never loaded. Clear with textContent and append instead. --- slideshowpure.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/slideshowpure.js b/slideshowpure.js index 532e749..39611cb 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -2907,7 +2907,10 @@ const SlideCreator = { ); const host = document.getElementById(`yt-player-${itemId}`); - host?.replaceChildren(video); + if (host) { + host.textContent = ""; + host.appendChild(video); + } STATE.slideshow.players[itemId] = createLocalPlayer(video); STATE.slideshow.slideVideoIds[itemId] = `local:${itemId}`; } else { @@ -2979,7 +2982,7 @@ const SlideshowManager = { container.appendChild(dotsContainer); } - dotsContainer.replaceChildren(); + dotsContainer.textContent = ""; const count = Math.min(STATE.slideshow.totalItems, CONFIG.maxDots); From 8cd503c8fdff0da7f1076f8ceaca58ec6be50ac1 Mon Sep 17 00:00:00 2001 From: MakD Date: Tue, 13 Oct 2026 03:47:29 +0530 Subject: [PATCH 03/10] Fix slide timer not auto progressing --- slideshowpure.js | 69 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/slideshowpure.js b/slideshowpure.js index 532e749..c4ff846 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -342,6 +342,10 @@ const setConfig = (patch) => { saveStoredConfig(patch); const changed = applyConfig(patch, { label: "runtime" }); + if (changed.includes("shuffleInterval") && STATE.slideshow.slideInterval) { + STATE.slideshow.slideInterval.interval = CONFIG.shuffleInterval; + } + if (changed.some((key) => STRUCTURAL_CONFIG_KEYS.has(key))) { resetSlideshowState(); bootstrap(); @@ -1902,10 +1906,11 @@ class SlideTimer { this.startedAt = Date.now() - (this.interval - this.remaining); this.resumeId = setTimeout(() => { this.resumeId = null; - this.callback(); + this.startedAt = Date.now(); this.remaining = this.interval; this.timerId = setInterval(this.callback, this.interval); + this.callback(); }, this.remaining); return this; } @@ -3232,13 +3237,13 @@ const SlideshowManager = { STATE.slideshow.trailerStartTimer = setTimeout(() => { this.startTrailer(index, currentItemId, fallbackToTimer); }, CONFIG.trailerStartDelayMs); - } else { - fallbackToTimer(); } + fallbackToTimer(); + function fallbackToTimer() { if (!STATE.slideshow.isPaused && STATE.slideshow.slideInterval) { - STATE.slideshow.slideInterval.restart(); + STATE.slideshow.slideInterval.start(); } } @@ -3704,6 +3709,8 @@ const SlideshowManager = { STATE.slideshow.isVideoPlaying = true; this.setTrailerVisible(itemId, trailerContainer, true); STATE.slideshow.slideInterval?.stop(); + + this.setProgressRunning(false); }, onTrailerEnded(itemId, trailerContainer) { @@ -3712,6 +3719,28 @@ const SlideshowManager = { this.nextSlide(); }, + resumeAfterTrailer(itemId) { + const currentItemId = + STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex]; + if (itemId !== currentItemId) return; + + if ( + STATE.slideshow.isPaused || + STATE.slideshow.hoverHeld || + document.hidden + ) { + return; + } + + const timer = STATE.slideshow.slideInterval; + if (!timer || timer.timerId || timer.resumeId) return; + + timer.start(); + this.restartProgress( + document.querySelector(`.slide[data-item-id="${itemId}"]`), + ); + }, + onPlayerStateChange(event, itemId, trailerContainer) { if (!document.querySelector(`.slide[data-item-id="${itemId}"]`)) return; @@ -3726,7 +3755,13 @@ const SlideshowManager = { event.data === YTRef.PlayerState.PAUSED || event.data === YTRef.PlayerState.CUED ) { + + const currentItemId = + STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex]; + if (itemId !== currentItemId) return; + STATE.slideshow.isVideoPlaying = false; + this.resumeAfterTrailer(itemId); } }, @@ -3735,10 +3770,6 @@ const SlideshowManager = { `Trailer for ${itemId} failed with YouTube error ${event?.data}; continuing without it.`, ); - clearTimeout(STATE.slideshow.trailerWatchdog); - STATE.slideshow.trailerWatchdog = null; - STATE.slideshow.isVideoPlaying = false; - const slide = document.querySelector(`.slide[data-item-id="${itemId}"]`); trailerContainer?.classList.remove("active"); slide?.querySelector(".backdrop")?.classList.remove("with-video"); @@ -3746,9 +3777,15 @@ const SlideshowManager = { STATE.slideshow.slideVideoIds[itemId] = null; - if (!STATE.slideshow.isPaused && STATE.slideshow.slideInterval) { - STATE.slideshow.slideInterval.restart(); - } + const currentItemId = + STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex]; + if (itemId !== currentItemId) return; + + clearTimeout(STATE.slideshow.trailerWatchdog); + STATE.slideshow.trailerWatchdog = null; + STATE.slideshow.isVideoPlaying = false; + + this.resumeAfterTrailer(itemId); }, async loadSlideshowData() { @@ -4417,9 +4454,11 @@ const initPageVisibilityHandler = () => { if (document.hidden) { console.log("Tab inactive - pausing slideshow and videos"); wasVideoPlayingBeforeHide = STATE.slideshow.isVideoPlaying; + if (STATE.slideshow.slideInterval) { - STATE.slideshow.slideInterval.stop(); + STATE.slideshow.slideInterval.pause(); } + SlideshowManager.setProgressRunning(false); const currentItemId = STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex]; if (currentItemId && STATE.slideshow.players[currentItemId]) { @@ -4451,14 +4490,16 @@ const initPageVisibilityHandler = () => { } catch (e) { console.warn("Error resuming video on tab show:", e); if (STATE.slideshow.slideInterval) { - STATE.slideshow.slideInterval.start(); + STATE.slideshow.slideInterval.resume(); } + SlideshowManager.setProgressRunning(true); } } } else { if (STATE.slideshow.slideInterval) { - STATE.slideshow.slideInterval.start(); + STATE.slideshow.slideInterval.resume(); } + SlideshowManager.setProgressRunning(true); } wasVideoPlayingBeforeHide = false; } From fea6e2928faa69c43afe011e3d4e1c1146ada6ac Mon Sep 17 00:00:00 2001 From: M0RPH3US Date: Tue, 13 Oct 2026 04:00:08 +0530 Subject: [PATCH 04/10] Update version number to v6.1.1 --- slideshowpure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slideshowpure.js b/slideshowpure.js index c4ff846..60860fa 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -1,5 +1,5 @@ /* - * Jellyfin Slideshow by M0RPH3US v6.1.0 + * Jellyfin Slideshow by M0RPH3US v6.1.1 */ const CONFIG = { From 4f9d981933aaabc4a8519a9e174eb3095ed645ba Mon Sep 17 00:00:00 2001 From: M0RPH3US Date: Tue, 13 Oct 2026 04:00:31 +0530 Subject: [PATCH 05/10] Update version number to 6.1.1 --- slideshowpure.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slideshowpure.css b/slideshowpure.css index b2862d4..0bb8a05 100644 --- a/slideshowpure.css +++ b/slideshowpure.css @@ -1,5 +1,5 @@ /* - * Jellyfin Slideshow by M0RPH3US v6.0.0 + * Jellyfin Slideshow by M0RPH3US v6.1.1 * * The Archivo Narrow webfont is linked from index.html, not @import-ed here: * an @import cannot start downloading until this stylesheet has itself been From b1418df2db3056b8fe21d4f0b86050a39fe52339 Mon Sep 17 00:00:00 2001 From: M0RPH3US Date: Tue, 13 Oct 2026 14:13:21 +0000 Subject: [PATCH 06/10] Revise README for Play Now function and CSS support Updated README to reflect changes in CSS support and added badges. --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9972520..332ff5c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # Jellyfin-Media-Bar - Now with Play Now Function -![jsDelivr hits (GitHub)](https://img.shields.io/jsdelivr/gh/hm/makd/Jellyfin-Media-Bar?style=for-the-badge&logo=javascript&logoColor=white&labelColor=F0DB4F&color=323330&cacheSeconds=3600) +
+![jsDelivr hits (GitHub)](https://img.shields.io/jsdelivr/gh/hm/makd/Jellyfin-Media-Bar?style=for-the-badge&logo=javascript&logoColor=white&labelColor=F0DB4F&color=323330&cacheSeconds=3600) +![jsDelivr hits (Plugin)](https://img.shields.io/jsdelivr/gh/hm/IAmParadox27/jellyfin-plugin-media-bar?style=for-the-badge&logo=javascript&logoColor=white&labelColor=F0DB4F&color=435274&cacheSeconds=3600) + +
**IMP UPDATE — We have dropped support for the normal CSS version (for now). _(It still works, but there will be no further updates till the fullscreen mode is stabilized)_** From 1618b0db12f71c87fd841daa550f3b027b551072 Mon Sep 17 00:00:00 2001 From: M0RPH3US Date: Mon, 19 Oct 2026 18:59:38 +0530 Subject: [PATCH 07/10] Update version number to v6.1.2 --- slideshowpure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slideshowpure.js b/slideshowpure.js index 60860fa..49f551f 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -1,5 +1,5 @@ /* - * Jellyfin Slideshow by M0RPH3US v6.1.1 + * Jellyfin Slideshow by M0RPH3US v6.1.2 */ const CONFIG = { From 9bf86e922bec4f47b53db1c2eb8e4ed96c946115 Mon Sep 17 00:00:00 2001 From: M0RPH3US Date: Mon, 19 Oct 2026 19:00:50 +0530 Subject: [PATCH 08/10] Update version number to v6.1.2 --- slideshowpure.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slideshowpure.css b/slideshowpure.css index 0bb8a05..cacc4ba 100644 --- a/slideshowpure.css +++ b/slideshowpure.css @@ -1,5 +1,5 @@ /* - * Jellyfin Slideshow by M0RPH3US v6.1.1 + * Jellyfin Slideshow by M0RPH3US v6.1.2 * * The Archivo Narrow webfont is linked from index.html, not @import-ed here: * an @import cannot start downloading until this stylesheet has itself been From 6ed7f144c6b4e0c341b9105c68a2ec142b26fc4e Mon Sep 17 00:00:00 2001 From: M0RPH3US Date: Wed, 21 Oct 2026 10:45:38 +0530 Subject: [PATCH 09/10] Update version number to v6.1.3 --- slideshowpure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slideshowpure.js b/slideshowpure.js index b7efcb3..6eaa207 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -1,5 +1,5 @@ /* - * Jellyfin Slideshow by M0RPH3US v6.1.2 + * Jellyfin Slideshow by M0RPH3US v6.1.3 */ const CONFIG = { From 557b31e8d8af8ef71f758993ded1d990b1193767 Mon Sep 17 00:00:00 2001 From: M0RPH3US Date: Wed, 21 Oct 2026 10:45:58 +0530 Subject: [PATCH 10/10] Update version number to v6.1.3 --- slideshowpure.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slideshowpure.css b/slideshowpure.css index cacc4ba..f9643c0 100644 --- a/slideshowpure.css +++ b/slideshowpure.css @@ -1,5 +1,5 @@ /* - * Jellyfin Slideshow by M0RPH3US v6.1.2 + * Jellyfin Slideshow by M0RPH3US v6.1.3 * * The Archivo Narrow webfont is linked from index.html, not @import-ed here: * an @import cannot start downloading until this stylesheet has itself been