Compare commits

..

49 Commits

Author SHA1 Message Date
CodeDevMLH
b08a93718e Bump version to 1.8.1.5
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 47s
2026-03-24 00:43:37 +01:00
CodeDevMLH
eecb81d59a Implement play signal management for slideshow video playback 2026-03-24 00:43:17 +01:00
CodeDevMLH
664eecf779 Enhance video playback logic by introducing a play signal check for active slides 2026-03-24 00:38:04 +01:00
CodeDevMLH
c833a94c3f Update manifest.json for release v1.8.1.4 [skip ci] 2026-03-23 23:18:11 +00:00
CodeDevMLH
b1c39b4b38 Bump version to 1.8.1.4
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 45s
2026-03-24 00:17:24 +01:00
CodeDevMLH
5e398d06a8 Add backdrop video delay and plot width constraint options; improve autoplay handling 2026-03-24 00:17:05 +01:00
CodeDevMLH
23a73543c9 Update manifest.json for release v1.8.1.3 [skip ci] 2026-03-23 22:47:55 +00:00
CodeDevMLH
8804048c61 Bump version to 1.8.1.3
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 46s
2026-03-23 23:47:09 +01:00
CodeDevMLH
992f854dd2 Add configuration options for backdrop video delay and plot width constraint 2026-03-23 23:37:47 +01:00
CodeDevMLH
c30300d1ba Update manifest.json for release v1.8.1.2 [skip ci] 2026-03-23 17:35:37 +00:00
CodeDevMLH
387f0dd26f Bump version to 1.8.1.2
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 46s
2026-03-23 18:34:50 +01:00
CodeDevMLH
71d9cddebb Add failsafe to remove loading screen after 15 seconds to prevent infinite lockouts 2026-03-23 18:34:33 +01:00
CodeDevMLH
9abcdf522d Fix slides container initialization to create a dummy element if not present 2026-03-23 18:26:18 +01:00
CodeDevMLH
8c703ce171 Update manifest.json for release v1.8.1.1 [skip ci] 2026-03-23 16:42:42 +00:00
CodeDevMLH
a40ee4a40d Bump version to 1.8.1.1 and update changelog for mobile pagination fix
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 46s
2026-03-23 17:41:55 +01:00
CodeDevMLH
d7e9238e21 Add padding to the slides container for improved layout 2026-03-23 17:41:40 +01:00
CodeDevMLH
a296caf70d Update manifest.json for release v1.8.1.0 [skip ci] 2026-03-23 16:28:17 +00:00
CodeDevMLH
86e9968243 bump version 1.8.1.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 55s
2026-03-23 17:27:08 +01:00
CodeDevMLH
e9fe356cee Enhance slideshow responsiveness by adjusting dot display for small screens 2026-03-23 17:25:48 +01:00
CodeDevMLH
e25a99439a Update manifest.json for release v1.8.0.0 [skip ci] 2026-03-11 01:38:44 +00:00
CodeDevMLH
ba2ad8f2cc Bump version to 1.8.0.0 in project files and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 45s
2026-03-11 02:37:57 +01:00
CodeDevMLH
5274e61204 Update manifest.json for release v1.7.2.16 [skip ci] 2026-03-11 00:46:03 +00:00
CodeDevMLH
93919c08ef Bump version to 1.7.2.16 in project files and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 46s
2026-03-11 01:45:14 +01:00
CodeDevMLH
c80de82a76 Refactor clear image button styling in configPage.html for improved usability 2026-03-11 01:44:59 +01:00
CodeDevMLH
c541e1e543 Update manifest.json for release v1.7.2.15 [skip ci] 2026-03-11 00:31:08 +00:00
CodeDevMLH
2c7dae4d6d Bump version to 1.7.2.15 in project files and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 47s
2026-03-11 01:30:19 +01:00
CodeDevMLH
3869a089a1 Improve styling for clear image button in configPage.html 2026-03-11 01:30:06 +01:00
CodeDevMLH
014c908a1e Update manifest.json for release v1.7.2.14 [skip ci] 2026-03-11 00:20:42 +00:00
CodeDevMLH
f6cecf6a9e Bump version to 1.7.2.14 in project files and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 45s
2026-03-11 01:19:56 +01:00
CodeDevMLH
81e4643652 Improve styling for clear image buttons in configPage.html 2026-03-11 01:19:43 +01:00
CodeDevMLH
1b65843b20 Update manifest.json for release v1.7.2.13 [skip ci] 2026-03-11 00:13:26 +00:00
CodeDevMLH
a70690c0de Bump version to 1.7.2.13 in project files and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 44s
2026-03-11 01:12:40 +01:00
CodeDevMLH
1e21286a66 Refactor seasonal image clear button styling in configPage.html 2026-03-11 01:12:28 +01:00
CodeDevMLH
90a64d49ed Update manifest.json for release v1.7.2.12 [skip ci] 2026-03-11 00:05:56 +00:00
CodeDevMLH
009798ea06 Bump version to 1.7.2.12 in project files and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-03-11 01:05:03 +01:00
CodeDevMLH
53f0f3c401 Refactor seasonal image clear button visibility and styling in configPage.html 2026-03-11 01:04:50 +01:00
CodeDevMLH
81dad8c6c6 Update manifest.json for release v1.7.2.11 [skip ci] 2026-03-10 23:58:38 +00:00
CodeDevMLH
66fe4dc3cb Bump version to 1.7.2.11 in project files and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-03-11 00:57:45 +01:00
CodeDevMLH
7be968d1c6 test 2026-03-11 00:57:31 +01:00
CodeDevMLH
2d492f3fed Update manifest.json for release v1.7.2.10 [skip ci] 2026-03-10 23:50:23 +00:00
CodeDevMLH
2160e2963c Bump version to 1.7.2.10 in project files and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 44s
2026-03-11 00:49:37 +01:00
CodeDevMLH
dc64bbe345 Improve styling and positioning of the clear image button in configPage.html 2026-03-11 00:49:25 +01:00
CodeDevMLH
3a61b3e548 Update manifest.json for release v1.7.2.9 [skip ci] 2026-03-10 23:38:07 +00:00
CodeDevMLH
950116a775 Bump version to 1.7.2.9 in project files and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 46s
2026-03-11 00:37:17 +01:00
CodeDevMLH
b9040c20fc Update field description and adjust seasonal dropzone height in configPage.html 2026-03-11 00:37:02 +01:00
CodeDevMLH
8388463880 delete example configPage 2026-03-11 00:36:58 +01:00
CodeDevMLH
233e569c94 Update field description for image upload in configPage.html 2026-03-11 00:27:32 +01:00
CodeDevMLH
93c265ffed Add authorization to overlay image upload, retrieval, and deletion endpoints 2026-03-11 00:27:26 +01:00
CodeDevMLH
8f4dfa31c8 Remove fallback logic for resource stream retrieval in MediaBarEnhancedController 2026-03-11 00:27:21 +01:00
9 changed files with 158 additions and 2982 deletions

