Add option to prefer local trailers over remote ones; update configuration and UI
This commit is contained in:
@@ -21,6 +21,7 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Configuration
|
||||
public bool SlideAnimationEnabled { get; set; } = true;
|
||||
public bool EnableVideoBackdrop { get; set; } = true;
|
||||
public bool UseSponsorBlock { get; set; } = true;
|
||||
public bool PreferLocalTrailers { get; set; } = false;
|
||||
public bool WaitForTrailerToEnd { get; set; } = true;
|
||||
public bool StartMuted { get; set; } = true;
|
||||
public bool FullWidthVideo { get; set; } = true;
|
||||
|
||||
@@ -57,6 +57,14 @@
|
||||
<div class="fieldDescription">Show trailers as background if available.<br>Adds a
|
||||
mute/unmute and pause/play button to control the video in the right top corner.</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription" id="PreferLocalTrailersContainer">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="PreferLocalTrailers"
|
||||
name="PreferLocalTrailers" />
|
||||
<span>Prefer Local Trailers</span>
|
||||
</label>
|
||||
<div class="fieldDescription">If enabled, local trailers will be preferred over remote (YouTube) trailers.</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="WaitForTrailerToEnd"
|
||||
@@ -397,7 +405,8 @@
|
||||
'WaitForTrailerToEnd', 'StartMuted', 'FullWidthVideo', 'EnableMobileVideo',
|
||||
'ShowTrailerButton', 'AlwaysShowArrows', 'EnableKeyboardControls',
|
||||
'EnableCustomMediaIds', 'CustomMediaIds', 'EnableLoadingScreen',
|
||||
'EnableSeasonalContent', 'EnableClientSideSettings', 'SortBy', 'SortOrder'
|
||||
'EnableSeasonalContent', 'EnableClientSideSettings', 'SortBy', 'SortOrder',
|
||||
'PreferLocalTrailers'
|
||||
];
|
||||
|
||||
keys.forEach(function (key) {
|
||||
@@ -431,6 +440,23 @@
|
||||
updateDesc();
|
||||
}
|
||||
|
||||
// Handle Prefer Local Trailers visibility
|
||||
var enableVideoBackdropCheckbox = page.querySelector('#EnableVideoBackdrop');
|
||||
var preferLocalContainer = page.querySelector('#PreferLocalTrailersContainer');
|
||||
|
||||
function updatePreferLocalVisibility() {
|
||||
if (enableVideoBackdropCheckbox && enableVideoBackdropCheckbox.checked) {
|
||||
if (preferLocalContainer) preferLocalContainer.style.display = 'block';
|
||||
} else {
|
||||
if (preferLocalContainer) preferLocalContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (enableVideoBackdropCheckbox) {
|
||||
enableVideoBackdropCheckbox.addEventListener('change', updatePreferLocalVisibility);
|
||||
updatePreferLocalVisibility();
|
||||
}
|
||||
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
},
|
||||
@@ -446,7 +472,8 @@
|
||||
'WaitForTrailerToEnd', 'StartMuted', 'FullWidthVideo', 'EnableMobileVideo',
|
||||
'ShowTrailerButton', 'AlwaysShowArrows', 'EnableKeyboardControls',
|
||||
'EnableCustomMediaIds', 'CustomMediaIds', 'EnableLoadingScreen',
|
||||
'EnableSeasonalContent', 'EnableClientSideSettings', 'SortBy', 'SortOrder'
|
||||
'EnableSeasonalContent', 'EnableClientSideSettings', 'SortBy', 'SortOrder',
|
||||
'PreferLocalTrailers'
|
||||
];
|
||||
|
||||
keys.forEach(function (key) {
|
||||
|
||||
@@ -38,6 +38,7 @@ const CONFIG = {
|
||||
slideAnimationEnabled: true,
|
||||
enableVideoBackdrop: true,
|
||||
useSponsorBlock: true,
|
||||
preferLocalTrailers: false,
|
||||
waitForTrailerToEnd: true,
|
||||
startMuted: true,
|
||||
fullWidthVideo: true,
|
||||
@@ -1023,7 +1024,7 @@ const ApiUtils = {
|
||||
|
||||
const response = await fetch(
|
||||
// `${STATE.jellyfinData.serverAddress}/Items/${itemId}`,
|
||||
`${STATE.jellyfinData.serverAddress}/Items/${itemId}?Fields=Overview,RemoteTrailers,Genres,CommunityRating,CriticRating,OfficialRating,PremiereDate,ProductionYear,MediaSources,RunTimeTicks`,
|
||||
`${STATE.jellyfinData.serverAddress}/Items/${itemId}?Fields=Overview,RemoteTrailers,Genres,CommunityRating,CriticRating,OfficialRating,PremiereDate,ProductionYear,MediaSources,RunTimeTicks,LocalTrailerCount`,
|
||||
{
|
||||
headers: this.getAuthHeaders(),
|
||||
}
|
||||
@@ -1323,6 +1324,39 @@ const ApiUtils = {
|
||||
console.error(`Error fetching collection items for ${collectionId}:`, error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches the first local trailer for an item
|
||||
* @param {string} itemId - Item ID
|
||||
* @returns {Promise<string|null>} Stream URL or null
|
||||
*/
|
||||
async fetchLocalTrailer(itemId) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${STATE.jellyfinData.serverAddress}/Users/${STATE.jellyfinData.userId}/Items/${itemId}/LocalTrailers`,
|
||||
{
|
||||
headers: this.getAuthHeaders(),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const trailers = await response.json();
|
||||
if (trailers && trailers.length > 0) {
|
||||
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 null;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching local trailer for ${itemId}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1505,12 +1539,21 @@ const SlideCreator = {
|
||||
let isVideo = false;
|
||||
let trailerUrl = null;
|
||||
|
||||
// 1. Check for Remote Trailers (YouTube)
|
||||
// Priority: Custom Config URL > Metadata RemoteTrailer
|
||||
// 1. Check for Remote/Local Trailers
|
||||
// Priority: Custom Config URL > (PreferLocal -> Local) > Metadata RemoteTrailer
|
||||
|
||||
// 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}`);
|
||||
} else if (item.RemoteTrailers && item.RemoteTrailers.length > 0) {
|
||||
}
|
||||
// 1b. Check Local Trailer if preferred
|
||||
else if (CONFIG.preferLocalTrailers && item.LocalTrailerCount > 0 && item.localTrailerUrl) {
|
||||
trailerUrl = item.localTrailerUrl;
|
||||
console.log(`Using local trailer for ${itemId}: ${trailerUrl}`);
|
||||
}
|
||||
// 1c. Fallback to Remote Trailer
|
||||
else if (item.RemoteTrailers && item.RemoteTrailers.length > 0) {
|
||||
trailerUrl = item.RemoteTrailers[0].Url;
|
||||
}
|
||||
|
||||
@@ -2004,6 +2047,11 @@ const SlideCreator = {
|
||||
|
||||
const item = await ApiUtils.fetchItemDetails(itemId);
|
||||
|
||||
// Pre-fetch local trailer URL if needed
|
||||
if (CONFIG.preferLocalTrailers && item.LocalTrailerCount > 0) {
|
||||
item.localTrailerUrl = await ApiUtils.fetchLocalTrailer(itemId);
|
||||
}
|
||||
|
||||
const slideElement = this.createSlideElement(
|
||||
item,
|
||||
item.Type === "Movie" ? "Movie" : "TV Show"
|
||||
|
||||
Reference in New Issue
Block a user