// ==UserScript== // @name Jellyfin Movie Card Trailer Playback // @namespace http://tampermonkey.net/ // @version 0.8 // @description Plays trailer when hovering over movie card in Jellyfin, and navigates to movie detail page on video click. // @author Your Name // @match http://localhost:8096/web/index.html* // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // Add styles for video display GM_addStyle(` .preview-video { position: absolute; top: 0; left: 0; width: 100%; height: auto; z-index: 10; display: none; object-fit: cover; /* Ensure video fits the card size */ } .card { position: relative; overflow: visible; /* Ensure video does not exceed card bounds */ } `); let currentVideoElement = null; // Add event listeners to cards function addEventListenersToCards() { const movieCardSelector = '.card'; // Adjust selector according to Jellyfin's actual HTML structure const movieCards = document.querySelectorAll(movieCardSelector); movieCards.forEach(card => { card.addEventListener('mouseenter', async function() { if (currentVideoElement) { return; // Return if a video element is already playing } const videoElement = document.createElement('video'); videoElement.classList.add('preview-video'); card.appendChild(videoElement); currentVideoElement = videoElement; // Use the provided trailer URL const trailerUrl = "http://localhost:8096/Videos/2a92e925159c3b5f941ef2697183940d/stream.mp4?Static=true&mediaSourceId=2a92e925159c3b5f941ef2697183940d&deviceId=TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzEyNi4wLjAuMCBTYWZhcmkvNTM3LjM2IEVkZy8xMjYuMC4wLjB8MTcyMDA3NzcwMDUyNA11&api_key=0b61fb304c1d477882156032ea0b6ad5&Tag=ba4e0e4a2ba88ea00552067682c5c08e"; videoElement.src = trailerUrl; videoElement.muted = true; // Mute the video to avoid autoplay restrictions videoElement.style.display = 'block'; // Get movie link const movieLink = card.querySelector('a') ? card.querySelector('a').href : null; if (movieLink) { videoElement.addEventListener('click', () => { window.location.href = movieLink; }); } try { await videoElement.play(); console.log('Video played successfully'); } catch (error) { console.error('Video playback error:', error); } card.addEventListener('mouseleave', function() { videoElement.pause(); videoElement.remove(); currentVideoElement = null; // Clear the current video element }, { once: true }); }); }); } // Use MutationObserver to monitor DOM changes, ensuring the script runs after content is loaded const observer = new MutationObserver((mutations, obs) => { const movieCardSelector = '.card'; if (document.querySelector(movieCardSelector)) { addEventListenersToCards(); } mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1 && node.matches(movieCardSelector)) { addEventListenersToCards(); } }); }); }); // Start observing document.body for changes in child and descendant nodes observer.observe(document.body, { childList: true, subtree: true }); // Initial load window.addEventListener('load', addEventListenersToCards); })();