mirror of
https://github.com/IAmParadox27/jellyfin-plugin-media-bar.git
synced 2026-11-07 08:01:01 +01:00
Merge pull request #114 from MrRussLuther/sync-page-backdrop
Sync the page backdrop with the featured slide
This commit is contained in:
@@ -36,6 +36,7 @@ const CONFIG = {
|
||||
"described audio",
|
||||
"vertical",
|
||||
],
|
||||
syncPageBackdrop: true,
|
||||
youtubeApiLoadTimeoutMs: 8000,
|
||||
};
|
||||
|
||||
@@ -1133,6 +1134,7 @@ const VisibilityObserver = {
|
||||
STATE.slideshow.players = {};
|
||||
container.querySelectorAll(".slide").forEach((slide) => slide.remove());
|
||||
STATE.slideshow.createdSlides = {};
|
||||
PageBackdrop.clear();
|
||||
}
|
||||
|
||||
this.wasVisible = isVisible;
|
||||
@@ -1159,6 +1161,163 @@ const VisibilityObserver = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Mirrors the featured slide into Jellyfin's own page backdrop layer, so the
|
||||
* background behind the home page follows whatever the slideshow is showing
|
||||
* instead of staying on unrelated library art.
|
||||
*
|
||||
* Reuses the exact backdrop URL the active slide already requested, so the
|
||||
* browser serves it from cache and no extra image is fetched.
|
||||
*
|
||||
* Only active while the slideshow is visible. Leaving the home page restores
|
||||
* Jellyfin's normal backdrop behaviour, so no other page is affected.
|
||||
* Disable with CONFIG.syncPageBackdrop.
|
||||
*/
|
||||
const PageBackdrop = {
|
||||
LAYER_CLASS: "slideshow-page-backdrop",
|
||||
observer: null,
|
||||
isWriting: false,
|
||||
currentItemId: null,
|
||||
|
||||
/**
|
||||
* Jellyfin creates .backdropContainer lazily, so it may not exist yet
|
||||
* @returns {HTMLElement} The backdrop container
|
||||
*/
|
||||
getOrCreateContainer() {
|
||||
let container = document.querySelector(".backdropContainer");
|
||||
if (!container) {
|
||||
container = SlideUtils.createElement("div", {
|
||||
className: "backdropContainer",
|
||||
});
|
||||
document.body.insertBefore(container, document.body.firstChild);
|
||||
}
|
||||
return container;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} container - The backdrop container
|
||||
* @returns {HTMLElement} Our own backdrop layer, created if needed
|
||||
*/
|
||||
getOrCreateLayer(container) {
|
||||
let layer = container.querySelector(`.${this.LAYER_CLASS}`);
|
||||
if (!layer || !layer.isConnected) {
|
||||
layer = SlideUtils.createElement("div", {
|
||||
className: `backdropImage ${this.LAYER_CLASS}`,
|
||||
});
|
||||
container.appendChild(layer);
|
||||
}
|
||||
return layer;
|
||||
},
|
||||
|
||||
/**
|
||||
* Jellyfin runs its own backdrop rotator into the same container: it adds
|
||||
* .backdropImage siblings and cycles them on a timer, which paints over
|
||||
* whatever we set. While the slideshow owns the screen, keep our layer the
|
||||
* only one and make sure it stays last.
|
||||
* @param {HTMLElement} container - The backdrop container
|
||||
*/
|
||||
evictForeignLayers(container) {
|
||||
const layers = container.querySelectorAll(".backdropImage");
|
||||
layers.forEach((layer) => {
|
||||
if (!layer.classList.contains(this.LAYER_CLASS)) {
|
||||
layer.remove();
|
||||
}
|
||||
});
|
||||
|
||||
const ours = container.querySelector(`.${this.LAYER_CLASS}`);
|
||||
if (ours && ours !== container.lastElementChild) {
|
||||
container.appendChild(ours);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reacts to the rotator instead of polling for it
|
||||
*/
|
||||
startObserver(container) {
|
||||
if (this.observer) return;
|
||||
|
||||
this.observer = new MutationObserver(() => {
|
||||
// Our own eviction mutates the container, which would re-enter here
|
||||
if (this.isWriting) return;
|
||||
|
||||
this.isWriting = true;
|
||||
try {
|
||||
this.evictForeignLayers(container);
|
||||
} finally {
|
||||
this.isWriting = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.observer.observe(container, { childList: true });
|
||||
},
|
||||
|
||||
stopObserver() {
|
||||
if (!this.observer) return;
|
||||
this.observer.disconnect();
|
||||
this.observer = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Points the page backdrop at the given item
|
||||
* @param {string} itemId - Id of the item the slideshow is showing
|
||||
*/
|
||||
update(itemId) {
|
||||
if (!CONFIG.syncPageBackdrop) return;
|
||||
|
||||
const item = STATE.slideshow.loadedItems[itemId];
|
||||
// Item data arrives with the slide. Until it does, leave the previous
|
||||
// backdrop up rather than clearing it, so the page does not flash.
|
||||
if (!item) return;
|
||||
|
||||
const src = SlideCreator.buildImageUrl(
|
||||
item,
|
||||
"Backdrop",
|
||||
0,
|
||||
STATE.jellyfinData.serverAddress,
|
||||
60,
|
||||
);
|
||||
if (!src) return;
|
||||
|
||||
const container = this.getOrCreateContainer();
|
||||
|
||||
this.isWriting = true;
|
||||
try {
|
||||
const layer = this.getOrCreateLayer(container);
|
||||
|
||||
if (this.currentItemId !== itemId || layer.style.backgroundImage === "") {
|
||||
layer.style.backgroundImage = `url("${src.replace(/"/g, "%22")}")`;
|
||||
layer.classList.remove("backdropImageFadeIn");
|
||||
// Force a reflow so the fade replays for the new image
|
||||
void layer.offsetWidth;
|
||||
layer.classList.add("backdropImageFadeIn");
|
||||
this.currentItemId = itemId;
|
||||
}
|
||||
|
||||
this.evictForeignLayers(container);
|
||||
} finally {
|
||||
this.isWriting = false;
|
||||
}
|
||||
|
||||
document
|
||||
.querySelector(".backgroundContainer")
|
||||
?.classList.add("withBackdrop");
|
||||
this.startObserver(container);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hands the backdrop back to Jellyfin
|
||||
*/
|
||||
clear() {
|
||||
this.stopObserver();
|
||||
this.currentItemId = null;
|
||||
|
||||
document.querySelector(`.${this.LAYER_CLASS}`)?.remove();
|
||||
document
|
||||
.querySelector(".backgroundContainer")
|
||||
?.classList.remove("withBackdrop");
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Slideshow UI creation and management
|
||||
*/
|
||||
@@ -1803,6 +1962,7 @@ const SlideshowManager = {
|
||||
|
||||
STATE.slideshow.currentSlideIndex = index;
|
||||
this.updateDots();
|
||||
PageBackdrop.update(currentItemId);
|
||||
this.preloadAdjacentSlides(index);
|
||||
this.pruneSlideCache();
|
||||
|
||||
@@ -2443,6 +2603,7 @@ window.slideshowPure = {
|
||||
SlideCreator,
|
||||
SlideshowManager,
|
||||
VisibilityObserver,
|
||||
PageBackdrop,
|
||||
initSlideshowData: () => {
|
||||
SlideshowManager.loadSlideshowData();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user