diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs index 7909786..e015864 100644 --- a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs +++ b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/PluginConfiguration.cs @@ -18,6 +18,7 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Configuration public int PreloadCount { get; set; } = 3; public int FadeTransitionDuration { get; set; } = 500; public int MaxPaginationDots { get; set; } = 15; + public bool ShowPaginationDots { get; set; } = true; public bool SlideAnimationEnabled { get; set; } = true; public bool EnableVideoBackdrop { get; set; } = true; public bool UseSponsorBlock { get; set; } = true; diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html index e3cb11b..ba64a44 100644 --- a/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html +++ b/Jellyfin.Plugin.MediaBarEnhanced/Configuration/configPage.html @@ -43,7 +43,8 @@

Main Plugin Settings

Enable or disable the entire plugin functionality.
@@ -57,21 +58,25 @@
Show trailers as background if available.
Adds a mute/unmute and pause/play button to control the video in the right top corner.
-
+
-
If enabled, local trailers will be preferred over remote (YouTube) trailers.
+
If enabled, local trailers will be preferred over remote + (YouTube) trailers.
-
+
-
If enabled, local backdrop videos (Theme Videos) will be preferred over remote and local trailers.
+
If enabled, local backdrop videos (Theme Videos) will be + preferred over remote and local trailers.
If enabled, the slideshow will show the items listed - below as the default content. If the list is empty, random items from your library are used.
+ below as the default content. If the list is empty, random items from your library are + used.
-
If enabled, the Max Items limit (Advanced → Content Limits) will also apply to Custom Media IDs and Collections. By default, custom lists are not limited.
+
If enabled, the Max Items limit (Advanced → Content + Limits) will also apply to Custom Media IDs and Collections. By default, custom lists + are not limited.
- - -
Enter the IDs of the items you want to show in the slideshow as your default content. You can separate them by new line or comma. +
Enter the IDs of the items you want to show in the + slideshow as your default content. You can separate them by new line or comma.

