• Login
  • Register
  • Login Register
    Login
    Username/Email:
    Password:
    Or login with a social network below
  • Forum
  • Website
  • GitHub
  • Status
  • Translation
  • Features
  • Team
  • Rules
  • Help
  • Feeds
User Links
  • Login
  • Register
  • Login Register
    Login
    Username/Email:
    Password:
    Or login with a social network below

    Useful Links Forum Website GitHub Status Translation Features Team Rules Help Feeds
    Jellyfin Forum Support General Questions Featured Content Bar

    Poll: does anyone want a featured content bar?
    You do not have permission to vote in this poll.
    Yes
    87.50%
    42 87.50%
    No
    6.25%
    3 6.25%
    Don't care
    6.25%
    3 6.25%
    Total 48 vote(s) 100%
    * You voted for this item. [Show Results]

    Pages (10): « Previous 1 2 3 4 5 … 10 Next »
     

     
    • 3 Vote(s) - 5 Average

    Featured Content Bar

    is anyone currently working on a featured content bar?
    BobHasNoSoul
    Offline

    Junior Member

    Posts: 28
    Threads: 2
    Joined: 2023 Oct
    Reputation: 0
    #11
    2023-11-11, 09:08 AM
    some screens of the updated look of it after taking a few pms into consideration

    [Image: 282232452-be19e601-da6f-4428-ba66-0c8179b2dd55.png]
    [Image: 282232448-b130be09-fe20-489f-a22a-7d241c429a71.png]
    [Image: 282232446-38532b32-f335-4918-a970-48a5d6685076.png]
    HP ProLiant DL380 Gen9 142GB 56TB pool soon to be 80TB
    SethBacon
    Offline

    Member

    Posts: 51
    Threads: 3
    Joined: 2023 Nov
    Reputation: 5
    Country:Canada
    #12
    2023-11-12, 09:04 AM (This post was last modified: 2023-11-12, 09:40 AM by SethBacon. Edited 3 times in total.)
    Not a linux guy, ive been editing the slideshow js directly; I've managed to get it selectable by remote with

    slide.setAttribute('tabindex', '0');

    slide.addEventListener('keydown', function(event) {
    switch(event.keyCode) {
    case 13: // Enter key
    // Code to handle the selection
    window.location.href = this.href;
    break;
    // Add cases for other keys if needed
    }
    });

    Didn't feel like worth a full fork, but heres my slideshow.html
    Changes:
    -Reads list.txt (ignoring labels after id codes), if list.txt is not present, grabs 200 movies from server and randomizes a selection of them to display (because i cant find an API call for random selection)
    -Fetches the plot through API - movie.Overview
    -Accessible / Clickable in webUI (see video)
    -Offset top border & title font to match my JF theme (ymmv)
    -Text bottom justified (this number of chrs looks good on my desktop widescreen and on my phone, though ive set my slide frame to 350px tall)
    -Config vars grouped at beginning of JS
    -Various visual changes

    Issues:
    Pressing the webui back button (or remote controll back) after selecting a slide and going to its page will lock up JF. Using browser back button works fine, or pressing the webui home button works fine. I tried different referral options but no luck so far.

    My border highlight for the selection of the frame doesnt seem to be working, I have a hard time testing this accessibility on desktop.

    I dont know how you got this to work on Jellyfin for Android TV, it wont pick up Any server css for me.

    <!DOCTYPE html>
    <html>
    <head>
    <title>Jellyfin Featured Slideshow</title>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap" rel="stylesheet">
    <style>
    /* CSS styles for the slideshow elements */
    body {
    margin: 0;
    padding: 0;
    overflow: hidden;
    }
    .slide {
    position: relative;
    width: 100vw;
    height: 100vh;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
    cursor: pointer; /* Indicates the element is clickable */
    outline: none; /* Custom focus style will be used */
    }
    .slide:focus {
    outline: 2px solid #fff; /* Visual focus indicator */
    }
    .backdrop {
    position: absolute;
    top: 50px;
    left: 0;
    width: 100%;
    height: calc(100% - 50px);
    object-fit: cover;
    object-position: center 20%;
    border-radius: 5px;
    z-index: 1;
    }
    .logo {
    position: absolute;
    top: 65%;
    left: 10px;
    transform: translateY(-50%);
    max-height: 50%;
    max-width: 30%;
    width: auto;
    z-index: 3;
    }
    .featured-content {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 50px;
    background-color: transparent;
    font-family: 'Noto Sans', sans-serif;
    color: #D3D3D3;
    font-size: 22px;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    z-index: 2;
    }
    .plot {
    position: absolute;
    bottom: 0px;
    left: 0; /* Align to the left edge */
    right: 0; /* Stretch to the right edge */
    color: white;
    width: 100%; /* Full width */
    font-family: 'Noto Sans', sans-serif;
    font-size: 15px;
    background: linear-gradient(to top, rgba(0, 0, 0, 1) 20%, rgba(0, 0, 0, 0) 100%);
    padding: 10px;
    border-radius: 5px;
    z-index: 4;
    box-sizing: border-box;
    }
    </style>
    </head>
    <body>

    <!-- Container for dynamic slides -->
    <div id="slides-container"></div>

    <!-- JavaScript for fetching movies and creating the slideshow -->
    <script>
    // Configuration variables
    let title = 'Spotlight'; // Default title
    const userId = '0e0755005f074a59bc0108cc2c3e650b'; // Replace with your User ID
    const token = 'de81a85d55024d7b9fa7b11031de7539'; // Replace with your API token
    const shuffleInterval = 10000; // Time in milliseconds between slide changes (25000ms = 25 seconds)
    const listFileName = 'list.txt'; // Name of the file containing the list of movie IDs

    function shuffleArray(array) {
    for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
    }

    function truncateText(element, maxLength) {
    var truncated = element.innerText;

    if (truncated.length > maxLength) {
    truncated = truncated.substr(0, maxLength) + '...';
    }
    element.innerText = truncated;
    }

    function createSlideForMovie(movie, title) {
    const container = document.getElementById('slides-container');
    const slideElement = createSlideElement(movie, title);
    container.appendChild(slideElement);
    }

    function createSlideElement(movie, title) {
    const itemId = movie.Id;
    const plot = movie.Overview;

    const slide = document.createElement('a');
    slide.className = 'slide';
    slide.href = /#!/details?id=${itemId};
    slide.target = '_top';
    slide.rel = 'noreferrer';
    slide.setAttribute('tabindex', '0'); // Make the slide focusable

    // Key event listener for remote control input
    slide.addEventListener('keydown', function(event) {
    if (event.keyCode === 13) { // Enter key
    window.location.href = this.href;
    }
    });

    const backdrop = document.createElement('img');
    backdrop.className = 'backdrop';
    backdrop.src = /Items/${itemId}/Images/Backdrop/0;
    backdrop.alt = 'Backdrop';

    const logo = document.createElement('img');
    logo.className = 'logo';
    logo.src = /Items/${itemId}/Images/Logo;
    logo.alt = 'Logo';

    const featuredContent = document.createElement('div');
    featuredContent.className = 'featured-content';
    featuredContent.textContent = title;

    const plotElement = document.createElement('div');
    plotElement.className = 'plot';
    plotElement.textContent = plot;

    // Truncate the text of this specific plot element
    truncateText(plotElement, 240); // Adjust 240 to your preferred character limit

    slide.appendChild(backdrop);
    slide.appendChild(logo);
    slide.appendChild(featuredContent);
    slide.appendChild(plotElement);

    return slide;
    }

    function initializeSlideshow() {
    var slides = document.querySelectorAll(".slide");
    var currentSlide = 0;
    var shuffledIndexes = shuffleArray(Array.from({ length: slides.length }, (_, i) => i));

    function showSlide(index) {
    for (var i = 0; i < slides.length; i++) {
    slides[i].style.display = "none";
    }
    slides[shuffledIndexes[index]].style.display = "block";
    }

    function nextSlide() {
    currentSlide = (currentSlide + 1) % slides.length;
    showSlide(currentSlide);
    }

    showSlide(currentSlide);
    setInterval(nextSlide, shuffleInterval);
    }

    function fetchMovies() {
    const noCacheUrl = listFileName + '?' + new Date().getTime();

    fetch(noCacheUrl)
    .then(response => {
    if (response.ok) {
    return response.text();
    } else {
    throw new Error('list.txt not found, fetching movies from server.');
    }
    })
    .then(text => {
    const lines = text.split('\n').filter(Boolean);
    title = lines.shift() || 'Spotlight'; // Set the global title

    const movieIds = lines.map(line => line.substring(0, 32));
    return Promise.all(movieIds.map(id => fetchMovieDetails(id)));
    })
    .then(movies => {
    movies.forEach(movie => createSlideForMovie(movie, title));
    initializeSlideshow();
    })
    .catch(error => {
    console.error(error);
    fetchMoviesFromServer(); // Fallback to fetching movies from the server
    });
    }

    function fetchMovieDetails(movieId) {
    return fetch(/Users/${userId}/Items/${movieId}, {
    headers: {
    'Authorization': MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"
    }
    })
    .then(response => response.json())
    .then(movie => {
    console.log("Movie Title:", movie.Name);
    console.log("Movie Overview:", movie.Overview);
    return movie;
    });
    }

    function fetchMoviesFromServer() {
    title = 'Spotlight'; // Reset title to 'Spotlight' when fetching from server
    fetch(/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&Limit=300, {
    headers: {
    'Authorization': MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"
    }
    })
    .then(response => response.json())
    .then(data => {
    const movies = data.Items;
    const shuffledMovies = shuffleArray(movies);
    const selectedMovieIds = shuffledMovies.slice(0, 30).map(movie => movie.Id);
    return Promise.all(selectedMovieIds.map(id => fetchMovieDetails(id)));
    })
    .then(movies => {
    movies.forEach(movie => createSlideForMovie(movie, 'Spotlight'));
    initializeSlideshow();
    })

    .catch(error => console.error('Error fetching movies:', error));
    }

    fetchMovies();
    </script>
    </body>
    </html>
    1
    SethBacon
    Offline

    Member

    Posts: 51
    Threads: 3
    Joined: 2023 Nov
    Reputation: 5
    Country:Canada
    #13
    2023-11-12, 09:22 AM (This post was last modified: 2023-11-12, 09:24 AM by SethBacon.)
    Oof it butchered my spacing. Attached. So in the video, we see the selection moving by remote (This is in Jellyfin for Android Not Jellyfin for Android TV, android has some remote accessibility, its limited, but still) so anyways we see the selection move up through ozark and (presumably, though my highlight isnt working) onto Jarhead.

    ... and now i cant upload my vid cause this forum be trippin  

    here https://imgur.com/a/TSAhqkR

    Attachment size limit  Upside-down-face


    Attached Files Thumbnail(s)
           

    .html   slideshow.html (Size: 8 KB / Downloads: 65)
    SethBacon
    Offline

    Member

    Posts: 51
    Threads: 3
    Joined: 2023 Nov
    Reputation: 5
    Country:Canada
    #14
    2023-11-12, 09:28 AM (This post was last modified: 2023-11-12, 09:29 AM by SethBacon. Edited 1 time in total.)
    So, all that being said, Im still surprised you mention getting this to run in Jellyfin for Android TV: I thought that app did not use the web UI / respect any css customization (and its the one im forced to use for firesticks 😠). Or maybe, like me, you're testing in Jellyfin for Android on a TV? or the Roku app maybe?
    BobHasNoSoul
    Offline

    Junior Member

    Posts: 28
    Threads: 2
    Joined: 2023 Oct
    Reputation: 0
    #15
    2023-11-12, 09:36 PM (This post was last modified: 2023-11-12, 10:05 PM by BobHasNoSoul. Edited 1 time in total.)
    it works in the android jellyfin app on fire stick, also works on the lg tv jellyfin client.. (except the actual clicking on the item inside the featured bar.. still having issues with that i can get it to select the item now but i need to figure out a way to dymanically load the links inside the page.. with work and kids im not sure it will be a fast thing i implement however this is on the todo list) works on xbox also incase anyone is wondering using the standard jellyfin app and the clicking on that works because it uses a pointer for some reason.

    Also holy hell thats a decent upgrade to it.. quick question you mentioned slideshow.js can you elaborate a bit more on that (i will merge it into the github)

    (2023-11-12, 09:28 AM)SethBacon Wrote: So, all that being said, Im still surprised you mention getting this to run in Jellyfin for Android TV: I thought that app did not use the web UI / respect any css customization (and its the one im forced to use for firesticks 😠). Or maybe, like me, you're testing in Jellyfin for Android on a TV? or the Roku app maybe?
    HP ProLiant DL380 Gen9 142GB 56TB pool soon to be 80TB
    SethBacon
    Offline

    Member

    Posts: 51
    Threads: 3
    Joined: 2023 Nov
    Reputation: 5
    Country:Canada
    #16
    2023-11-12, 10:11 PM (This post was last modified: 2023-11-12, 10:27 PM by SethBacon. Edited 1 time in total.)
    Oh, I just meant the javascript section in the slideshow.html, its all in there. Anyways cheers! its got everything I need for now (i was playing around with different transition option or a button to go back and forth between slides but meh..) Im already finding and watching stuff Id forgotten about! I still think were talking about different tv apps but I'm really happy to have this new functionality for some users. I really really think this is an invaluable UI experience when you get into larger libraries. Super happy to help in anyway to push this towards a full plugin or official feature, ping anytime.
    1hitsong
    Offline

    Moderator

    Posts: 95
    Threads: 11
    Joined: 2023 Jun
    Reputation: 3
    #17
    2023-11-13, 01:02 AM
    This looks neat and would be a fairly simple thing to add to Roku. Are y'all only featuring movies, or are all libraries fair game?
    🤘 Enjoy

    SethBacon
    Offline

    Member

    Posts: 51
    Threads: 3
    Joined: 2023 Nov
    Reputation: 5
    Country:Canada
    #18
    2023-11-13, 02:05 AM (This post was last modified: 2023-11-13, 02:08 AM by SethBacon. Edited 2 times in total.)
    This should work as pictured for any content with fanart, logos and plots so Im pulling from Movies And Series in the line -

    fetch(/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&Limit=300

    I bet episodes would work too. The recursive flag is me trying to get a random selection, which it does, the problem is its always the Same random selection, so we grab 30 of them and shuffle that list (before pulling any content)
    BobHasNoSoul
    Offline

    Junior Member

    Posts: 28
    Threads: 2
    Joined: 2023 Oct
    Reputation: 0
    #19
    2023-11-13, 06:36 PM
    (2023-11-13, 02:05 AM)SethBacon Wrote: This should work as pictured for any content with fanart, logos and plots so Im pulling from Movies And Series in the line -

    fetch(/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&Limit=300

    I bet episodes would work too. The recursive flag is me trying to get a random selection, which it does, the problem is its always the Same random selection, so we grab 30 of them and shuffle that list (before pulling any content)

    i ran into a problem trying to get the focus to work, can you maybe explain what you did to get yours to work.. i have got the html to work but needed to fix a few syntax issues (think the forum messed up a few things on it) but for the life of me the focus doesnt work at all on it from what i have tried

    the downside to using episodes is the logo isnt always linked the same as the parent so you would end up with a blank space or a white background if there isnt a backdrop

    im going to add the html as another option in the github while i figure a few things out if thats okay?
    HP ProLiant DL380 Gen9 142GB 56TB pool soon to be 80TB
    SethBacon
    Offline

    Member

    Posts: 51
    Threads: 3
    Joined: 2023 Nov
    Reputation: 5
    Country:Canada
    #20
    2023-11-13, 07:37 PM
    Absolutely, all open source here, but this is your riceball (a solution I've been searching for for like seriously 2 years so i'm happy to help). 

    [Clarity here for newcomers] Attached are my 2 files needed to run this - slideshow.html, list.txt (these live in \Jellyfin\Server\jellyfin-web\avatars) after putting them there, go to the /jellyfin-web folder and find the file home-html.randomcharacters.chunk.js, open it with a text editor and insert this line:

    <style>.featurediframe { width: 93vw; height: 350px; display: block; border: 0px solid #000; margin: 0 auto; margin-bottom: 40px}</style><iframe class="featurediframe" src="/web/avatars/slideshow.html"></iframe>

    directly after data-backdroptype="movie,series,book">. Restart JF and clear browser cache to get it loaded. If you remove list.txt it will pick pseudo-random movies and series. (Randomization could use work)

    I tried to fix the missing logo issue 3 different ways - checking for return errors, checking content length, and prefetching images but none worked. Will need fresh eyes later. 

    Regarding selection, did you definitely have this line in .slide?  cursor: pointer;


    Attached Files
    .html   slideshow.html (Size: 8 KB / Downloads: 186)
    .txt   list.txt (Size: 611 bytes / Downloads: 201)
    Pages (10): « Previous 1 2 3 4 5 … 10 Next »
     

    « Next Oldest | Next Newest »

    Users browsing this thread: 3 Guest(s)


    • View a Printable Version
    • Subscribe to this thread
    Forum Jump:

    Home · Team · Help · Contact
    © Designed by D&D - Powered by MyBB
    L


    Jellyfin

    The Free Software Media System

    Linear Mode
    Threaded Mode