Add seasonal content support and enhance custom media ID handling

This commit is contained in:
CodeDevMLH
2026-02-13 01:15:33 +01:00
parent 9896044988
commit 2ae147ac01
3 changed files with 293 additions and 152 deletions

View File

@@ -1319,7 +1319,7 @@ const ApiUtils = {
async fetchCollectionItems(collectionId) {
try {
const response = await fetch(
`${STATE.jellyfinData.serverAddress}/Items?ParentId=${collectionId}&Recursive=true&IncludeItemTypes=Movie,Series&fields=Id&userId=${STATE.jellyfinData.userId}`,
`${STATE.jellyfinData.serverAddress}/Items?ParentId=${collectionId}&Recursive=true&IncludeItemTypes=Movie,Series&fields=Id,Type&userId=${STATE.jellyfinData.userId}`,
{
headers: this.getAuthHeaders(),
}
@@ -1333,7 +1333,7 @@ const ApiUtils = {
const data = await response.json();
const items = data.Items || [];
console.log(`Resolved collection ${collectionId} to ${items.length} items`);
return items.map(i => i.Id);
return items.map(i => ({ Id: i.Id, Type: i.Type }));
} catch (error) {
console.error(`Error fetching collection items for ${collectionId}:`, error);
return [];
@@ -1783,7 +1783,7 @@ const SlideCreator = {
},
'onStateChange': (event) => {
if (event.data === YT.PlayerState.ENDED) {
SlideshowManager.nextSlide();
SlideshowManager.nextSlide();
}
},
'onError': (event) => {
@@ -1841,7 +1841,7 @@ const SlideCreator = {
});
backdrop.addEventListener('ended', () => {
SlideshowManager.nextSlide();
SlideshowManager.nextSlide();
});
backdrop.addEventListener('error', () => {
@@ -2703,7 +2703,7 @@ const SlideshowManager = {
videoBackdrop.play().catch(() => {
setTimeout(() => {
if (videoBackdrop.paused) {
if (videoBackdrop.paused && slide.classList.contains('active')) {
console.warn(`Autoplay blocked for ${itemId}, attempting muted fallback`);
videoBackdrop.muted = true;
videoBackdrop.play().catch(err => console.error("Muted fallback failed", err));
@@ -2721,6 +2721,9 @@ const SlideshowManager = {
startSeconds: player._startTime || 0,
endSeconds: player._endTime
});
// Explicitly call playVideo to ensure it starts
player.playVideo();
if (STATE.slideshow.isMuted) {
player.mute();
@@ -2730,6 +2733,8 @@ const SlideshowManager = {
}
setTimeout(() => {
if (!slide.classList.contains('active')) return;
if (player.getPlayerState &&
player.getPlayerState() !== YT.PlayerState.PLAYING &&
player.getPlayerState() !== YT.PlayerState.BUFFERING) {
@@ -2739,10 +2744,6 @@ const SlideshowManager = {
}
}, 1000);
return true;
} else if (player && typeof player.seekTo === 'function') {
player.seekTo(player._startTime || 0);
player.playVideo();
return true;
}
return false;
@@ -2969,22 +2970,96 @@ const SlideshowManager = {
},
/**
* Parses custom media IDs, handling seasonal content if enabled
* Parses custom media IDs, handling seasonal content if enabled.
* If Seasonal Content is enabled:
* - Check if any defined season matches the current date.
* - If match: Return IDs from that season.
* - If NO match: Fall back to Default Custom IDs.
* If Custom Media IDs are enabled (and no seasonal match):
* - Return Default Custom IDs.
* If no Custom Media IDs are enabled:
* - Return empty array (triggering random fallback).
* @returns {string[]} Array of media IDs
*/
parseCustomIds() {
if (!CONFIG.enableSeasonalContent) {
return CONFIG.customMediaIds
.split(/[\n,]/).map((line) => {
let idsString = CONFIG.customMediaIds;
let usingSeasonal = false;
if (CONFIG.enableSeasonalContent) {
try {
const sections = JSON.parse(CONFIG.seasonalSections || "[]");
const currentDate = new Date();
const currentMonth = currentDate.getMonth() + 1; // 1-12
const currentDay = currentDate.getDate(); // 1-31
for (const section of sections) {
const startDay = parseInt(section.StartDay);
const startMonth = parseInt(section.StartMonth);
const endDay = parseInt(section.EndDay);
const endMonth = parseInt(section.EndMonth);
let isInRange = false;
if (startMonth === endMonth) {
if (currentMonth === startMonth && currentDay >= startDay && currentDay <= endDay) {
isInRange = true;
}
} else if (startMonth < endMonth) {
// Normal range
if (
(currentMonth > startMonth && currentMonth < endMonth) ||
(currentMonth === startMonth && currentDay >= startDay) ||
(currentMonth === endMonth && currentDay <= endDay)
) {
isInRange = true;
}
} else {
// Wrap around year
if (
(currentMonth > startMonth || currentMonth < endMonth) ||
(currentMonth === startMonth && currentDay >= startDay) ||
(currentMonth === endMonth && currentDay <= endDay)
) {
isInRange = true;
}
}
if (isInRange) {
console.log(`Seasonal match found: ${section.Name}`);
idsString = section.MediaIds;
usingSeasonal = true;
break; // Use first matching season
}
}
} catch (e) {
console.error("Error parsing seasonal sections in JS:", e);
}
}
// If NOT using seasonal content (disabled or no match),
// Custom IDs are disabled, return empty to skip to random
if (!usingSeasonal && !CONFIG.enableCustomMediaIds) {
return [];
}
// Parse the resulting string (either seasonal or default)
if (!idsString) return [];
return idsString
.split(/[\n,]/)
.map((line) => {
const urlMatch = line.match(/\[(.*?)\]/);
let id = line;
if (urlMatch) {
const url = urlMatch[1];
// Remove the [url] part from the ID string for parsing
id = line.replace(/\[.*?\]/, '').trim();
// Attempt to extract GUID if present
const guidMatch = id.match(/([0-9a-f]{32})/i);
if (guidMatch) {
id = guidMatch[1];
} else {
// Fallback: split by pipe if used
id = id.split('|')[0].trim();
}
STATE.slideshow.customTrailerUrls[id] = url;
@@ -2993,83 +3068,6 @@ const SlideshowManager = {
})
.map((id) => id.trim())
.filter((id) => id);
} else {
return this.parseSeasonalIds();
}
},
/**
* Parses custom media IDs, handling seasonal content if enabled
* @returns {string[]} Array of media IDs
*/
parseSeasonalIds() {
console.log("Using Seasonal Content Mode");
const lines = CONFIG.customMediaIds.split('\n');
const currentDate = new Date();
const currentMonth = currentDate.getMonth() + 1; // 1-12
const currentDay = currentDate.getDate(); // 1-31
const rawIds = [];
for (const line of lines) {
const match = line.match(/^\s*(\d{1,2})\.(\d{1,2})-(\d{1,2})\.(\d{1,2})\s*\|.*\|(.*)$/) ||
line.match(/^\s*(\d{1,2})\.(\d{1,2})-(\d{1,2})\.(\d{1,2})\s*\|(.*)$/);
if (match) {
const startDay = parseInt(match[1]);
const startMonth = parseInt(match[2]);
const endDay = parseInt(match[3]);
const endMonth = parseInt(match[4]);
const idsPart = match[5];
let isInRange = false;
if (startMonth === endMonth) {
if (currentMonth === startMonth && currentDay >= startDay && currentDay <= endDay) {
isInRange = true;
}
} else if (startMonth < endMonth) {
// Normal range spanning months (e.g. 15.06 - 15.08)
if (
(currentMonth > startMonth && currentMonth < endMonth) ||
(currentMonth === startMonth && currentDay >= startDay) ||
(currentMonth === endMonth && currentDay <= endDay)
) {
isInRange = true;
}
} else {
// Wrap around year (e.g. 01.12 - 15.01)
if (
(currentMonth > startMonth || currentMonth < endMonth) ||
(currentMonth === startMonth && currentDay >= startDay) ||
(currentMonth === endMonth && currentDay <= endDay)
) {
isInRange = true;
}
}
if (isInRange) {
console.log(`Seasonal match found: ${line}`);
const ids = idsPart.split(/[,]/).map(line => {
const urlMatch = line.match(/\[(.*?)\]/);
let id = line;
if (urlMatch) {
const url = urlMatch[1];
id = line.replace(/\[.*?\]/, '').trim();
const guidMatch = id.match(/([0-9a-f]{32})/i);
if (guidMatch) {
id = guidMatch[1];
} else {
id = id.split('|')[0].trim();
}
STATE.slideshow.customTrailerUrls[id] = url;
}
return id.trim();
}).filter(id => id);
rawIds.push(...ids);
}
}
}
return rawIds;
},
/**
@@ -3111,7 +3109,7 @@ const SlideshowManager = {
const children = await ApiUtils.fetchCollectionItems(id);
finalIds.push(...children);
} else if (item) {
finalIds.push(id);
finalIds.push({ Id: item.Id, Type: item.Type });
}
} catch (e) {
console.warn(`Error resolving item ${rawId}:`, e);
@@ -3129,15 +3127,40 @@ const SlideshowManager = {
let itemIds = [];
// 1. Try Custom Media/Collection IDs from Config & seasonal content
if (CONFIG.enableCustomMediaIds && CONFIG.customMediaIds) {
if (CONFIG.enableCustomMediaIds || CONFIG.enableSeasonalContent) {
console.log("Using Custom Media IDs from configuration");
const rawIds = this.parseCustomIds();
itemIds = await this.resolveCollectionsAndItems(rawIds);
const resolvedItems = await this.resolveCollectionsAndItems(rawIds);
// Apply max items limit to custom IDs if enabled
if (CONFIG.applyLimitsToCustomIds && itemIds.length > CONFIG.maxItems) {
console.log(`Limiting custom IDs from ${itemIds.length} to ${CONFIG.maxItems}`);
itemIds = itemIds.slice(0, CONFIG.maxItems);
if (CONFIG.applyLimitsToCustomIds) {
let movieCount = 0;
let showCount = 0;
let keptItems = [];
for (const item of resolvedItems) {
if (keptItems.length >= CONFIG.maxItems) break;
if (item.Type === 'Movie') {
if (movieCount < CONFIG.maxMovies) {
movieCount++;
keptItems.push(item);
}
} else if (item.Type === 'Series' || item.Type === 'Season' || item.Type === 'Episode') {
// Count Seasons/Episodes as TV Shows
if (showCount < CONFIG.maxTvShows) {
showCount++;
keptItems.push(item);
}
} else {
// Other types: count towards total only
keptItems.push(item);
}
}
itemIds = keptItems.map(i => i.Id);
console.log(`Applied limits to custom IDs: ${itemIds.length} items (Movies: ${movieCount}, Shows: ${showCount})`);
} else {
itemIds = resolvedItems.map(i => i.Id);
}
}