Crunchyroll Auto Skip Intro/Outro, Fullscreen Video & Mouse Volume Control

Automatically clicks the Skip Intro button on Crunchyroll.com when available and makes the video fullscreen

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Crunchyroll Auto Skip Intro/Outro, Fullscreen Video & Mouse Volume Control
// @namespace    https://greasyfork.org/en/users/807108-jeremy-r
// @version      5.1
// @description  Automatically clicks the Skip Intro button on Crunchyroll.com when available and makes the video fullscreen
// @author       JRem
// @match        https://*.crunchyroll.com/watch/*
// @match        https://static.crunchyroll.com/vilos-v2/web/vilos/player.html
// @grant        GM_addStyle
// @grant        GM.xmlHttpRequest
// @license MIT
// ==/UserScript==

////////////////////////
// USER CUSTOMIZATION //
////////////////////////
// 1 = Enabled / 0 = Disabled
const enableFullscreen=1;
const enableSkipIntro=1;
const enableSkipCredits=1;
const enableVolumeControl=1;
// Volume +/- percentage
const volumePercentage = 5; // Default 5, Set the percentage of volume change (+/-)
// Volume Control Disable/Bypass
const holdDisableKey = 'Alt'; // Key to temporarily disable while being pressed (e.g., 'h')
const disableKey = 'Shift'; // Key to completely disable the event listener (e.g., 'd')
// Global user settings for toast appearance and positioning
const toastSettings = {
    toastPositionX: '45', // Default='45' (LeftCenter), Range '0' to '100' on the X axis
    toastPositionY: '0', // Default='0' (Top), Range '0' to '100' on the Y axis
    fontSize: '16px', // Font size for the toast message
    fontFamily: 'Arial, sans-serif', // Font family for the toast message
    backgroundColor: '#333', // Background color for the toast
    textColor: 'white', // Text color for the toast
    padding: '10px', // Padding around the toast text
    margin: '5px', // Margin between toasts
    borderRadius: '5px', // Border radius for rounded corners
    toastDuration: 3500, // Duration to display the toast (in ms)
    fadeDuration: 300, // Duration of the fade-in/out animation (in ms)
};

// Function to show or update toast message
function showToast(message) {
    // Create a toast container if it doesn't already exist
    let toastContainer = document.getElementById('toast-container');
    if (!toastContainer) {
        toastContainer = document.createElement('div');
        toastContainer.id = 'toast-container';
        document.body.appendChild(toastContainer);

        // Add styles for the toast container
        console.log(`transform: translateX(${toastSettings.toastPositionX}%);`);
        console.log(`transform: translateY(${toastSettings.toastPositionY}%);`);
        const style = document.createElement('style');
        style.innerHTML = `
            #toast-container {
                position: fixed;
                z-index: 9999;
                ${toastSettings.position}: 10px;
                ${toastSettings.align}: 50%;
                transform: translateX(${toastSettings.toastPositionX}vw);
                max-width: 90%;
                pointer-events: none; /* Prevent interaction with toasts */
            }
            .toast {
                background-color: ${toastSettings.backgroundColor};
                color: ${toastSettings.textColor};
                padding: ${toastSettings.padding};
                margin: ${toastSettings.margin};
                border-radius: ${toastSettings.borderRadius};
                font-family: ${toastSettings.fontFamily};
                font-size: ${toastSettings.fontSize};
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
                opacity: 0;
                transform: translateY(${toastSettings.toastPositionY}vh);
                animation: fadeIn ${toastSettings.fadeDuration / 1000}s forwards, fadeOut 3s forwards ${toastSettings.toastDuration / 1000 - toastSettings.fadeDuration / 1000}s;
            }
            @keyframes fadeIn {
                to {
                    opacity: 1;
                    transform: translateY(0);
                }
            }
            @keyframes fadeOut {
                to {
                    opacity: 0;
                    transform: translateY(20px);
                }
            }
        `;
        document.head.appendChild(style);
    }

    // Function to update toast position (reverts to the original fixed position)
    function updateToastPosition() {
        // Restore the toast position as fixed on the screen
        toastContainer.style.left = ''; // Let it use the default position from toastSettings
        toastContainer.style.top = ''; // Let it use the default position from toastSettings
        toastContainer.style.width = ''; // Use max-width as defined
        toastContainer.style.height = ''; // Default height
    }

    // Update position on fullscreen change
    document.addEventListener('fullscreenchange', updateToastPosition);
    document.addEventListener('webkitfullscreenchange', updateToastPosition); // for Safari
    document.addEventListener('mozfullscreenchange', updateToastPosition); // for Firefox
    document.addEventListener('msfullscreenchange', updateToastPosition); // for IE/Edge

    // Update position initially (restores the default fixed positioning)
    updateToastPosition();

    // Check if there's an existing toast being displayed
    let currentToast = toastContainer.querySelector('.toast');
    if (currentToast) {
        // If a toast is already visible, update its text content
        currentToast.textContent = message;
        // Reset the animation so that the toast updates instantly
        currentToast.style.animation = 'none';
        currentToast.offsetHeight; // Trigger reflow to restart animation
        currentToast.style.animation = `fadeIn ${toastSettings.fadeDuration / 1000}s forwards, fadeOut 3s forwards ${toastSettings.toastDuration / 1000 - toastSettings.fadeDuration / 1000}s`;
    } else {
        // If no toast is visible, create a new toast
        const toast = document.createElement('div');
        toast.classList.add('toast');
        toast.textContent = message;
        toastContainer.appendChild(toast);
    }

    // Remove the toast after the duration is finished
    setTimeout(() => {
        if (currentToast) {
            toastContainer.removeChild(currentToast);
        }
    }, toastSettings.toastDuration);
}


