Incestflix - UI & Playback Tweaks

Improved site layout, windowed fullscreen support, new video playback options

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==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});