diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs
index 2cffe87..0e2c015 100644
--- a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs
+++ b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs
@@ -49,6 +49,8 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Configuration
public bool IncludeWatchedContent { get; set; } = false;
public string SortBy { get; set; } = "Random";
public string SortOrder { get; set; } = "Ascending";
+ public int BackdropVideoDelay { get; set; } = 0;
+ public bool ConstrainPlotWidth { get; set; } = false;
public bool EnableCustomOverlay { get; set; } = false;
public string CustomOverlayText { get; set; } = "";
diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html
index 5a5e19f..0621868 100644
--- a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html
+++ b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html
@@ -418,6 +418,13 @@
on
mobile devices. (looks bad on desktops)
+
+
+
+ Constrain Plot Width
+
+
Align the description text left to match the logo width, preventing it from crossing the entire screen. (Increases the text limit to 3 lines instead of 2).
+
+
Retry Interval
@@ -663,7 +675,8 @@
'MaxDaysRecent', 'ExcludeSeasonalContent', 'HideArrowsOnMobile',
'EnableCustomOverlay', 'CustomOverlayText', 'CustomOverlayImageUrl',
'CustomOverlayStyle', 'CustomOverlayImageStyle', 'CustomOverlayPriority',
- 'CustomOverlayPositionX', 'CustomOverlayPositionY', 'CustomOverlayScale'
+ 'CustomOverlayPositionX', 'CustomOverlayPositionY', 'CustomOverlayScale',
+ 'BackdropVideoDelay', 'ConstrainPlotWidth'
];
// Manual mapping for MediaBarIsEnabled -> IsEnabled, to avoid conflicts with other plugins
@@ -908,7 +921,8 @@
'MaxDaysRecent', 'ExcludeSeasonalContent', 'HideArrowsOnMobile',
'EnableCustomOverlay', 'CustomOverlayText', 'CustomOverlayImageUrl',
'CustomOverlayStyle', 'CustomOverlayImageStyle', 'CustomOverlayPriority',
- 'CustomOverlayPositionX', 'CustomOverlayPositionY', 'CustomOverlayScale'
+ 'CustomOverlayPositionX', 'CustomOverlayPositionY', 'CustomOverlayScale',
+ 'BackdropVideoDelay', 'ConstrainPlotWidth'
];
keys.forEach(function (key) {
diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css
index 9a5788f..c884fae 100644
--- a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css
+++ b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css
@@ -489,6 +489,15 @@
overflow: hidden;
}
+.plot-container.constrained-plot {
+ width: 40%;
+}
+
+.plot-container.constrained-plot .plot {
+ line-clamp: 3;
+ -webkit-line-clamp: 3;
+}
+
.genre {
display: flex;
gap: 5px;
diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js
index c9c8686..78633f3 100644
--- a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js
+++ b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js
@@ -1,4 +1,4 @@
-/*
+/*
* Jellyfin Slideshow by M0RPH3US v4.0.1
* Modified by CodeDevMLH
*
@@ -2055,7 +2055,7 @@ const SlideCreator = {
SlideUtils.truncateText(plotElement, CONFIG.maxPlotLength);
const plotContainer = SlideUtils.createElement("div", {
- className: "plot-container",
+ className: "plot-container" + (CONFIG.constrainPlotWidth ? " constrained-plot" : ""),
});
plotContainer.appendChild(plotElement);
@@ -2443,6 +2443,11 @@ const SlideshowManager = {
STATE.slideshow.isTransitioning = true;
+ if (STATE.slideshow.backdropVideoTimeout) {
+ clearTimeout(STATE.slideshow.backdropVideoTimeout);
+ STATE.slideshow.backdropVideoTimeout = null;
+ }
+
let previousVisibleSlide;
try {
const container = SlideUtils.getOrCreateSlidesContainer();
@@ -2534,11 +2539,13 @@ const SlideshowManager = {
}
if (videoBackdrop) {
+ // preload logic
if (videoBackdrop.tagName === 'VIDEO') {
// Restore src from data-src if it was deactivated to release connections
const lazySrc = videoBackdrop.getAttribute('data-src');
if (lazySrc && !videoBackdrop.src) {
videoBackdrop.src = lazySrc;
+ videoBackdrop.load(); // Force pre-buffering
}
videoBackdrop.currentTime = 0;
@@ -2547,22 +2554,11 @@ const SlideshowManager = {
if (!STATE.slideshow.isMuted) {
videoBackdrop.volume = 0.4;
}
-
- videoBackdrop.play().catch(e => {
- // Check if it actually started playing after a short delay (handling autoplay blocks)
- setTimeout(() => {
- if (videoBackdrop.paused && currentSlide.classList.contains('active')) {
- console.warn("🎬 Media Bar:", `Autoplay blocked for ${currentItemId}, attempting muted fallback`);
- videoBackdrop.muted = true;
- videoBackdrop.play().catch(err => console.error("🎬 Media Bar:", "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({
+ if (player && typeof player.cueVideoById === 'function' && player._videoId) {
+ // Use cueVideoById to buffer video without auto-playing it
+ player.cueVideoById({
videoId: player._videoId,
startSeconds: player._startTime || 0,
endSeconds: player._endTime
@@ -2574,25 +2570,52 @@ const SlideshowManager = {
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("🎬 Media Bar:", "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();
}
}
+
+ // play logic
+ const playVideoLogic = () => {
+ if (!currentSlide.classList.contains('active')) return;
+
+ if (videoBackdrop.tagName === 'VIDEO') {
+ videoBackdrop.play().catch(e => {
+ // Check if it actually started playing after a short delay (handling autoplay blocks)
+ setTimeout(() => {
+ if (videoBackdrop.paused && currentSlide.classList.contains('active')) {
+ console.warn("🎬 Media Bar:", `Autoplay blocked for ${currentItemId}, attempting muted fallback`);
+ videoBackdrop.muted = true;
+ videoBackdrop.play().catch(err => console.error("🎬 Media Bar:", "Muted fallback failed", err));
+ }
+ }, 1000);
+ });
+ } else if (STATE.slideshow.videoPlayers && STATE.slideshow.videoPlayers[currentItemId]) {
+ const player = STATE.slideshow.videoPlayers[currentItemId];
+ if (player && typeof player.playVideo === 'function') {
+ player.playVideo();
+
+ // 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("🎬 Media Bar:", "YouTube didn't start playback, retrying muted...");
+ player.mute();
+ player.playVideo();
+ }
+ }, 1000);
+ }
+ }
+ };
+
+ if (CONFIG.backdropVideoDelay > 0) {
+ STATE.slideshow.backdropVideoTimeout = setTimeout(playVideoLogic, CONFIG.backdropVideoDelay);
+ } else {
+ playVideoLogic();
+ }
}
const enableAnimations = MediaBarEnhancedSettingsManager.getSetting('slideAnimations', CONFIG.slideAnimationEnabled);