5-Minute Auto-Next Chapter with Navigation Controls

Automatically clicks the next chapter button after customizable time, stops after customizable number of chapters, with manual navigation

目前為 2025-04-19 提交的版本,檢視 最新版本

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         5-Minute Auto-Next Chapter with Navigation Controls
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Automatically clicks the next chapter button after customizable time, stops after customizable number of chapters, with manual navigation
// @author       You
// @match        https://inovel*
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    // Configuration
    const DEFAULT_MAX_CHAPTERS = 4; // Default maximum number of chapters
    // Default countdown will be determined based on audio duration or fallback to 7 minutes

    // Load user preferences from localStorage or use defaults
    let userSettings = JSON.parse(localStorage.getItem('autoNextSettings')) || {};
    let MAX_CHAPTERS = userSettings.maxChapters || DEFAULT_MAX_CHAPTERS;
    let isMinimized = userSettings.isMinimized || false;
    
    // Initialize with a temporary default that will be updated
    let COUNTDOWN_MINUTES = 7; // Will be updated based on audio duration

    // Selector for the next chapter button
    const NEXT_BUTTON_SELECTOR = 'a.nextchap[rel="next"]';
    // Selector for the previous chapter button
    const PREV_BUTTON_SELECTOR = 'a.prevchap[rel="prev"]';

    // Convert minutes to milliseconds (initial value, will be updated)
    let countdownMs = COUNTDOWN_MINUTES * 60 * 1000;

    // Chapter counter - initialize or retrieve from session storage
    let chaptersNavigated = parseInt(sessionStorage.getItem('auto_next_chapters_count') || '0');

    // Check if this is a "next chapter" page by checking session storage
    const isFirstPage = !sessionStorage.getItem('auto_next_started');

    // Timer states
    let isRunning = false;
    let isPaused = false;
    let startTime = 0;
    let endTime = 0;
    let remainingTime = countdownMs;
    let countdownInterval;
    let settingsPanelOpen = false;

    // Create main container
    const mainContainer = document.createElement('div');
    mainContainer.style.cssText = `
        position: fixed;
        bottom: 80px;
        left: 20px;
        z-index: 9999;
    `;
    document.body.appendChild(mainContainer);

    // Create the expanded view container
    const expandedView = document.createElement('div');
    expandedView.className = 'auto-next-expanded';
    expandedView.style.cssText = `
        background-color: rgba(0, 0, 0, 0.7);
        color: white;
        padding: 15px;
        border-radius: 8px;
        font-size: 16px;
        font-family: Arial, sans-serif;
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 10px;
        min-width: 180px;
        max-width: 250px;
        touch-action: manipulation;
    `;

    // Create minimized bubble view with timer
    const bubbleView = document.createElement('div');
    bubbleView.className = 'auto-next-bubble';
    bubbleView.style.cssText = `
        width: 70px;
        height: 70px;
        border-radius: 50%;
        background-color: rgba(0, 0, 0, 0.7);
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        cursor: pointer;
        padding: 5px;
    `;

    // Create timer icon
    const bubbleIcon = document.createElement('div');
    bubbleIcon.style.cssText = `
        font-size: 20px;
        margin-bottom: 2px;
    `;
    bubbleIcon.innerHTML = '⏱️';

    // Create timer text for bubble
    const bubbleTimer = document.createElement('div');
    bubbleTimer.style.cssText = `
        font-size: 12px;
        color: white;
        font-weight: bold;
    `;
    bubbleTimer.textContent = COUNTDOWN_MINUTES + ':00';

    bubbleView.appendChild(bubbleIcon);
    bubbleView.appendChild(bubbleTimer);

    // Function to toggle between views
    function toggleView() {
        isMinimized = !isMinimized;
        updateViewState();

        // Save state
        userSettings.isMinimized = isMinimized;
        localStorage.setItem('autoNextSettings', JSON.stringify(userSettings));
    }

    // Function to update the view based on minimized state
    function updateViewState() {
        if (isMinimized) {
            // Show bubble view, hide expanded view
            if (mainContainer.contains(expandedView)) {
                mainContainer.removeChild(expandedView);
            }
            if (!mainContainer.contains(bubbleView)) {
                mainContainer.appendChild(bubbleView);
            }
        } else {
            // Show expanded view, hide bubble view
            if (mainContainer.contains(bubbleView)) {
                mainContainer.removeChild(bubbleView);
            }
            if (!mainContainer.contains(expandedView)) {
                mainContainer.appendChild(expandedView);
            }
        }
    }

    // Add click handler to bubble
    bubbleView.addEventListener('click', toggleView);

    // Create timer text element
    const timerText = document.createElement('div');
    timerText.className = 'timer-text';
    timerText.style.cssText = `
        font-size: 18px;
        font-weight: bold;
        margin-bottom: 5px;
        text-align: center;
        width: 100%;
    `;
    timerText.textContent = `Next chapter in: ${COUNTDOWN_MINUTES}:00`;
    expandedView.appendChild(timerText);

    // Create chapter counter text element
    const chapterCounterText = document.createElement('div');
    chapterCounterText.className = 'chapter-counter-text';
    chapterCounterText.style.cssText = `
        font-size: 14px;
        color: #ffcc00;
        margin-bottom: 5px;
        text-align: center;
        width: 100%;
    `;
    chapterCounterText.textContent = `Chapters: ${chaptersNavigated}/${MAX_CHAPTERS}`;
    expandedView.appendChild(chapterCounterText);

    // Create playback rate display element - NEW ADDITION
    const playbackRateText = document.createElement('div');
    playbackRateText.className = 'playback-rate-text';
    playbackRateText.style.cssText = `
        font-size: 12px;
        color: #8cf;
        margin-bottom: 5px;
        text-align: center;
        width: 100%;
    `;
    const currentPlaybackRate = parseFloat(localStorage.getItem('audio_playback_rate')) || 1.0;
    playbackRateText.textContent = `Playback Speed: ${currentPlaybackRate.toFixed(1)}x`;
    expandedView.appendChild(playbackRateText);

    // Create settings panel (initially hidden)
    const settingsPanel = document.createElement('div');
    settingsPanel.style.cssText = `
        background-color: rgba(40, 40, 40, 0.95);
        padding: 12px;
        border-radius: 5px;
        margin-top: 8px;
        display: none;
        width: 100%;
    `;

    // Create minutes input with label
    const timerSettingContainer = document.createElement('div');
    timerSettingContainer.style.cssText = `
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 10px;
    `;

    const timerLabel = document.createElement('label');
    timerLabel.textContent = 'Timer (minutes):';
    timerLabel.style.marginRight = '10px';

    const timerInput = document.createElement('input');
    timerInput.type = 'number';
    timerInput.min = '0.5';
    timerInput.max = '60';
    timerInput.step = '0.5';
    timerInput.value = COUNTDOWN_MINUTES;
    timerInput.style.cssText = `
        width: 60px;
        background-color: #333;
        color: white;
        border: 1px solid #555;
        border-radius: 3px;
        padding: 4px;
    `;

    timerSettingContainer.appendChild(timerLabel);
    timerSettingContainer.appendChild(timerInput);

    // Create max chapters input with label
    const maxChaptersContainer = document.createElement('div');
    maxChaptersContainer.style.cssText = `
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 10px;
    `;

    const maxChaptersLabel = document.createElement('label');
    maxChaptersLabel.textContent = 'Max Chapters:';
    maxChaptersLabel.style.marginRight = '10px';

    const maxChaptersInput = document.createElement('input');
    maxChaptersInput.type = 'number';
    maxChaptersInput.min = '1';
    maxChaptersInput.max = '20';
    maxChaptersInput.step = '1';
    maxChaptersInput.value = MAX_CHAPTERS;
    maxChaptersInput.style.cssText = `
        width: 60px;
        background-color: #333;
        color: white;
        border: 1px solid #555;
        border-radius: 3px;
        padding: 4px;
    `;

    maxChaptersContainer.appendChild(maxChaptersLabel);
    maxChaptersContainer.appendChild(maxChaptersInput);

    // Create save and cancel buttons
    const settingsButtonContainer = document.createElement('div');
    settingsButtonContainer.style.cssText = `
        display: flex;
        justify-content: space-between;
        margin-top: 10px;
    `;

    const saveButton = document.createElement('button');
    saveButton.textContent = 'Save';
    saveButton.style.cssText = `
        background-color: #4CAF50;
        border: none;
        color: white;
        padding: 5px 10px;
        text-align: center;
        text-decoration: none;
        font-size: 14px;
        cursor: pointer;
        border-radius: 4px;
    `;

    const cancelButton = document.createElement('button');
    cancelButton.textContent = 'Cancel';
    cancelButton.style.cssText = `
        background-color: #f44336;
        border: none;
        color: white;
        padding: 5px 10px;
        text-align: center;
        text-decoration: none;
        font-size: 14px;
        cursor: pointer;
        border-radius: 4px;
    `;

    settingsButtonContainer.appendChild(saveButton);
    settingsButtonContainer.appendChild(cancelButton);

    // Add components to settings panel
    settingsPanel.appendChild(timerSettingContainer);
    settingsPanel.appendChild(maxChaptersContainer);
    settingsPanel.appendChild(settingsButtonContainer);

    // Add settings panel to expanded view
    expandedView.appendChild(settingsPanel);

    // Create a button container for all controls
    const buttonContainer = document.createElement('div');
    buttonContainer.style.cssText = `
        display: flex;
        flex-direction: column;
        gap: 10px;
        margin-top: 5px;
        width: 100%;
    `;
    expandedView.appendChild(buttonContainer);

    // Create the main multi-function button (Row 1)
    const actionButton = document.createElement('button');
    actionButton.textContent = isFirstPage ? 'Start' : 'Pause';
    actionButton.style.cssText = `
        background-color: ${isFirstPage ? '#2196F3' : '#4CAF50'};
        border: none;
        color: white;
        padding: 10px 15px;
        text-align: center;
        text-decoration: none;
        font-size: 17px;
        font-weight: bold;
        cursor: pointer;
        border-radius: 6px;
        width: 100%;
    `;
    buttonContainer.appendChild(actionButton);

    // Create container for time adjustment buttons (Row 2)
    const adjustButtonsContainer = document.createElement('div');
    adjustButtonsContainer.style.cssText = `
        display: flex;
        justify-content: space-between;
        width: 100%;
        gap: 10px;
    `;

    // Create -30s button
    const minusButton = document.createElement('button');
    minusButton.textContent = '-30s';
    minusButton.style.cssText = `
        background-color: #FF9800;
        border: none;
        color: white;
        padding: 8px 0;
        text-align: center;
        text-decoration: none;
        font-size: 14px;
        cursor: pointer;
        border-radius: 4px;
        flex: 1;
    `;

    // Create +30s button
    const plusButton = document.createElement('button');
    plusButton.textContent = '+30s';
    plusButton.style.cssText = `
        background-color: #9C27B0;
        border: none;
        color: white;
        padding: 8px 0;
        text-align: center;
        text-decoration: none;
        font-size: 14px;
        cursor: pointer;
        border-radius: 4px;
        flex: 1;
    `;

    // Add time adjustment buttons to their container
    adjustButtonsContainer.appendChild(minusButton);
    adjustButtonsContainer.appendChild(plusButton);
    buttonContainer.appendChild(adjustButtonsContainer);

    // Create container for minimize and settings buttons (Row 3)
    const controlButtonsContainer = document.createElement('div');
    controlButtonsContainer.style.cssText = `
        display: flex;
        justify-content: space-between;
        width: 100%;
        gap: 10px;
    `;

    // Create minimize button with text and icon (switched position)
    const minimizeButton = document.createElement('button');
    minimizeButton.innerHTML = '− Minimize';
    minimizeButton.style.cssText = `
        background-color: #607D8B;
        border: none;
        color: white;
        padding: 8px 0;
        text-align: center;
        text-decoration: none;
        font-size: 14px;
        cursor: pointer;
        border-radius: 4px;
        flex: 1;
    `;
    minimizeButton.addEventListener('click', toggleView);

    // Create settings button with text and icon (switched position)
    const settingsButton = document.createElement('button');
    settingsButton.innerHTML = '⚙️ Settings';
    settingsButton.style.cssText = `
        background-color: #2196F3;
        border: none;
        color: white;
        padding: 8px 0;
        text-align: center;
        text-decoration: none;
        font-size: 14px;
        cursor: pointer;
        border-radius: 4px;
        flex: 1;
    `;

    // Add minimize and settings buttons to their container (switched order)
    controlButtonsContainer.appendChild(minimizeButton);
    controlButtonsContainer.appendChild(settingsButton);
    buttonContainer.appendChild(controlButtonsContainer);

    // Create Stop button (Row 4)
    const stopButton = document.createElement('button');
    stopButton.textContent = '⛔ Stop Permanently';
    stopButton.style.cssText = `
        background-color: #f44336;
        border: none;
        color: white;
        padding: 8px 0;
        text-align: center;
        text-decoration: none;
        font-size: 14px;
        font-weight: bold;
        cursor: pointer;
        border-radius: 4px;
        width: 100%;
        margin-top: 5px;
    `;
    buttonContainer.appendChild(stopButton);

    // Create container for navigation buttons (Row 5 - new addition)
    const navButtonsContainer = document.createElement('div');
    navButtonsContainer.style.cssText = `
        display: flex;
        justify-content: space-between;
        width: 100%;
        gap: 10px;
        margin-top: 10px;
    `;

    // Create Previous Chapter button
    const prevChapterButton = document.createElement('button');
    prevChapterButton.innerHTML = '⬅️ Previous';
    prevChapterButton.style.cssText = `
        background-color: #FF9800;
        border: none;
        color: white;
        padding: 8px 0;
        text-align: center;
        text-decoration: none;
        font-size: 14px;
        cursor: pointer;
        border-radius: 4px;
        flex: 1;
    `;

    // Create Next Chapter button
    const nextChapterButton = document.createElement('button');
    nextChapterButton.innerHTML = 'Next ➡️';
    nextChapterButton.style.cssText = `
        background-color: #4CAF50;
        border: none;
        color: white;
        padding: 8px 0;
        text-align: center;
        text-decoration: none;
        font-size: 14px;
        cursor: pointer;
        border-radius: 4px;
        flex: 1;
    `;

    // Add navigation buttons to their container
    navButtonsContainer.appendChild(prevChapterButton);
    navButtonsContainer.appendChild(nextChapterButton);
    buttonContainer.appendChild(navButtonsContainer);

    // Function to get audio duration and set default countdown with retry mechanism
    function setTimerFromAudioDuration() {
        let retryCount = 0;
        const MAX_RETRIES = 3;
        const RETRY_DELAY = 1000; // 1 second between retries
        const BUFFER_TIME = 10; // 10 seconds buffer time
        
        function tryGetDuration() {
            const audioElements = document.querySelectorAll('audio');
            if (audioElements.length > 0) {
                // Get the first audio element
                const audio = audioElements[0];
                
                // If duration is already available
                if (audio.duration && !isNaN(audio.duration) && audio.duration > 0) {
                    // Get current playback rate from localStorage (set by the Audio Controls script)
                    // Default to 1 if not found
                    const playbackRate = parseFloat(localStorage.getItem('audio_playback_rate')) || 1.0;
                    
                    // Set timer to (audio duration ÷ playback speed) + buffer time (converted to minutes)
                    const adjustedDuration = (audio.duration / playbackRate) + BUFFER_TIME;
                    const durationInMinutes = adjustedDuration / 60;
                    COUNTDOWN_MINUTES = Math.ceil(durationInMinutes * 10) / 10; // Round to 1 decimal
                    console.log(`[Auto-Next] Set timer to ${COUNTDOWN_MINUTES} minutes based on audio duration (${audio.duration.toFixed(1)}s) and playback rate (${playbackRate}x) (attempt ${retryCount + 1})`);
                    
                    // Update countdown values
                    countdownMs = COUNTDOWN_MINUTES * 60 * 1000;
                    remainingTime = countdownMs;
                    updateCountdown();

                    // Update playback rate display
                    playbackRateText.textContent = `Playback Speed: ${playbackRate.toFixed(1)}x`;
                    
                    return true; // Successfully got duration
                } else {
                    retryCount++;
                    if (retryCount < MAX_RETRIES) {
                        console.log(`[Auto-Next] Couldn't get audio duration, retry ${retryCount}/${MAX_RETRIES}...`);
                        // Try again after delay
                        setTimeout(tryGetDuration, RETRY_DELAY);
                        return false; // Still trying
                    } else {
                        // Max retries reached, use default
                        console.log(`[Auto-Next] Max retries (${MAX_RETRIES}) reached. Using default timer of 7 minutes`);
                        COUNTDOWN_MINUTES = 7;
                        countdownMs = COUNTDOWN_MINUTES * 60 * 1000;
                        remainingTime = countdownMs;
                        updateCountdown();
                        return true; // Finished with fallback value
                    }
                }
            } else {
                // No audio found after retry
                if (retryCount < MAX_RETRIES) {
                    retryCount++;
                    console.log(`[Auto-Next] No audio found, retry ${retryCount}/${MAX_RETRIES}...`);
                    setTimeout(tryGetDuration, RETRY_DELAY);
                    return false;
                } else {
                    // Max retries reached, use default
                    console.log(`[Auto-Next] No audio found after ${MAX_RETRIES} retries. Using default timer of 7 minutes`);
                    COUNTDOWN_MINUTES = 7;
                    countdownMs = COUNTDOWN_MINUTES * 60 * 1000;
                    remainingTime = countdownMs;
                    updateCountdown();
                    return true;
                }
            }
        }

        // Listen for metadata loaded event on any audio that appears
        document.addEventListener('DOMNodeInserted', function(e) {
            if (e.target.tagName === 'AUDIO' || (e.target.querySelector && e.target.querySelector('audio'))) {
                const audio = e.target.tagName === 'AUDIO' ? e.target : e.target.querySelector('audio');
                if (audio) {
                    audio.addEventListener('loadedmetadata', function() {
                        if (!isRunning && !isPaused && retryCount < MAX_RETRIES) {
                            // If timer hasn't started yet and we're still in retry phase
                            tryGetDuration();
                        }
                    });
                }
            }
        });
        
        // Start the first attempt
        tryGetDuration();
    }

    // Function to monitor playback rate changes
    function setupPlaybackRateMonitor() {
        // Check localStorage every 2 seconds for playback rate changes
        const playbackRateCheckInterval = setInterval(() => {
            const currentPlaybackRate = parseFloat(localStorage.getItem('audio_playback_rate')) || 1.0;
            const storedPlaybackRate = parseFloat(localStorage.getItem('auto_next_last_playback_rate')) || currentPlaybackRate;
            
            // Update playback rate display regardless of whether it changed
            playbackRateText.textContent = `Playback Speed: ${currentPlaybackRate.toFixed(1)}x`;
            
            // If playback rate has changed significantly (more than 0.01 difference)
            if (Math.abs(currentPlaybackRate - storedPlaybackRate) > 0.01) {
                console.log(`[Auto-Next] Playback rate changed from ${storedPlaybackRate}x to ${currentPlaybackRate}x, adjusting timer...`);
                
                // Store new rate
                localStorage.setItem('auto_next_last_playback_rate', currentPlaybackRate.toString());
                
                // Only adjust timer if it's running
                if (isRunning && !isPaused) {
                    // Calculate ratio of change
                    const ratioChange = storedPlaybackRate / currentPlaybackRate;
                    
                    // Adjust remaining time proportionally
                    // If playback is faster, time should decrease; if slower, time should increase
                    const currentRemainingTime = endTime - Date.now();
                    const adjustedRemainingTime = currentRemainingTime * ratioChange;
                    
                    // Update end time based on adjusted remaining time
                    endTime = Date.now() + adjustedRemainingTime;
                    remainingTime = adjustedRemainingTime;
                    
                    // Update display immediately
                    updateCountdown();
                    console.log(`[Auto-Next] Timer adjusted to ${Math.floor(remainingTime / 60000)}:${Math.floor((remainingTime % 60000) / 1000).toString().padStart(2, '0')}`);
                }
            }
        }, 2000); // Check every 2 seconds
        
        // Store initial playback rate
        const initialPlaybackRate = parseFloat(localStorage.getItem('audio_playback_rate')) || 1.0;
        localStorage.setItem('auto_next_last_playback_rate', initialPlaybackRate.toString());
    }

    // Function to set up audio state listeners
    function setupAudioStateListeners() {
        const audioElements = document.querySelectorAll('audio');
        if (audioElements.length > 0) {
            const audio = audioElements[0];
            
            // Listen for play events
            audio.addEventListener('play', function() {
                // Start timer if not running yet
                if (!isRunning && !isPaused) {
                    startTimer();
                    console.log('[Auto-Next] Starting timer because audio is playing');
                }
                // Resume timer if it was paused
                else if (isPaused && !isRunning) {
                    resumeTimer();
                    console.log('[Auto-Next] Resuming timer because audio is playing');
                }
            });
            
            // Listen for pause events
            audio.addEventListener('pause', function() {
                // Pause timer if it was running
                if (isRunning && !isPaused) {
                    pauseTimer();
                    console.log('[Auto-Next] Pausing timer because audio is paused');
                }
            });
            
            // Set initial state based on audio
            if (audio.paused && isRunning) {
                pauseTimer();
            }
        }
    }

    // Function to toggle settings panel
    function toggleSettingsPanel() {
        settingsPanelOpen = !settingsPanelOpen;
        settingsPanel.style.display = settingsPanelOpen ? 'block' : 'none';

        // Reset input values to current settings
        timerInput.value = COUNTDOWN_MINUTES;
        maxChaptersInput.value = MAX_CHAPTERS;
    }

    // Function to save settings
    function saveSettings() {
        // Get and validate timer minutes (ensure it's a valid number)
        const newTimerMinutes = parseFloat(timerInput.value);

        if (isNaN(newTimerMinutes) || newTimerMinutes < 0.5 || newTimerMinutes > 60) {
            alert('Please enter a valid time between 0.5 and 60 minutes.');
            return;
        }

        // Get and validate max chapters
        const newMaxChapters = parseInt(maxChaptersInput.value);

        if (isNaN(newMaxChapters) || newMaxChapters < 1 || newMaxChapters > 20) {
            alert('Please enter a valid number of chapters between 1 and 20.');
            return;
        }

        // Update settings
        COUNTDOWN_MINUTES = newTimerMinutes;
        countdownMs = COUNTDOWN_MINUTES * 60 * 1000;

        // Update max chapters
        MAX_CHAPTERS = newMaxChapters;
        chapterCounterText.textContent = `Chapters: ${chaptersNavigated}/${MAX_CHAPTERS}`;

        // If timer is not running, update the remaining time
        if (!isRunning) {
            remainingTime = countdownMs;
            updateCountdown();
        }

        // Save to localStorage
        userSettings.timerMinutes = COUNTDOWN_MINUTES;
        userSettings.maxChapters = MAX_CHAPTERS;
        localStorage.setItem('autoNextSettings', JSON.stringify(userSettings));

        // Close settings panel
        toggleSettingsPanel();
    }

    // Settings button click handler
    settingsButton.addEventListener('click', toggleSettingsPanel);

    // Save button click handler
    saveButton.addEventListener('click', saveSettings);

    // Cancel button click handler
    cancelButton.addEventListener('click', toggleSettingsPanel);

    // Function to start the timer
    function startTimer() {
        isRunning = true;
        isPaused = false;
        startTime = Date.now();
        endTime = startTime + remainingTime;

        // Store in session storage that we've started
        sessionStorage.setItem('auto_next_started', 'true');

        // Update button
        actionButton.textContent = 'Pause';
        actionButton.style.backgroundColor = '#4CAF50';

        // Start the countdown interval
        if (!countdownInterval) {
            countdownInterval = setInterval(updateCountdown, 1000);
        }
    }

    // Function to pause the timer
    function pauseTimer() {
        isPaused = true;
        isRunning = false;

        // Store the remaining time when paused
        remainingTime = Math.max(0, endTime - Date.now());

        // Update button
        actionButton.textContent = 'Resume';
        actionButton.style.backgroundColor = '#f44336';
    }

    // Function to resume the timer
    function resumeTimer() {
        isPaused = false;
        isRunning = true;

        // Recalculate the end time based on the remaining time
        endTime = Date.now() + remainingTime;

        // Update button
        actionButton.textContent = 'Pause';
        actionButton.style.backgroundColor = '#4CAF50';
    }

    // Update the countdown display
    function updateCountdown() {
        if (isRunning && !isPaused) {
            remainingTime = Math.max(0, endTime - Date.now());
        }

        const minutesLeft = Math.floor(remainingTime / 60000);
        const secondsLeft = Math.floor((remainingTime % 60000) / 1000);
        const formattedTime = `${minutesLeft}:${secondsLeft.toString().padStart(2, '0')}`;

        // Update the timer text in expanded view
        timerText.textContent = `Next chapter in: ${formattedTime}`;

        // Update the timer text in bubble view
        if (bubbleTimer) {
            bubbleTimer.textContent = formattedTime;
        }

        if (remainingTime <= 0 && isRunning && !isPaused) {
            clearInterval(countdownInterval);
            countdownInterval = null;
            clickNextChapter();
        }
    }

    // Button click handler - cycles through Start, Pause, Resume
    actionButton.addEventListener('click', function() {
        if (!isRunning && !isPaused) {
            // Start the timer
            startTimer();
        } else if (isRunning && !isPaused) {
            // Pause the timer
            pauseTimer();
        } else if (!isRunning && isPaused) {
            // Resume the timer
            resumeTimer();
        }
    });

    // Add 30 seconds to the timer
    plusButton.addEventListener('click', function() {
        // Only allow adjustment if timer is running or paused
        if (isRunning || isPaused) {
            // If paused, just adjust the remaining time
            if (isPaused) {
                remainingTime += 30000; // 30 seconds in milliseconds
            } else {
                // If running, adjust the end time
                endTime += 30000;
            }

            // Update the display immediately
            updateCountdown();
        }
    });

    // Subtract 30 seconds from the timer
    minusButton.addEventListener('click', function() {
        // Only allow adjustment if timer is running or paused
        if (isRunning || isPaused) {
            if (isPaused) {
                // Don't let it go below zero
                remainingTime = Math.max(0, remainingTime - 30000);
            } else {
                // Adjust end time but don't let it go below current time
                endTime = Math.max(Date.now(), endTime - 30000);
                // Recalculate remaining time
                remainingTime = Math.max(0, endTime - Date.now());
            }

            // Update the display immediately
            updateCountdown();

            // If we reduced to zero, trigger next chapter
            if (remainingTime <= 0 && isRunning) {
                clearInterval(countdownInterval);
                countdownInterval = null;
                clickNextChapter();
            }
        }
    });

    // Function to check if we should stop due to chapter limit
    function checkChapterLimit() {
        if (chaptersNavigated >= MAX_CHAPTERS) {
            // Update the UI to show we've reached the limit
            timerText.textContent = `Reached limit of ${MAX_CHAPTERS} chapters`;
            chapterCounterText.textContent = `Chapters: ${chaptersNavigated}/${MAX_CHAPTERS} - Limit reached!`;
            chapterCounterText.style.color = '#ff6666';

            // Reset the chapter counter after reaching the limit
            chaptersNavigated = 0;
            sessionStorage.setItem('auto_next_chapters_count', '0');

            // Stop the timer
            stopPermanently();

            return true;
        }
        return false;
    }

    // Function to click the next chapter button
    function clickNextChapter() {
        // Increment chapter counter before checking
        chaptersNavigated++;
        sessionStorage.setItem('auto_next_chapters_count', chaptersNavigated.toString());

        // Update chapter counter display
        chapterCounterText.textContent = `Chapters: ${chaptersNavigated}/${MAX_CHAPTERS}`;

        // Check if we've reached the limit
        if (checkChapterLimit()) {
            // We've reached the limit, don't proceed
            return;
        }

        // Update the timer text
        timerText.textContent = 'Moving to next chapter...';

        // Try to find the next chapter button
        const nextButton = document.querySelector(NEXT_BUTTON_SELECTOR);

        if (nextButton) {
            // Highlight the button being clicked
            const originalBackground = nextButton.style.backgroundColor;
            const originalTransition = nextButton.style.transition;

            nextButton.style.transition = 'background-color 0.3s ease';
            nextButton.style.backgroundColor = 'yellow';

            // Click after a short delay to show the highlight
            setTimeout(() => {
                nextButton.click();

                // If for some reason we're still on the page after clicking
                setTimeout(() => {
                    nextButton.style.backgroundColor = originalBackground;
                    nextButton.style.transition = originalTransition;
                    timerText.textContent = 'Click failed or redirecting...';
                }, 1000);
            }, 500);
        } else {
            timerText.textContent = 'Next button not found! Adjust the selector in the script.';

            // Error message will remain visible
        }
    }

    // Function to permanently stop the script
    function stopPermanently() {
        // Clear any running intervals
        if (countdownInterval) {
            clearInterval(countdownInterval);
            countdownInterval = null;
        }

        // Reset states
        isRunning = false;
        isPaused = false;

        // Reset chapter counter
        chaptersNavigated = 0;
        sessionStorage.setItem('auto_next_chapters_count', '0');

        // Update UI
        timerText.textContent = 'Timer stopped permanently';
        chapterCounterText.textContent = `Chapters: ${chaptersNavigated}/${MAX_CHAPTERS}`;
        chapterCounterText.style.color = '#ffcc00'; // Reset color

        if (bubbleTimer) {
            bubbleTimer.textContent = 'Stopped';
        }

        // Disable timer control buttons but NOT navigation buttons
        actionButton.disabled = true;
        minusButton.disabled = true;
        plusButton.disabled = true;
        stopButton.disabled = true;
        // Keep navigation buttons enabled

        // Change button appearances for timer controls only
        actionButton.style.backgroundColor = '#999';
        minusButton.style.backgroundColor = '#999';
        plusButton.style.backgroundColor = '#999';
        stopButton.style.backgroundColor = '#999';
        stopButton.textContent = 'Stopped';
    }

    // Function to navigate to previous chapter
    function goToPrevChapter() {
        const prevButton = document.querySelector(PREV_BUTTON_SELECTOR);
        if (prevButton) {
            // Highlight the button being clicked
            const originalBackground = prevButton.style.backgroundColor;
            const originalTransition = prevButton.style.transition;

            prevButton.style.transition = 'background-color 0.3s ease';
            prevButton.style.backgroundColor = 'yellow';

            // Click after a short delay to show the highlight
            setTimeout(() => {
                prevButton.click();
            }, 300);
        } else {
            timerText.textContent = 'Previous chapter button not found!';
        }
    }

    // Function to navigate to next chapter immediately
    function goToNextChapter() {
        const nextButton = document.querySelector(NEXT_BUTTON_SELECTOR);
        if (nextButton) {
            // Increment chapter counter (just like the auto-next function)
            chaptersNavigated++;
            sessionStorage.setItem('auto_next_chapters_count', chaptersNavigated.toString());

            // Update chapter counter display
            chapterCounterText.textContent = `Chapters: ${chaptersNavigated}/${MAX_CHAPTERS}`;

            // Check if we've reached the limit before navigating
            if (checkChapterLimit()) {
                // We've reached the limit, don't proceed
                return;
            }

            // Highlight the button being clicked
            const originalBackground = nextButton.style.backgroundColor;
            const originalTransition = nextButton.style.transition;

            nextButton.style.transition = 'background-color 0.3s ease';
            nextButton.style.backgroundColor = 'yellow';

            // Click after a short delay to show the highlight
            setTimeout(() => {
                nextButton.click();
            }, 300);
        } else {
            timerText.textContent = 'Next chapter button not found!';
        }
    }

    // Add stop button click handler
    stopButton.addEventListener('click', stopPermanently);

    // Add click handlers for navigation buttons
    prevChapterButton.addEventListener('click', goToPrevChapter);
    nextChapterButton.addEventListener('click', goToNextChapter);
    
    // Initialize - try to get audio duration first
    function initialize() {
        console.log('[Auto-Next] Initializing script v1.2');
        
        // Set timer based on audio duration
        setTimerFromAudioDuration();
        
        // Set up audio state listeners
        setupAudioStateListeners();
        
        // Set up playback rate change monitor
        setupPlaybackRateMonitor();
        
        // Check if we've already reached the chapter limit
        if (chaptersNavigated >= MAX_CHAPTERS) {
            checkChapterLimit();
        } else {
            // Set initial view state based on preference
            updateViewState();

            // Don't automatically start timer - wait for audio play event instead
            
            // Update countdown display initially
            updateCountdown();
        }
    }
    
    // If DOM is already loaded, initialize immediately
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }
    
    // Try again after full page load (helps with audio elements loaded dynamically)
    window.addEventListener('load', function() {
        // Check if audio elements are now available
        const audioElements = document.querySelectorAll('audio');
        if (audioElements.length > 0 && !isRunning && !isPaused) {
            // Re-set timer if needed
            setTimerFromAudioDuration();
            setupAudioStateListeners();
        }
    });
})();