Compare commits

..

34 Commits

Author SHA1 Message Date
CodeDevMLH
216dddad94 Update manifest.json for release v1.6.1.0 [skip ci] 2026-02-11 17:24:36 +00:00
CodeDevMLH
a6de148ca1 Bump version to 1.6.1.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 56s
2026-02-11 18:23:42 +01:00
CodeDevMLH
1f9378d74d Enhance video backdrop styling for TV layout and improve focus management in slideshow 2026-02-11 18:22:44 +01:00
CodeDevMLH
cc025779dc Update manifest.json for release v1.6.0.2 [skip ci] 2026-02-10 22:07:34 +00:00
CodeDevMLH
3d10fd59b5 Enhance manual trailer/video override instructions to support Jellyfin Item IDs and clarify usage
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 54s
2026-02-10 23:06:40 +01:00
CodeDevMLH
bf261eba96 Update manifest.json for release v1.6.0.2 [skip ci] 2026-02-10 21:44:36 +00:00
CodeDevMLH
e3116c30cf Bump version to 1.6.0.2
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 50s
2026-02-10 22:43:47 +01:00
CodeDevMLH
bb39c91d32 Enhance custom trailer URL handling to support Jellyfin Item IDs and standard URLs 2026-02-10 22:43:40 +01:00
CodeDevMLH
4e0c74614a Update manifest.json for release v1.6.0.1 [skip ci] 2026-02-10 21:27:25 +00:00
CodeDevMLH
b61bf92437 Bump version to 1.6.0.1 in project files and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-02-10 22:26:32 +01:00
CodeDevMLH
dfbd6ce964 Enable autoplay for video playback in SlideUtils 2026-02-10 22:26:28 +01:00
CodeDevMLH
f1cbcad177 Update manifest.json for release v1.6.0.0 [skip ci] 2026-02-10 21:18:26 +00:00
CodeDevMLH
feedd5d95f Bump version to 1.6.0.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 50s
2026-02-10 22:17:36 +01:00
CodeDevMLH
87d82cca15 Enhance trailer handling: support object format for trailer URLs and add widget referrer for YouTube embeds 2026-02-10 22:17:24 +01:00
CodeDevMLH
a70746e095 Update manifest.json for release v1.5.1.3 [skip ci] 2026-02-10 20:12:59 +00:00
CodeDevMLH
f32283e0bf Bump version to 1.5.1.3 and update changelog for release
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 54s
2026-02-10 21:12:05 +01:00
CodeDevMLH
8f3985f307 Update manifest.json for release v1.5.1.2 [skip ci] 2026-02-10 17:39:23 +00:00
CodeDevMLH
0b2817ecff Bump version to 1.5.1.2 and update changelog for release
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-02-10 18:38:31 +01:00
CodeDevMLH
84faee2db4 Update manifest.json for release v1.5.1.1 [skip ci] 2026-02-10 16:58:48 +00:00
CodeDevMLH
3efa07ec51 Bump version to 1.5.1.1 and update changelog for release
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 55s
2026-02-10 17:57:54 +01:00
CodeDevMLH
af603e8803 Enhance options in mediaBarEnhanced for seasonal content, local trailers, and sorting criteria [skip ci] 2026-02-10 01:57:51 +01:00
CodeDevMLH
fe63414e4b Update manifest.json for release v1.5.1.0 [skip ci] 2026-02-10 00:51:17 +00:00
CodeDevMLH
614c86083f Bump version to 1.5.1.0 and update changelog for iOS/MacOS playback fix
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-02-10 01:50:27 +01:00
CodeDevMLH
17b9e8921e Update manifest.json for release v1.5.0.28 [skip ci] 2026-02-10 00:35:42 +00:00
CodeDevMLH
5075226ba8 fix wording
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 56s
2026-02-10 01:34:47 +01:00
CodeDevMLH
443fb1008b Update version to 1.5.0.28 and enhance changelog with new features and fixes [skip ci] 2026-02-10 01:32:33 +01:00
CodeDevMLH
8f12140aad Update manifest.json for release v1.5.0.28 [skip ci] 2026-02-10 00:22:00 +00:00
CodeDevMLH
1469712bb5 reverted to 25
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 54s
2026-02-10 01:21:08 +01:00
CodeDevMLH
76ff03ac17 Update manifest.json for release v1.5.0.27 [skip ci] 2026-02-10 00:16:23 +00:00
CodeDevMLH
25b1ba5f2b Bump version to 1.5.0.26 and update changelog for recent changes
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 50s
2026-02-10 01:15:33 +01:00
CodeDevMLH
eed3ca1860 Update manifest.json for release v1.5.0.25 [skip ci] 2026-02-10 00:07:06 +00:00
CodeDevMLH
0f14577f5d Bump version to 1.5.0.25 and update changelog for recent changes
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-02-10 01:06:15 +01:00
CodeDevMLH
9999d6a633 Update manifest.json for release v1.5.0.24 [skip ci] 2026-02-09 23:53:21 +00:00
CodeDevMLH
a935fd7d5d Bump version to 1.5.0.24 and update changelog for recent changes
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-02-10 00:52:29 +01:00
5 changed files with 195 additions and 68 deletions