- Manual Trailer/Video Override: You can specify a YouTube URL OR a Jellyfin Item ID (e.g. for a Theme Video) for an item by adding it in - brackets:
e.g. ID DESCRIPTION [https://youtu.be/...] or ID [JellyfinItemId] DESCRIPTION. + Manual Trailer/Video Override: You can specify a YouTube URL OR a + Jellyfin Item ID (e.g. for a Theme Video) for an item by adding it in + brackets:
e.g. ID DESCRIPTION [https://youtu.be/...] or + ID [JellyfinItemId] DESCRIPTION.
Methods:
  • YouTube URL: Play a remote trailer from YouTube.
  • -
  • Jellyfin Item ID (GUID): Play the video of another library item (e.g. a Theme Video or Backdrop Video) using the native player.
  • +
  • Jellyfin Item ID (GUID): Play the video of another library item (e.g. + a Theme Video or Backdrop Video) using the native player.
- You can also add a description after the ID using any separator like space, pipe (|) or dash (-):
e.g. ID DESCRIPTION or ID | DESCRIPTION + You can also add a description after the ID using any separator like space, pipe (|) + or dash (-):
e.g. ID DESCRIPTION or ID | DESCRIPTION

- Note: If using a Collection Name (instead of an ID) combined with a description, you MUST use the pipe (|) separator. + Note: If using a Collection Name (instead of an ID) combined with a + description, you MUST use the pipe (|) separator.
Note: The separator MUST NOT be a hex character (0-9, a-f).
-

You can find the IDs of your items in the URL of the item page in the web interface.
+

You can find the IDs of your items in the URL of the item page in the web + interface.
Example: https://your-jellyfin-url/web/#/details?id=your-item-id&serverId=your-server-id

- You can also insert a name of a collection or playlist to fetch the IDs of all items in - it (will take the first hit.
Note: There is currently no feedback if the name - resolution succeeded, you will have to look if the bar displays the correct items). + You can also insert a name of a collection or playlist to fetch the IDs of all items + in it (will take the first hit.
Note: There is currently no feedback if + the name resolution succeeded, you will have to look if the bar displays the correct + items).

@@ -160,14 +178,18 @@ name="EnableSeasonalContent" /> Enable Seasonal Content -
When enabled, seasonal sections below will override the default list or random selection - during their active date ranges. If no season matches the current date, the default Custom Media IDs above or random selection are used as fallback.
+
When enabled, seasonal sections below will override the + default list or random selection + during their active date ranges. If no season matches the current date, the default + Custom Media IDs above or random selection are used as fallback.
-
If enabled, a random video from the backdrops/theme videos will be selected instead of the first one (if multiple exist).
+
If enabled, a random video from the backdrops/theme videos + will be selected instead of the first one (if multiple exist).
-
If enabled, a random local trailer will be selected instead of the first one (if multiple exist).
+
If enabled, a random local trailer will be selected instead of + the first one (if multiple exist).
-
Skip intro/outro segments in YouTube trailers (if data is available).
+
Skip intro/outro segments in YouTube trailers (if data is + available).
- @@ -241,7 +270,9 @@ Start Muted
Start trailer video playback muted. (Known issue: In the - Android/IOS app, backdrop trailers are always muted.)
Warning: Disabling this may cause autoplay to fail on certain browsers due to strict autoplay policies.
+ Android/IOS app, backdrop trailers are always muted.)
+ Warning: Disabling this may cause autoplay to fail on + certain browsers due to strict autoplay policies.
+
+ +
Show or hide the pagination dots/counter navigation at the + bottom of the slideshow.
+
@@ -397,7 +442,8 @@ name="IncludeWatchedContent" /> Include Watched Content -
If enabled, watched content will be included in the random selection results.
+
If enabled, watched content will be included in the random + selection results.
@@ -459,7 +505,7 @@ 'EnableSeasonalContent', 'EnableClientSideSettings', 'SortBy', 'SortOrder', 'PreferLocalTrailers', 'ApplyLimitsToCustomIds', 'SeasonalSections', 'PreferLocalBackdrops', 'RandomizeThemeVideos', 'RandomizeLocalTrailers', - 'IncludeWatchedContent' + 'IncludeWatchedContent', 'ShowPaginationDots' ]; // Manual mapping for MediaBarIsEnabled -> IsEnabled, to avoid conflicts with other plugins @@ -501,16 +547,16 @@ seasonalCheckbox.addEventListener('change', updateSeasonalVisibility); updateSeasonalVisibility(); } - + // Add Season Button var addSeasonBtn = page.querySelector('#addSeasonBtn'); if (addSeasonBtn) { // Remove existing listeners to avoid duplicates if re-attached - var newBtn = addSeasonBtn.cloneNode(true); - addSeasonBtn.parentNode.replaceChild(newBtn, addSeasonBtn); - newBtn.addEventListener('click', function() { - MediaBarEnhancedConfigurationPage.addSeasonalSection(page); - }); + var newBtn = addSeasonBtn.cloneNode(true); + addSeasonBtn.parentNode.replaceChild(newBtn, addSeasonBtn); + newBtn.addEventListener('click', function () { + MediaBarEnhancedConfigurationPage.addSeasonalSection(page); + }); } // Handle Prefer Local Trailers visibility @@ -545,7 +591,7 @@ if (seasonalInput) seasonalInput.value = sectionsJson; var config = {}; - + // Manual mapping for MediaBarIsEnabled -> IsEnabled, to avoid conflicts with other plugins var mediaBarEnabledCheckbox = page.querySelector('#MediaBarIsEnabled'); if (mediaBarEnabledCheckbox) { @@ -563,7 +609,7 @@ 'EnableSeasonalContent', 'EnableClientSideSettings', 'SortBy', 'SortOrder', 'PreferLocalTrailers', 'ApplyLimitsToCustomIds', 'SeasonalSections', 'PreferLocalBackdrops', 'RandomizeThemeVideos', 'RandomizeLocalTrailers', - 'IncludeWatchedContent' + 'IncludeWatchedContent', 'ShowPaginationDots' ]; keys.forEach(function (key) { @@ -582,16 +628,16 @@ }); }, - renderSeasonalSections: function(page, sections) { + renderSeasonalSections: function (page, sections) { var container = page.querySelector('#seasonalSectionsList'); if (!container) return; container.innerHTML = ''; - sections.forEach(function(section, index) { + sections.forEach(function (section, index) { MediaBarEnhancedConfigurationPage.createSectionElement(container, section, index + 1); }); }, - addSeasonalSection: function(page) { + addSeasonalSection: function (page) { var container = page.querySelector('#seasonalSectionsList'); if (!container) return; var index = container.children.length + 1; @@ -603,25 +649,25 @@ }, index); }, - createSectionElement: function(container, data, index) { + createSectionElement: function (container, data, index) { var div = document.createElement('div'); div.className = 'seasonal-section'; div.style.cssText = 'background: rgba(0,0,0,0.2); padding: 1em; margin-bottom: 1em; border-radius: 4px; border: 1px solid rgba(255,255,255,0.1);'; - + var days = []; - for(var i=1; i<=31; i++) days.push(i); + for (var i = 1; i <= 31; i++) days.push(i); var months = [ - {v:1, n:'Jan'}, {v:2, n:'Feb'}, {v:3, n:'Mar'}, {v:4, n:'Apr'}, - {v:5, n:'May'}, {v:6, n:'Jun'}, {v:7, n:'Jul'}, {v:8, n:'Aug'}, - {v:9, n:'Sep'}, {v:10, n:'Oct'}, {v:11, n:'Nov'}, {v:12, n:'Dec'} + { v: 1, n: 'Jan' }, { v: 2, n: 'Feb' }, { v: 3, n: 'Mar' }, { v: 4, n: 'Apr' }, + { v: 5, n: 'May' }, { v: 6, n: 'Jun' }, { v: 7, n: 'Jul' }, { v: 8, n: 'Aug' }, + { v: 9, n: 'Sep' }, { v: 10, n: 'Oct' }, { v: 11, n: 'Nov' }, { v: 12, n: 'Dec' } ]; function mkSelect(val, opts, cls) { var h = ''; return h; @@ -648,11 +694,11 @@ ' ' + '
' + ' From:' + - mkSelect(data.StartDay, days, 'start-day') + - mkSelect(data.StartMonth, months, 'start-month') + + mkSelect(data.StartDay, days, 'start-day') + + mkSelect(data.StartMonth, months, 'start-month') + ' To:' + - mkSelect(data.EndDay, days, 'end-day') + - mkSelect(data.EndMonth, months, 'end-month') + + mkSelect(data.EndDay, days, 'end-day') + + mkSelect(data.EndMonth, months, 'end-month') + '
' + '
Date range (inclusive) when this content is active.
' + '' + @@ -662,19 +708,19 @@ '
Comma-separated or Newline separated list of Movie/Series/Collection IDs to show during this season.
Same options available as for the default media IDs.
' + ''; - div.querySelector('.btn-remove').addEventListener('click', function() { + div.querySelector('.btn-remove').addEventListener('click', function () { div.remove(); MediaBarEnhancedConfigurationPage.updateSectionTitles(container); }); - div.querySelector('.btn-move-up').addEventListener('click', function() { + div.querySelector('.btn-move-up').addEventListener('click', function () { if (div.previousElementSibling) { container.insertBefore(div, div.previousElementSibling); MediaBarEnhancedConfigurationPage.updateSectionTitles(container); } }); - div.querySelector('.btn-move-down').addEventListener('click', function() { + div.querySelector('.btn-move-down').addEventListener('click', function () { if (div.nextElementSibling) { container.insertBefore(div.nextElementSibling, div); MediaBarEnhancedConfigurationPage.updateSectionTitles(container); @@ -684,9 +730,9 @@ container.appendChild(div); }, - updateSectionTitles: function(container) { + updateSectionTitles: function (container) { var sections = container.querySelectorAll('.seasonal-section'); - sections.forEach(function(section, index) { + sections.forEach(function (section, index) { var title = section.querySelector('.section-title'); if (title) { title.innerText = 'Season list #' + (index + 1); @@ -694,10 +740,10 @@ }); }, - getSeasonalSectionsFromUI: function(page) { + getSeasonalSectionsFromUI: function (page) { var sections = []; var els = page.querySelectorAll('.seasonal-section'); - els.forEach(function(el) { + els.forEach(function (el) { sections.push({ Name: el.querySelector('.section-name').value, StartDay: parseInt(el.querySelector('.start-day').value), diff --git a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js index 1b25b61..363a4f1 100644 --- a/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js +++ b/Jellyfin.Plugin.MediaBarEnhanced/Web/mediaBarEnhanced.js @@ -38,6 +38,7 @@ const CONFIG = { preloadCount: 3, fadeTransitionDuration: 500, maxPaginationDots: 15, + showPaginationDots: true, slideAnimationEnabled: true, enableVideoBackdrop: true, useSponsorBlock: true, @@ -2280,6 +2281,8 @@ const SlideCreator = { const SlideshowManager = { createPaginationDots() { + if (!CONFIG.showPaginationDots) return; + let dotsContainer = document.querySelector(".dots-container"); if (!dotsContainer) { dotsContainer = document.createElement("div");