mirror of
https://github.com/IAmParadox27/jellyfin-plugin-media-bar.git
synced 2026-11-05 07:42:46 +01:00
Merge branch 'MakD:main' into main
This commit is contained in:
@@ -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
|
||||
|
||||

|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||
|
||||
</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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user