View File

@@ -123,8 +123,14 @@
<div class="fieldDescription" id="customMediaIdsDesc">Enter the IDs of the items you want to show in the slideshow.
You can separate them by new line or comma.
<br><br>
<b>Manual Trailer Override:</b> You can specify a YouTube URL for an item by adding it in
brackets: <br> <code>e.g. ID DESCRIPTION [https://youtu.be/...]</code> or <code>ID [https://youtu.be/...] DESCRIPTION</code>
<b>Manual Trailer/Video Override:</b> You can specify a YouTube URL <b>OR</b> a Jellyfin Item ID (e.g. for a Theme Video) for an item by adding it in
brackets: <br> <code>e.g. ID DESCRIPTION [https://youtu.be/...]</code> or <code>ID [Method] DESCRIPTION</code>.
<br>
Methods:
<ul>
<li><b>YouTube URL:</b> Play a remote trailer from YouTube.</li>
<li><b>Jellyfin Item ID (GUID):</b> Play the video of another library item (e.g. a Theme Video or Backdrop Video) using the native player.</li>
</ul>
<br><br>
You can also add a description after the ID using any separator like space, pipe
(|) or dash (-): <br>e.g. <code>ID DESCRIPTION</code> or <code>ID | DESCRIPTION</code>
@@ -182,8 +188,7 @@
</div>
<div class="selectContainer">
<label class="selectLabel" for="PreferredVideoQuality">Preferred YouTube Quality</label>
<select is="emby-select" id="PreferredVideoQuality" name="PreferredVideoQuality"
class="emby-select-withcolor emby-select">
<select is="emby-select" id="PreferredVideoQuality" name="PreferredVideoQuality" class="selectLayout emby-select-withcolor emby-select">
<option value="Auto">Auto (Smart)</option>
<option value="Maximum">Maximum (4K+)</option>
<option value="1080p">1080p</option>
@@ -285,7 +290,7 @@
<h2 class="sectionTitle">Content Sorting</h2>
<div class="selectContainer">
<label class="selectLabel" for="SortBy">Sort By</label>
<select is="emby-select" id="SortBy" name="SortBy" class="emby-select-withcolor emby-select">
<select is="emby-select" id="SortBy" name="SortBy" class="selectLayout emby-select-withcolor emby-select">
<option value="Random">Random</option>
<option value="Original">Original (Custom List Order)</option>
<option value="PremiereDate">Premiere Date</option>
@@ -299,7 +304,7 @@
</div>
<div class="selectContainer">
<label class="selectLabel" for="SortOrder">Sort Order</label>
<select is="emby-select" id="SortOrder" name="SortOrder" class="emby-select-withcolor emby-select">
<select is="emby-select" id="SortOrder" name="SortOrder" class="selectLayout emby-select-withcolor emby-select">
<option value="Ascending">Ascending</option>
<option value="Descending">Descending</option>
</select>

View File

@@ -12,7 +12,7 @@
<!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> -->
<Title>Jellyfin Media Bar Enhanced Plugin</Title>
<Authors>CodeDevMLH</Authors>
<Version>1.5.0.23</Version>
<Version>1.6.1.0</Version>
<RepositoryUrl>https://github.com/CodeDevMLH/jellyfin-plugin-media-bar-enhanced</RepositoryUrl>
</PropertyGroup>

View File

@@ -1,6 +1,6 @@
/*
* Jellyfin Slideshow by M0RPH3US v3.0.9
* Modified by CodeDevMLH v1.1.0.0
* Jellyfin Slideshow by M0RPH3US v4.0.1
* Modified by CodeDevMLH
*
* New features:
* - optional Trailer background video support
@@ -14,6 +14,9 @@
* - option to disable loading screen
* - option to put collection (boxsets) IDs into the slideshow to display their items
* - option to enable client-side settings (allow users to override settings locally on their device)
* - option to enable seasonal content (only show items that are relevant to the current season/holiday)
* - option to prefer local trailers (from the media item) over online sources
* - options to sort the content by various criteria (PremiereDate, ProductionYear, Random, Original order, etc.)
*/
@import url(https://fonts.googleapis.com/css2?family=Archivo+Narrow:ital,wght@0,400..700;1,400..700&display=swap);
@@ -161,7 +164,7 @@
.homeSectionsContainer {
position: relative;
margin-top: 65vh;
top: 65vh;
z-index: 6;
}
@@ -991,4 +994,16 @@
.dots-container .slide-counter {
margin: 0;
}
}
.layout-tv .video-backdrop {
top: -5%;
mask-image: linear-gradient(to top,
#fff0 2%,
rgb(0 0 0 / 0.5) 6%,
#000000 8%);
-webkit-mask-image: linear-gradient(to top,
#fff0 2%,
rgb(0 0 0 / 0.5) 6%,
#000000 8%);
}

View File

@@ -1,6 +1,6 @@
/*
* Jellyfin Slideshow by M0RPH3US v4.0.1
* Modified by CodeDevMLH v1.1.0.0
* Modified by CodeDevMLH
*
* New features:
* - optional Trailer background video support
@@ -14,6 +14,9 @@
* - option to disable loading screen
* - option to put collection (boxsets) IDs into the slideshow to display their items
* - option to enable client-side settings (allow users to override settings locally on their device)
* - option to enable seasonal content (only show items that are relevant to the current season/holiday)
* - option to prefer local trailers (from the media item) over online sources
* - options to sort the content by various criteria (PremiereDate, ProductionYear, Random, Original order, etc.)
*/
//Core Module Configuration
@@ -737,7 +740,11 @@ const SlideUtils = {
autoplay: 1,
controls: 1,
iv_load_policy: 3,
rel: 0
rel: 0,
playsinline: 1,
origin: window.location.origin,
widget_referrer: window.location.href,
enablejsapi: 1
}
});
});
@@ -1333,7 +1340,7 @@ const ApiUtils = {
/**
* Fetches the first local trailer for an item
* @param {string} itemId - Item ID
* @returns {Promise<string|null>} Stream URL or null
* @returns {Promise<Object|null>} Trailer data object {id, url} or null
*/
async fetchLocalTrailer(itemId) {
try {
@@ -1353,8 +1360,11 @@ const ApiUtils = {
const trailer = trailers[0];
const mediaSourceId = trailer.MediaSources && trailer.MediaSources[0] ? trailer.MediaSources[0].Id : trailer.Id;
// Construct stream URL
return `${STATE.jellyfinData.serverAddress}/Videos/${trailer.Id}/stream.mp4?Static=true&mediaSourceId=${mediaSourceId}&api_key=${STATE.jellyfinData.accessToken}`;
// Return object with ID and URL
return {
id: trailer.Id,
url: `${STATE.jellyfinData.serverAddress}/Videos/${trailer.Id}/stream.mp4?Static=true&mediaSourceId=${mediaSourceId}&api_key=${STATE.jellyfinData.accessToken}`
};
}
return null;
} catch (error) {
@@ -1566,8 +1576,24 @@ const SlideCreator = {
// 1a. Custom URL override
if (STATE.slideshow.customTrailerUrls && STATE.slideshow.customTrailerUrls[itemId]) {
trailerUrl = STATE.slideshow.customTrailerUrls[itemId];
console.log(`Using custom trailer URL for ${itemId}: ${trailerUrl}`);
const customValue = STATE.slideshow.customTrailerUrls[itemId];
// Check if the custom value is a Jellyfin Item ID (GUID)
const guidMatch = customValue.match(/^([0-9a-f]{32})$/i);
if (guidMatch) {
const videoId = guidMatch[1];
console.log(`Using custom local video ID for ${itemId}: ${videoId}`);
trailerUrl = {
id: videoId,
url: `${STATE.jellyfinData.serverAddress}/Videos/${videoId}/stream.mp4?Static=true&api_key=${STATE.jellyfinData.accessToken}`
};
} else {
// Assume it's a standard URL (YouTube, etc.)
trailerUrl = customValue;
console.log(`Using custom trailer URL for ${itemId}: ${trailerUrl}`);
}
}
// 1b. Check Local Trailer if preferred
else if (CONFIG.preferLocalTrailers && item.LocalTrailerCount > 0 && item.localTrailerUrl) {
@@ -1592,12 +1618,17 @@ const SlideCreator = {
let videoId = null;
try {
const urlObj = new URL(trailerUrl);
if (urlObj.hostname.includes('youtube.com') || urlObj.hostname.includes('youtu.be')) {
let urlToCheck = trailerUrl;
if (typeof trailerUrl === 'object' && trailerUrl.url) {
urlToCheck = trailerUrl.url;
}
const urlObjChecked = new URL(urlToCheck);
if (urlObjChecked.hostname.includes('youtube.com') || urlObjChecked.hostname.includes('youtu.be')) {
isYoutube = true;
videoId = urlObj.searchParams.get('v');
if (!videoId && urlObj.hostname.includes('youtu.be')) {
videoId = urlObj.pathname.substring(1);
videoId = urlObjChecked.searchParams.get('v');
if (!videoId && urlObjChecked.hostname.includes('youtu.be')) {
videoId = urlObjChecked.pathname.substring(1);
}
}
} catch (e) {
@@ -1626,7 +1657,11 @@ const SlideCreator = {
fs: 0,
iv_load_policy: 3,
rel: 0,
loop: 0
loop: 0,
playsinline: 1,
origin: window.location.origin,
widget_referrer: window.location.href,
enablejsapi: 1
};
// Determine video quality
@@ -1739,7 +1774,7 @@ const SlideCreator = {
const videoAttributes = {
className: "backdrop video-backdrop",
src: trailerUrl,
src: (typeof trailerUrl === 'object' ? trailerUrl.url : trailerUrl),
autoplay: false,
preload: "auto",
loop: false,
@@ -2030,11 +2065,20 @@ const SlideCreator = {
/**
* Creates a trailer button
* @param {string} url - Trailer URL
* @param {string|Object} trailerInfo - Trailer URL string or object {id, url}
* @returns {HTMLElement} Trailer button element
*/
createTrailerButton(url) {
createTrailerButton(trailerInfo) {
const trailerText = LocalizationUtils.getLocalizedString('Trailer', 'Trailer');
let url = trailerInfo;
let localTrailerId = null;
if (typeof trailerInfo === 'object' && trailerInfo !== null) {
url = trailerInfo.url;
localTrailerId = trailerInfo.id;
}
return SlideUtils.createElement("button", {
className: "detailButton trailer-button",
innerHTML: `<span class="material-icons">movie</span> <span class="trailer-text">${trailerText}</span>`,
@@ -2042,7 +2086,13 @@ const SlideCreator = {
onclick: (e) => {
e.preventDefault();
e.stopPropagation();
SlideUtils.openVideoModal(url);
if (localTrailerId) {
// Play local trailer using native player
ApiUtils.playItem(localTrailerId);
} else {
SlideUtils.openVideoModal(url);
}
},
});
},
@@ -2187,6 +2237,16 @@ const SlideshowManager = {
let previousVisibleSlide;
try {
const container = SlideUtils.getOrCreateSlidesContainer();
const activeElement = document.activeElement;
let focusSelector = null;
if (container.contains(activeElement)) {
if (activeElement.classList.contains('play-button')) focusSelector = '.play-button';
else if (activeElement.classList.contains('detail-button')) focusSelector = '.detail-button';
else if (activeElement.classList.contains('favorite-button')) focusSelector = '.favorite-button';
else if (activeElement.classList.contains('trailer-button')) focusSelector = '.trailer-button';
}
const totalItems = STATE.slideshow.totalItems;
index = Math.max(0, Math.min(index, totalItems - 1));
@@ -2214,6 +2274,15 @@ const SlideshowManager = {
}
currentSlide.classList.add("active");
if (focusSelector) {
const target = currentSlide.querySelector(focusSelector);
if (target) {
requestAnimationFrame(() => {
target.focus();
});
}
}
// Manage Video Playback: Stop others, Play current
@@ -2705,61 +2774,75 @@ const SlideshowManager = {
}
const activeElement = document.activeElement;
const isTvMode = window.layoutManager && window.layoutManager.tv;
const isSlideshowFocused = container.contains(activeElement) || activeElement === container || (!isTvMode && activeElement === document.body);
const isInputElement = activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable);
if (isInputElement) {
return;
}
const isTvDevice = window.browser && window.browser.tv;
const isTvLayout = window.layoutManager && window.layoutManager.tv;
const hasTvClass = document.documentElement.classList.contains('layout-tv') || document.body.classList.contains('layout-tv');
const isTvMode = isTvDevice || isTvLayout || hasTvClass;
// Check if video player is open
// Check Focus State
const isBodyFocused = activeElement === document.body;
const hasDirectFocus = container.contains(activeElement) || activeElement === container;
// Determine if we should handle navigation keys (Arrows, Space, M)
// TV Mode: Strict focus required (must be on slideshow)
// Desktop Mode: Loose focus allowed (slideshow OR body/nothing focused)
const canControlSlideshow = isTvMode ? hasDirectFocus : (hasDirectFocus || isBodyFocused);
// Check for Input Fields (always ignore typing)
const isInputElement = activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable);
if (isInputElement) return;
// Check active video players (ignore if video is playing/overlay is open)
const videoPlayer = document.querySelector('.videoPlayerContainer');
const trailerPlayer = document.querySelector('.youtubePlayerContainer');
if ((videoPlayer && !videoPlayer.classList.contains('hide')) || (trailerPlayer && !trailerPlayer.classList.contains('hide'))) {
return;
}
const isVideoOpen = (videoPlayer && !videoPlayer.classList.contains('hide')) || (trailerPlayer && !trailerPlayer.classList.contains('hide'));
if (isVideoOpen) return;
switch (e.key) {
case "ArrowRight":
if (isTvMode && !isSlideshowFocused) return;
SlideshowManager.nextSlide();
e.preventDefault();
if (canControlSlideshow) {
SlideshowManager.nextSlide();
e.preventDefault();
}
break;
case "ArrowLeft":
if (isTvMode && !isSlideshowFocused) return;
SlideshowManager.prevSlide();
e.preventDefault();
if (canControlSlideshow) {
SlideshowManager.prevSlide();
e.preventDefault();
}
break;
case " ": // Space bar
if (isTvMode && !isSlideshowFocused) return;
this.togglePause();
e.preventDefault();
if (canControlSlideshow) {
this.togglePause();
e.preventDefault();
}
break;
case "m": // Mute toggle
case "M":
if (isTvMode && !isSlideshowFocused) return;
this.toggleMute();
e.preventDefault();
if (canControlSlideshow) {
this.toggleMute();
e.preventDefault();
}
break;
case "Enter":
if (!isSlideshowFocused) return;
const currentItemId = STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex];
if (currentItemId) {
if (window.Emby && window.Emby.Page) {
Emby.Page.show(
`/details?id=${currentItemId}&serverId=${STATE.jellyfinData.serverId}`
);
} else {
window.location.href = `#/details?id=${currentItemId}&serverId=${STATE.jellyfinData.serverId}`;
}
// Enter always requires direct focus on the slideshow to avoid conflicts
if (hasDirectFocus) {
const currentItemId = STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex];
if (currentItemId) {
if (window.Emby && window.Emby.Page) {
Emby.Page.show(
`/details?id=${currentItemId}&serverId=${STATE.jellyfinData.serverId}`
);
} else {
window.location.href = `#/details?id=${currentItemId}&serverId=${STATE.jellyfinData.serverId}`;
}
}
e.preventDefault();
}
e.preventDefault();
break;
}
});

View File

@@ -9,12 +9,36 @@
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/raw/branch/main/logo.png",
"versions": [
{
"version": "1.5.0.23",
"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",
"version": "1.6.1.0",
"changelog": "- fix tv mode issue",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.5.0.23/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "f4d3c0e717b1662dbfa3831a71827505",
"timestamp": "2026-02-09T23:34:10Z"
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.6.1.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "646e58f7723dab118e767a57d62004c9",
"timestamp": "2026-02-11T17:24:36Z"
},
{
"version": "1.6.0.2",
"changelog": "- add local trailer support on trailer button\nfix: iOS/MacOS playback issue?",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.6.0.2/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "cdd0208f8cc4f4b04f50e7138e508370",
"timestamp": "2026-02-10T22:07:33Z"
},
{
"version": "1.5.1.3",
"changelog": "- fix: iOS/MacOS playback issue?",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.5.1.3/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "9d9dbed453673d4b78acf2adaaaee126",
"timestamp": "2026-02-10T20:12:59Z"
},
{
"version": "1.5.0.28",
"changelog": "- fix: Keyboard controls in TV mode\n- Add sorting options for content\n- Add local trailer support\n- fix performance issue\n- Update mediaBarEnhanced.js and mediaBarEnhanced.css with version 4.0.1 from upstream repo",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.5.0.28/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "0261ff27be18d48cefa5078706954240",
"timestamp": "2026-02-10T00:35:41Z"
},
{
"version": "1.3.0.3",