您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improved site layout, windowed fullscreen support, new video playback options
// ==UserScript== // @name Incestflix - UI & Playback Tweaks // @namespace shobu-san/scripts // @version 0.0.5 // @description Improved site layout, windowed fullscreen support, new video playback options // @author shobu-san // @license MIT // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @run-at document-start // @match *://*.incestflix.org/* // @match *://*.incestflix.com/* // ==/UserScript== // Fullscreen CSS for windowed fullscreen const windowedFullscreenCss = ` /* Expand video player for windowed fullscreen */ #incflix-bodywrap { max-width: none; text-align: center; } #incflix-player { max-height: 100%; height: 100%; } #incflix-videowrap { background: #0f0f0f; padding: 0.5em 0em; margin: 0.5em 0; } ::-webkit-scrollbar { display: none; } /* Fix recommended videos/tags */ #photos .img-overflow { margin: 1px; width: 445px !important; height: 250px !important; } #incflix-indexwrap { max-width: none; text-align: center; } #tables { text-align: left; padding: 0em 0.5em; max-width: none; } #videotags { width: 100%; margin-top: 0em; text-align: left; } `; // Menu CSS for latest/random/popular video pages const menuCss = ` /* Expand search results to fit screen */ #incflix-bodywrap { max-width: none; text-align: center; } /* Fix recommended videos/tags */ #photos .img-overflow { margin: 1px; width: 445px !important; height: 250px !important; } #incflix-indexwrap { max-width: none; text-align: center; } #tables { text-align: left; padding: 0em 0.5em; max-width: none; } #videotags { width: 100%; margin-top: 0em; text-align: left; } #incflix-header{ position:absolute; top:-75px; } [class=headerlogo]{ position:absolute; top:70px; left:0px; transform: scale(0.5); } #tagline{ display:none!important; } #videotags { width: 100%; margin-top: 0em; text-align: left; } `; let styleNode = null; // Used to load CSS rules into the page let videoPlayer = null; // Global variable to hold the video player element let pauseTimeout = 200 // Time in milliseconds to wait before a pause event is actioned // Menu options const menuOptions = { windowedFullscreen: { state: true, label: 'Enable Windowed Fullscreen' }, fullscreenOnPlay: { state: true, label: 'Fullscreen on Play' }, fullscreenOnLoad: { state: false, label: 'Fullscreen on Load' }, autoplayOnLoad: { state: false, label: 'Autoplay on Load' }, mutedOnLoad: { state: false, label: 'Muted on Load' }, reloadOnStalled: { state: true, label: 'Auto-reload Stalled Video - BETA' }, enableLogging: { state: false, label: 'DEBUG: Enable Logging' }, }; // Function for logging if debug mode is enabled function logDebug(message, ...optionalParams) { if (getState('enableLogging')) { console.log(`DEBUG: ${message}`, ...optionalParams); } } /** * Get the saved state of an option or the default state if not saved. * @param {string} option - The option key. * @returns {boolean} - The state of the option. */ function getState(option) { const savedMenu = GM_getValue('menuOptions', menuOptions); return savedMenu.hasOwnProperty(option) ? savedMenu[option].state : menuOptions[option].state; } /** * Toggle the state of a menu option and update the menu display. * @param {string} option - The option key to toggle. */ function toggleOption(option) { const currentState = getState(option); menuOptions[option].state = !currentState; GM_setValue('menuOptions', menuOptions); displayMenu(); } /** * Build the Tampermonkey menu based on the current state of the options. */ function displayMenu() { Object.entries(menuOptions).forEach(([key, option]) => { const currentState = getState(key); GM_registerMenuCommand(`[${currentState ? '✔️' : '❌'}]: ${option.label}`, () => toggleOption(key), { id: key, autoClose: false }); }); } /** * Injects the provided CSS into the document. * @param {string} css - The CSS to be injected. */ function injectStyle(css) { if (!styleNode) { logDebug('Injecting CSS into the document'); styleNode = document.createElement('style'); styleNode.textContent = css; (document.head || document.documentElement).appendChild(styleNode); } } /** * Removes the injected CSS from the document. */ function removeStyle() { if (styleNode) { logDebug('Removing injected CSS'); styleNode.remove(); styleNode = null; } } // Function to check if the URL contains '/watch' function applyMenuCss() { const url = window.location.href; logDebug('Checking if URL contains /watch'); if (!url.includes('/watch')) { logDebug('URL does not contain /watch, applying menu CSS'); injectStyle(menuCss); // Enable the CSS immediately if URL doesn't contain '/watch' } } /** * Enable fullscreen mode by applying styles and scrolling the video player into view. * If windowed fullscreen is disabled, it will trigger the browser's normal fullscreen. * @param {HTMLElement} videoPlayer - The video player element. */ function enableFullscreen() { const windowedFullscreen = getState('windowedFullscreen'); logDebug(`Enabling fullscreen. Windowed Fullscreen: ${windowedFullscreen}`); if (windowedFullscreen) { injectStyle(windowedFullscreenCss); // Enable windowed fullscreen videoPlayer.scrollIntoView({ behavior: 'instant' }); } else { // Trigger normal fullscreen mode if (videoPlayer && videoPlayer.requestFullscreen) { logDebug('Requesting browser fullscreen'); videoPlayer.requestFullscreen(); } } } /** * Disable fullscreen mode by removing styles or exiting normal fullscreen. */ function disableFullscreen() { const windowedFullscreen = getState('windowedFullscreen'); logDebug('Disabling fullscreen'); removeStyle(); // Disable windowed fullscreen if (windowedFullscreen) { window.scrollTo(0, 0); // Scroll back to the top of the page } else if (document.fullscreenElement) { // Exit normal fullscreen if active logDebug('Exiting browser fullscreen'); document.exitFullscreen(); } } // Function to check if the video player exists function getVideoPlayer() { videoPlayer = document.querySelector('#incflix-player'); logDebug('Checking if video player exists:', videoPlayer ? 'Found' : 'Not Found'); } async function getStatus(singleVideo) { return new Promise(resolve => { setTimeout(() => { resolve(singleVideo); }, pauseTimeout); }); } // Function to handle fullscreen behavior on video play function handlePlay() { if (!videoPlayer) return; const fullscreenOnPlay = getState('fullscreenOnPlay'); logDebug(`Video started playing. Fullscreen on Play: ${fullscreenOnPlay}`); if (fullscreenOnPlay) { enableFullscreen(videoPlayer); } } // Function to handle disabling fullscreen on video pause function handlePause() { if (!videoPlayer) return; const fullscreenOnPlay = getState('fullscreenOnPlay'); getStatus(videoPlayer).then(video => { if(video.paused){ logDebug(`Video paused. Fullscreen on Play: ${fullscreenOnPlay}`); if (fullscreenOnPlay) { disableFullscreen(); // Disable fullscreen when the video is actually paused } }else{ logDebug(`Video is not paused after ${pauseTimeout}ms, skipping pause event.`); }}) } // Function to handle stalled event by reloading the video at the same timestamp function handleStalled() { if (!videoPlayer) return; const currentTime = videoPlayer.currentTime; // Capture the current timestamp logDebug(`Video stalled at ${currentTime}s. Reloading video source...`); const src = videoPlayer.currentSrc; // Capture the current video source videoPlayer.src = ''; // Clear the source to force reload videoPlayer.load(); // Reset the video element videoPlayer.src = src; // Reassign the source videoPlayer.currentTime = currentTime; // Seek to the same timestamp videoPlayer.play(); // Start playing from the same position logDebug('Video reloaded and resumed at the same timestamp.'); } // Function to handle video settings on page load function initializeVideoPlayerSettings() { logDebug('Initializing video player settings'); if (!videoPlayer) return; // Fullscreen on Load if (getState('fullscreenOnLoad')) { logDebug('Fullscreen on load is enabled'); enableFullscreen(videoPlayer); } // Autoplay on Load if (getState('autoplayOnLoad')) { logDebug('Autoplay on load is enabled'); videoPlayer.play(); } // Mute on Load if (getState('mutedOnLoad')) { logDebug('Muted on load is enabled'); videoPlayer.muted = true; } } // Attach event listeners to video player function attachVideoPlayerListeners() { if (!videoPlayer) return; logDebug('Attaching event listeners to video player'); videoPlayer.addEventListener('play', handlePlay, { passive: true }); videoPlayer.addEventListener('pause', handlePause, { passive: true }); videoPlayer.addEventListener('stalled', handleStalled, { passive: true }); } // Initialize the script function init() { getVideoPlayer(); initializeVideoPlayerSettings(); attachVideoPlayerListeners(); } logDebug('Script initialized'); displayMenu(); applyMenuCss(); // Run the initialization after the page is fully loaded window.addEventListener('load', init, {passive: true});