diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs
index 725c5c2..894e1ed 100644
--- a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs
+++ b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs
@@ -36,6 +36,7 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Configuration
public bool EnableSeasonalContent { get; set; } = false;
public bool IsEnabled { get; set; } = true;
public bool EnableClientSideSettings { get; set; } = false;
+ public bool EnableUpstreamTrailerLayout { get; set; } = false;
public string SortBy { get; set; } = "Random";
public string SortOrder { get; set; } = "Ascending";
}
diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html
index b4eb512..b263099 100644
--- a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html
+++ b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html
@@ -73,6 +73,14 @@
Delay slide transition until trailer finishes.
+
+
+
+ Enable Upstream Trailer Layout
+
+
Use the upstream (original) layout for trailers. This renders the video inside a container overlaying the backdrop, instead of replacing it to support full-width video.
+
false -->
Jellyfin Media Bar Enhanced Plugin
CodeDevMLH
- 1.5.0.10
+ 1.5.0.11
https://github.com/CodeDevMLH/jellyfin-plugin-media-bar-enhanced
diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css
index 2911e93..3b6b0f4 100644
--- a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css
+++ b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.css
@@ -343,6 +343,45 @@
z-index: 1;
}
+.video-container {
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 100%;
+ width: 0;
+ z-index: 2;
+ overflow: hidden;
+ transition:
+ width 0.5s ease-in-out,
+ opacity 0.5s ease-in-out;
+ opacity: 0;
+ pointer-events: none;
+ mask-image:
+ linear-gradient(to right, transparent 10%, black 30%),
+ linear-gradient(to top, transparent 2%, rgba(0, 0, 0, 0.5) 6%, black 8%);
+
+ -webkit-mask-image:
+ linear-gradient(to right, transparent 10%, black 30%),
+ linear-gradient(to top, transparent 2%, rgba(0, 0, 0, 0.5) 6%, black 8%);
+
+ mask-composite: intersect;
+ -webkit-mask-composite: source-in;
+}
+
+.video-container.active {
+ opacity: 1;
+ pointer-events: auto;
+ width: 100% !important; /* Force width when active, matching upstream logic behavior */
+}
+
+/* Ensure video inside container fills it */
+.video-container iframe,
+.video-container video {
+ width: 100%;
+ height: 100%;
+ border: none;
+}
+
.backdrop-container {
position: absolute;
top: 0%;
@@ -389,6 +428,20 @@
#000000 8%);
}
+.backdrop.with-video {
+ width: 100%;
+ mask-image:
+ linear-gradient(to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, #000000 8%),
+ linear-gradient(to right, black 30%, transparent 85%);
+
+ -webkit-mask-image:
+ linear-gradient(to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, #000000 8%),
+ linear-gradient(to right, black 30%, transparent 85%);
+
+ mask-composite: source-in;
+ -webkit-mask-composite: source-in;
+}
+
.backdrop-overlay {
position: absolute;
top: 0;
diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js
index b7d218b..2029142 100644
--- a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js
+++ b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js
@@ -52,6 +52,7 @@ const CONFIG = {
customMediaIds: "",
enableLoadingScreen: true,
enableClientSideSettings: false,
+ enableUpstreamTrailerLayout: false,
sortBy: "Random",
sortOrder: "Ascending",
};
@@ -1537,6 +1538,7 @@ const SlideCreator = {
let backdrop;
let isVideo = false;
+ let hasUpstreamVideo = false;
let trailerUrl = null;
// 1. Check for Remote/Local Trailers
@@ -1584,13 +1586,31 @@ const SlideCreator = {
if (isYoutube && videoId) {
isVideo = true;
- // Create container for YouTube API
const videoClass = CONFIG.fullWidthVideo ? "video-backdrop-full" : "video-backdrop-default";
- backdrop = SlideUtils.createElement("div", {
- className: `backdrop video-backdrop ${videoClass}`,
- id: `youtube-player-${itemId}`
- });
+ if (CONFIG.enableUpstreamTrailerLayout) {
+ // UPSTREAM LAYOUT: Wrapper Container
+ const videoContainer = SlideUtils.createElement("div", {
+ className: "video-container",
+ id: `video-container-${itemId}`
+ });
+
+ const playerDiv = SlideUtils.createElement("div", {
+ id: `youtube-player-${itemId}`
+ });
+
+ videoContainer.appendChild(playerDiv);
+ slide.appendChild(videoContainer); // Append container first
+
+ isVideo = false;
+ hasUpstreamVideo = true;
+ } else {
+ // ENHANCED LAYOUT: Direct Backdrop Replacement
+ backdrop = SlideUtils.createElement("div", {
+ className: `backdrop video-backdrop ${videoClass}`,
+ id: `youtube-player-${itemId}`
+ });
+ }
// Initialize YouTube Player
SlideUtils.loadYouTubeIframeAPI().then(() => {
@@ -1676,6 +1696,15 @@ const SlideCreator = {
}
},
'onStateChange': (event) => {
+ const slide = document.querySelector(`.slide[data-item-id="${itemId}"]`);
+ const videoContainer = slide ? slide.querySelector('.video-container') : null;
+
+ if (event.data === YT.PlayerState.PLAYING) {
+ if (videoContainer) videoContainer.classList.add('active');
+ } else {
+ if (videoContainer) videoContainer.classList.remove('active');
+ }
+
if (event.data === YT.PlayerState.ENDED) {
if (CONFIG.waitForTrailerToEnd) {
SlideshowManager.nextSlide();
@@ -1737,12 +1766,30 @@ const SlideCreator = {
SlideshowManager.nextSlide();
}
});
+
+ if (CONFIG.enableUpstreamTrailerLayout) {
+ // Wrap in container
+ const videoContainer = SlideUtils.createElement("div", {
+ className: "video-container",
+ id: `video-container-${itemId}`
+ });
+ backdrop.style.position = "";
+ videoContainer.appendChild(backdrop);
+ slide.appendChild(videoContainer);
+
+ isVideo = false;
+ hasUpstreamVideo = true;
+
+ backdrop.addEventListener('play', () => videoContainer.classList.add('active'));
+ backdrop.addEventListener('pause', () => videoContainer.classList.remove('active'));
+ backdrop.addEventListener('ended', () => videoContainer.classList.remove('active'));
+ }
}
}
if (!isVideo) {
backdrop = SlideUtils.createElement("img", {
- className: "backdrop high-quality",
+ className: hasUpstreamVideo ? "backdrop high-quality with-video" : "backdrop high-quality",
src: this.buildImageUrl(item, "Backdrop", 0, serverAddress, 60),
alt: LocalizationUtils.getLocalizedString('Backdrop', 'Backdrop'),
loading: "eager",
@@ -2655,18 +2702,6 @@ const SlideshowManager = {
return;
}
- // Only trap keys if focus is on body (neutral) or inside our container.
- // To allow standard TV navigation to work for other elements (e.g. library cards).
- const activeEl = document.activeElement;
- const isBody = activeEl === document.body || !activeEl;
- const isInContainer = container.contains(activeEl) || activeEl === container;
-
- if (!isBody && !isInContainer) {
- return;
- }
-
- const focusElement = document.activeElement;
-
switch (e.key) {
case "ArrowRight":
SlideshowManager.nextSlide();
diff --git a/manifest.json b/manifest.json
index 67b8ad7..3f40806 100644
--- a/manifest.json
+++ b/manifest.json
@@ -9,7 +9,7 @@
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/raw/branch/main/logo.png",
"versions": [
{
- "version": "1.5.0.10",
+ "version": "1.5.0.11",
"changelog": "- fix: keyboard controls in TV mode\n- Add sorting options for content\n- Update mediaBarEnhanced.js and mediaBarEnhanced.css with version 4.0.1 from original repo",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.5.0.10/Jellyfin.Plugin.MediaBarEnhanced.zip",