// Fullscreen CSS Edit
if (enableFullscreen == 1) {
var css = '.video-player-wrapper { max-height: calc(100vh - 5.625rem) !important; height: calc(100vh) !important; }';
css += '.erc-header { flex: 0 0 1.55rem !important; }';
css += '.erc-header .header-content { height: 0 !important; }';
GM_addStyle(css);
showToast('Fullscreen enabled'); };

// Volume Control via mouse scroll
// Variable to track whether the volume control event listener is active
let isVolumeControlEnabled = true;
if (enableVolumeControl == 1) {
// Function to simulate the click event
function simulate(element, event) {
    const evt = new MouseEvent(event, { bubbles: true, cancelable: true });
    element.dispatchEvent(evt);
}
    // Get the current video element
    const video = document.querySelector('video');
    // Function to adjust volume
// Function to adjust volume
function adjustVolume(video, percentage) {
    if (!video) {
        console.error("Video element not found.");
        return;
    }

    // Get the current volume (between 0 and 1)
    let currentVolume = video.volume;

    // Calculate the new volume by adjusting it with the percentage
    let volumeChange = currentVolume + (percentage / 100);

    // Ensure the volume is within the valid range of 0 to 1
    if (volumeChange > 1) {
        volumeChange = 1;
    } else if (volumeChange < 0) {
        volumeChange = 0;
    }

    // Apply the new volume to the video
    video.volume = volumeChange;
    var newVol = (video.volume * 100).toFixed(2);
    showToast(`Volume: ${newVol}%`);
}

// Event listener for mouse scroll to adjust volume
let volumeScrollListener = (event) => {
    if (!isVolumeControlEnabled) return; // If volume control is disabled, do nothing

    // Check the direction of the scroll
    if (event.deltaY < 0) {
        // Scroll up (increase volume)
        adjustVolume(video, volumePercentage);
    } else if (event.deltaY > 0) {
        // Scroll down (decrease volume)
        adjustVolume(video, -volumePercentage);
    }
    // Prevent the default action to avoid scrolling the page
    event.preventDefault();
};

// Listen for the mouse wheel event over the video
if (document.getElementById("vilos")) {
    document.getElementById("vilos").addEventListener('wheel', volumeScrollListener);
}

// Listen for keypress events to control volume event listener status
document.addEventListener('keydown', (event) => {
    // Check if the user pressed the key to toggle the event listener on/off
    if (event.key === disableKey) {
        isVolumeControlEnabled = !isVolumeControlEnabled; // Toggle the enable/disable state
        const status = isVolumeControlEnabled ? 'enabled' : 'disabled';
        showToast(`Volume control ${status}`);
    }

    // Check if the user pressed the key to temporarily disable the event listener while pressed
    if (event.key === holdDisableKey) {
        isVolumeControlEnabled = false;
    }
});

// Listen for keyup event to re-enable the temporary disable when the key is released
document.addEventListener('keyup', (event) => {
    // Re-enable volume control when the hold-disable key is released
    if (event.key === holdDisableKey) {
        isVolumeControlEnabled = true;
    }
});

};

// Check for and click Skip Intro
if (enableSkipIntro == 1 || enableSkipCredits == 1) {
setInterval(function () {
// Check for skip intro button
    if (enableSkipIntro ==1 ) {
const skipIntroBtn = document.querySelector('div[data-testid="skipIntroText"]');
if (skipIntroBtn !== null && (skipIntroBtn.textContent.includes("SKIP INTRO"))) {
    simulate(skipIntroBtn, "click");
    console.log('Skip Btn Found');
    showToast('Intro Skipped');
} };

// Check for skip credits button
    if (enableSkipIntro ==1 ) {
const skipCreditsBtn = document.querySelector('div[data-testid="skipIntroText"]');
if (skipCreditsBtn !== null && (skipCreditsBtn.textContent.includes("SKIP CREDITS"))) {
    simulate(skipCreditsBtn, "click");
    console.log('Skip Btn Found');
    showToast('Credits Skipped');
}}
}, 1000)
};