View File

@@ -44,18 +44,6 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api
var stream = assembly.GetManifestResourceStream(resourceName);
// if (stream == null)
// {
// // Try fallback/debug matching
// var allNames = assembly.GetManifestResourceNames();
// var match = Array.Find(allNames, n => n.EndsWith(resourcePath, StringComparison.OrdinalIgnoreCase));
// if (match != null)
// {
// stream = assembly.GetManifestResourceStream(match);
// }
// }
if (stream == null)
{
return NotFound($"Resource not found: {resourceName}");

View File

@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MediaBrowser.Common.Configuration;
using Microsoft.AspNetCore.Authorization;
namespace Jellyfin.Plugin.MediaBarEnhanced.Api
{
@@ -26,7 +27,7 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api
/// <summary>
/// Uploads a new custom overlay image.
/// </summary>
// [Microsoft.AspNetCore.Authorization.Authorize]
[Authorize(Policy = "RequiresElevation")]
[HttpPost("OverlayImage")]
[Consumes("multipart/form-data")]
public async Task<IActionResult> UploadImage([FromForm] IFormFile file, [FromQuery] string? filename = null)
@@ -40,7 +41,7 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api
string extension = Path.GetExtension(file.FileName);
if (string.IsNullOrWhiteSpace(extension)) extension = ".jpg";
// Delete any existing file with this prefix before saving the new one (as extensions might differ)
// Delete any existing file with this prefix before saving the new one
string prefix = string.IsNullOrWhiteSpace(filename) ? "custom_overlay_image_global" : $"custom_overlay_image_{filename}";
try
@@ -60,9 +61,6 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api
string targetFileName = $"{prefix}{extension}";
string targetPath = Path.Combine(_imageDirectory, targetFileName);
// Delete is not strictly necessary and can cause locking issues if someone is currently reading it.
// FileMode.Create will truncate the file if it exists, effectively overwriting it.
// We use FileShare.None to ensure we have exclusive write access, but handle potential IOExceptions gracefully.
using (var stream = new FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
await file.CopyToAsync(stream).ConfigureAwait(false);
@@ -82,7 +80,6 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api
/// <summary>
/// Retrieves the custom overlay image.
/// </summary>
// [Microsoft.AspNetCore.Authorization.Authorize]
[HttpGet("OverlayImage")]
public IActionResult GetImage([FromQuery] string? filename = null)
{
@@ -98,7 +95,6 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api
string targetPath = existingFiles[0];
// Read the file and return with appropriate MIME type
// We use FileShare.ReadWrite | FileShare.Delete so that if someone is currently overwriting the file (uploading), we don't block them.
var stream = new FileStream(targetPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
string mimeType = "application/octet-stream";
@@ -115,7 +111,7 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api
/// <summary>
/// Deletes a custom overlay image.
/// </summary>
// [Microsoft.AspNetCore.Authorization.Authorize]
[Authorize(Policy = "RequiresElevation")]
[HttpDelete("OverlayImage")]
public IActionResult DeleteImage([FromQuery] string? filename = null)
{
@@ -144,7 +140,6 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api
/// <summary>
/// Renames a custom overlay image (used when a seasonal section is renamed).
/// </summary>
// [Microsoft.AspNetCore.Authorization.Authorize]
[HttpPut("OverlayImage/Rename")]
public IActionResult RenameImage([FromQuery] string oldName, [FromQuery] string newName)
{

View File

@@ -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; } = "";

View File

@@ -259,11 +259,11 @@
<input type="file" id="overlayImageInput" accept="image/png, image/jpeg, image/gif, image/webp" style="display: none;">
<img id="overlayImagePreview" style="display: none; max-width: 100%; max-height: 150px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); border-radius: 4px; z-index: 2;" />
<!-- A semi-transparent overlay to clear the image -->
<button type="button" id="clearOverlayImageBtn" is="paper-icon-button-light" style="display: none; position: absolute; top: 10px; right: 10px; z-index: 3; color: #a94442;" title="Clear Image">
<button type="button" id="clearOverlayImageBtn" class="clear-btn" is="paper-icon-button-light" style="display: none; position: absolute; top: 10px; right: 10px; z-index: 3; color: #a94442;" title="Clear Image">
<i class="material-icons">delete</i>
</button>
</div>
<div class="fieldDescription">Uploading an image will securely save it to the server and automatically update the URL field above.</div>
<div class="fieldDescription">Uploading an image will save it to your server and automatically update the URL field above.</div>
</div>
<h2 class="sectionTitle" style="margin-top: 1.2em;">Styles</h2>
@@ -418,6 +418,13 @@
on
mobile devices. (looks bad on desktops)</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="ConstrainPlotWidth" name="ConstrainPlotWidth" />
<span>Constrain Plot Width</span>
</label>
<div class="fieldDescription">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).</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="EnableLoadingScreen"
@@ -463,6 +470,11 @@
<input is="emby-input" type="number" id="ShuffleInterval" name="ShuffleInterval" />
<div class="fieldDescription">Time in milliseconds between changing slides.</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="BackdropVideoDelay">Backdrop Video Delay (ms)</label>
<input is="emby-input" type="number" id="BackdropVideoDelay" name="BackdropVideoDelay" />
<div class="fieldDescription">Time in milliseconds to wait before playing backdrops/theme videos (leaves static backdrop visible longer).</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="RetryInterval">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
@@ -756,7 +769,7 @@
if (urlInput.value && urlInput.value.trim() !== '') {
previewImg.src = urlInput.value;
previewImg.style.display = 'block';
clearBtn.style.display = 'block';
clearBtn.style.display = 'inline-flex';
if(icon) icon.style.display = 'none';
if(text) {
text.textContent = 'Drag and drop a new image here, or click to select';
@@ -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) {
@@ -943,7 +957,7 @@
MediaBarEnhancedConfigurationPage.createSectionElement(container, {
Name: 'New Season',
StartDay: 1, StartMonth: 1,
EndDay: 1, EndMonth: 1,
EndDay: 31, EndMonth: 1,
MediaIds: '',
OverlayText: '',
OverlayImageUrl: ''
@@ -1021,12 +1035,12 @@
' </div>' +
' </div>' +
' <div class="inputContainer" style="flex: 1; min-width: 250px; margin: 0; padding-bottom: 5px;">' +
' <div class="seasonal-dropzone" style="border: 2px dashed rgba(255,255,255,0.2); border-radius: 8px; padding: 1em; text-align: center; cursor: pointer; background: rgba(0,0,0,0.2); transition: all 0.2s ease; position: relative; min-height: 80px; display: flex; flex-direction: column; align-items: center; justify-content: center;">' +
' <div class="seasonal-dropzone" style="border: 2px dashed rgba(255,255,255,0.2); border-radius: 8px; padding: 1em; text-align: center; cursor: pointer; background: rgba(0,0,0,0.2); transition: all 0.2s ease; position: relative; min-height: 98px; display: flex; flex-direction: column; align-items: center; justify-content: center;">' +
' <i class="material-icons" style="font-size: 24px; color: rgba(255,255,255,0.4); margin-bottom: 4px;">cloud_upload</i>' +
' <span style="font-size: 0.85em; color: rgba(255,255,255,0.7);">Upload seasonal image</span>' +
' <input type="file" class="seasonal-file-input" accept="image/png, image/jpeg, image/gif, image/webp" style="display: none;">' +
' <img class="seasonal-preview-img" style="display: none; max-width: 100%; max-height: 100%; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); border-radius: 4px; z-index: 2; object-fit: contain;" />' +
' <button type="button" is="paper-icon-button-light" class="seasonal-clear-btn" title="Clear Image" style="display: none; position: absolute; top: 0px; right: 0px; z-index: 3; color: #a94442;"><i class="material-icons">delete</i></button>' +
' <button type="button" is="paper-icon-button-light" class="seasonal-clear-btn remove-img" title="Clear Image" style="background: transparent; border: none; padding: 0; cursor: pointer; display: none; position: absolute; top: 10px; right: 10px; z-index: 3; color: #a94442;"><i class="material-icons">delete</i></button>' +
' </div>' +
' </div>' +
'</div>';
@@ -1098,7 +1112,7 @@
if (val) {
previewImg.src = val;
previewImg.style.display = 'block';
clearBtn.style.display = 'block';
clearBtn.style.display = 'inline-flex';
if(icon) icon.style.display = 'none';
if(text) {
text.textContent = 'Drag and drop a new image here, or click to select';

View File

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

View File

@@ -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;
@@ -749,6 +758,15 @@
left: 50%;
transform: translateX(-50%) scale(0.8);
background-color: #ffffff00;
width: max-content;
max-width: 90vw;
flex-wrap: nowrap;
overflow: hidden;
padding: 10px 0;
}
.dot {
flex-shrink: 0;
}
.dot.active {

View File

@@ -1,4 +1,4 @@
/*
/*
* Jellyfin Slideshow by M0RPH3US v4.0.1
* Modified by CodeDevMLH
*
@@ -67,6 +67,8 @@ const CONFIG = {
customOverlayPositionX: 0,
customOverlayPositionY: 0,
customOverlayScale: 100,
backdropVideoDelay: 0,
constrainPlotWidth: false,
enableCustomMediaIds: true,
enableSeasonalContent: false,
customMediaIds: "",
@@ -110,6 +112,7 @@ const STATE = {
customTrailerUrls: {},
ytPromise: null,
autoplayTimeouts: [],
playSignals: {},
},
};
@@ -340,6 +343,15 @@ const initLoadingScreen = () => {
});
});
};
// Global Failsafe, force remove loading screen after 15 seconds to prevent infinite lockouts
setTimeout(() => {
const loader = document.querySelector(".bar-loading");
if (loader) {
console.warn("🎬 Media Bar:", "Loading screen timed out! Forcing removal as a failsafe.");
finishLoading();
}
}, 15000);
};
/**
@@ -387,6 +399,7 @@ const resetSlideshowState = () => {
STATE.slideshow.customTrailerUrls = {};
STATE.slideshow.totalItems = 0;
STATE.slideshow.isLoading = false;
STATE.slideshow.playSignals = {};
};
/**
@@ -1868,11 +1881,11 @@ const SlideCreator = {
event.target.setPlaybackQuality(quality);
}
// Only play if this is the active slide
// Only play if this is the active slide and the play signal has been issued (delay finished)
const slide = document.querySelector(`.slide[data-item-id="${itemId}"]`);
const isVideoPlayerOpen = document.querySelector('.videoPlayerContainer') || document.querySelector('.youtubePlayerContainer');
if (slide && slide.classList.contains('active') && !document.hidden && (!isVideoPlayerOpen || isVideoPlayerOpen.classList.contains('hide'))) {
if (slide && slide.classList.contains('active') && STATE.slideshow.playSignals[itemId] === true && !document.hidden && (!isVideoPlayerOpen || isVideoPlayerOpen.classList.contains('hide'))) {
event.target.playVideo();
// Check if it actually started playing after a short delay (handling autoplay blocks)
@@ -2046,7 +2059,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);
@@ -2363,8 +2376,17 @@ const SlideshowManager = {
const totalItems = STATE.slideshow.totalItems || 0;
// dynamically lower the max dots threshold on small screens
let effectiveMaxDots = CONFIG.maxPaginationDots;
if (window.matchMedia("(max-width: 767px) and (orientation: portrait)").matches) {
const availableWidth = window.innerWidth * 0.9;
const dotWidth = 18; // approximate width per dot
const fittingDots = Math.floor(availableWidth / dotWidth) - 1;
effectiveMaxDots = Math.min(effectiveMaxDots, fittingDots);
}
// Switch to counter style if too many items
if (totalItems > CONFIG.maxPaginationDots) {
if (totalItems > effectiveMaxDots) {
const counter = document.createElement("span");
counter.className = "slide-counter";
counter.id = "slide-counter";
@@ -2425,6 +2447,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();
@@ -2456,6 +2483,7 @@ const SlideshowManager = {
void currentSlide.offsetWidth;
currentSlide.classList.add("active");
STATE.slideshow.playSignals[currentItemId] = false;
// Manage Video Playback: Stop others, Play current
// 1. Stop all other YouTube players and local video elements, release connections
@@ -2516,11 +2544,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;
@@ -2529,22 +2559,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
@@ -2556,25 +2575,60 @@ 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;
STATE.slideshow.playSignals[currentItemId] = true;
if (videoBackdrop.tagName === 'VIDEO') {
videoBackdrop.play().catch(e => {
if (!STATE.slideshow.isMuted) {
// 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 {
console.error("🎬 Media Bar:", "Playback failed despite being muted", e);
}
});
} else if (STATE.slideshow.videoPlayers && STATE.slideshow.videoPlayers[currentItemId]) {
const player = STATE.slideshow.videoPlayers[currentItemId];
if (player && typeof player.playVideo === 'function') {
player.playVideo();
if (!STATE.slideshow.isMuted) {
// 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);
@@ -3892,8 +3946,16 @@ const slidesInit = async () => {
homeSections.style.top = '0';
homeSections.style.marginTop = '0';
}
const container = document.getElementById('slides-container');
if (container) container.style.display = 'none';
let container = document.getElementById('slides-container');
if (container) {
container.style.display = 'none';
} else {
// Create dummy container so loading screen's interval can trigger its own cleanup
container = document.createElement('div');
container.id = 'slides-container';
container.style.display = 'none';
document.body.appendChild(container);
}
return;
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,12 +9,20 @@
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/raw/branch/main/logo.png",
"versions": [
{
"version": "1.7.2.8",
"changelog": "feat: add custom text/image overlay option\n- feat: add option to disable pagination dots/counter\n- feat: add exclude seasonal content from random fetching option\n- Add hide arrows on mobile option \n- fix button issue on mobile when using ElegantFin Theme",
"version": "1.8.1.5",
"changelog": "- fix pagination dot issue on mobile when showing more than 10 dots (should now dynamically adjust the max dots threshold based on screen size)\n- add option to delay trailer playback\n- add option to limit the plot width",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.7.2.8/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "b2ddf7bd8f42d5bcddff03259115d086",
"timestamp": "2026-03-10T23:05:13Z"
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.8.1.4/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "5caa751c6979b6d0da221bae6989a174",
"timestamp": "2026-03-23T23:18:10Z"
},
{
"version": "1.8.0.0",
"changelog": "- feat: add custom text/image overlay option\n- feat: add option to disable pagination dots/counter\n- feat: add exclude seasonal content from random fetching option\n- Add hide arrows on mobile option \n- fix button issue on mobile when using ElegantFin Theme",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.8.0.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "0aac723796d41fc15987c94ac0476584",
"timestamp": "2026-03-11T01:38:43Z"
},
{
"version": "1.7.0.14",