Merge branch 'MakD:main' into main

This commit is contained in:
Zach Ross-Clyne
2026-10-28 10:28:46 +00:00
committed by GitHub
3 changed files with 90 additions and 22 deletions
+5 -1
View File
@@ -66,8 +66,12 @@ Credits for this plugin go to @MakD for his original work and to @BobHasNoSoul a
# 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)
<div align="center">
![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)
</div>
**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)_**
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Jellyfin Slideshow by M0RPH3US v6.0.0
* 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
+84 -20
View File
@@ -1,5 +1,5 @@
/*
* Jellyfin Slideshow by M0RPH3US v6.1.0
* Jellyfin Slideshow by M0RPH3US v6.1.3
*/
const CONFIG = {
@@ -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,
@@ -339,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();
@@ -412,6 +419,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;
@@ -1889,10 +1907,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;
}
@@ -2894,7 +2913,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 {
@@ -2924,13 +2946,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),
@@ -2961,7 +2988,7 @@ const SlideshowManager = {
container.appendChild(dotsContainer);
}
dotsContainer.replaceChildren();
dotsContainer.textContent = "";
const count = Math.min(STATE.slideshow.totalItems, CONFIG.maxDots);
@@ -3214,13 +3241,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();
}
}
@@ -3686,6 +3713,8 @@ const SlideshowManager = {
STATE.slideshow.isVideoPlaying = true;
this.setTrailerVisible(itemId, trailerContainer, true);
STATE.slideshow.slideInterval?.stop();
this.setProgressRunning(false);
},
onTrailerEnded(itemId, trailerContainer) {
@@ -3694,6 +3723,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;
@@ -3708,7 +3759,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);
}
},
@@ -3717,10 +3774,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");
@@ -3728,9 +3781,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() {
@@ -3958,6 +4017,7 @@ const SettingsPanel = {
step: 5,
format: (value) => `${value}%`,
},
{ key: "trailerCaptions", label: "Trailer captions", type: "switch" },
{
key: "trailerLibraries",
label: "Trailers from",
@@ -4399,9 +4459,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]) {
@@ -4433,14 +4495,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;
}