Jable视频播放进度记录

记住Jable网站视频播放进度,刷新后自动恢复

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Jable视频播放进度记录
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  记住Jable网站视频播放进度,刷新后自动恢复
// @author       You
// @match        https://jable.tv/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // 存储键前缀
    const STORAGE_PREFIX = 'jable_video_progress_';
    
    // 获取当前页面的唯一标识
    function getVideoId() {
        const url = window.location.href;
        const match = url.match(/\/videos\/([^\/]+)/);
        return match ? match[1] : null;
    }
    
    // 保存播放进度
    function saveProgress(videoId, currentTime, duration) {
        const progressData = {
            currentTime: currentTime,
            duration: duration,
            timestamp: Date.now(),
            url: window.location.href
        };
        localStorage.setItem(STORAGE_PREFIX + videoId, JSON.stringify(progressData));
    }
    
    // 获取播放进度
    function getProgress(videoId) {
        const data = localStorage.getItem(STORAGE_PREFIX + videoId);
        if (data) {
            try {
                const progressData = JSON.parse(data);
                // 检查数据是否过期(7天)
                if (Date.now() - progressData.timestamp < 7 * 24 * 60 * 60 * 1000) {
                    return progressData;
                }
            } catch (e) {
                console.error('解析进度数据失败:', e);
            }
        }
        return null;
    }
    
    // 清理过期数据
    function cleanExpiredData() {
        const keys = Object.keys(localStorage);
        const expireTime = 7 * 24 * 60 * 60 * 1000; // 7天
        
        keys.forEach(key => {
            if (key.startsWith(STORAGE_PREFIX)) {
                try {
                    const data = JSON.parse(localStorage.getItem(key));
                    if (Date.now() - data.timestamp > expireTime) {
                        localStorage.removeItem(key);
                    }
                } catch (e) {
                    // 删除无效数据
                    localStorage.removeItem(key);
                }
            }
        });
    }
    
    // 等待视频元素加载
    function waitForVideo(callback) {
        const checkVideo = () => {
            const video = document.querySelector('video');
            if (video) {
                callback(video);
            } else {
                setTimeout(checkVideo, 500);
            }
        };
        
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', checkVideo);
        } else {
            checkVideo();
        }
    }
    
    // 创建进度提示
    function showProgressTip(currentTime, duration) {
        const tip = document.createElement('div');
        tip.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px 15px;
            border-radius: 5px;
            font-size: 14px;
            z-index: 10000;
            max-width: 300px;
            word-wrap: break-word;
        `;
        
        const formatTime = (seconds) => {
            const minutes = Math.floor(seconds / 60);
            const secs = Math.floor(seconds % 60);
            return `${minutes}:${secs.toString().padStart(2, '0')}`;
        };
        
        tip.innerHTML = `
            <div>检测到播放记录</div>
            <div>上次播放到: ${formatTime(currentTime)} / ${formatTime(duration)}</div>
            <div style="margin-top: 5px; font-size: 12px;">
                <button id="resume-btn" style="margin-right: 10px; padding: 2px 8px; background: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer;">恢复播放</button>
                <button id="start-over-btn" style="padding: 2px 8px; background: #6c757d; color: white; border: none; border-radius: 3px; cursor: pointer;">从头开始</button>
            </div>
        `;
        
        document.body.appendChild(tip);
        
        // 5秒后自动隐藏
        setTimeout(() => {
            if (tip.parentNode) {
                tip.remove();
            }
        }, 8000);
        
        return tip;
    }
    
    // 主要功能
    function initVideoProgress() {
        const videoId = getVideoId();
        if (!videoId) return;
        
        // 清理过期数据
        cleanExpiredData();
        
        waitForVideo((video) => {
            let progressRestored = false;
            let saveTimeout = null;
            
            // 检查是否有保存的进度
            const savedProgress = getProgress(videoId);
            
            // 视频元数据加载完成后处理
            const handleLoadedMetadata = () => {
                if (savedProgress && !progressRestored && savedProgress.currentTime > 10) {
                    const tip = showProgressTip(savedProgress.currentTime, savedProgress.duration);
                    
                    // 恢复播放按钮
                    tip.querySelector('#resume-btn').addEventListener('click', () => {
                        video.currentTime = savedProgress.currentTime;
                        progressRestored = true;
                        tip.remove();
                    });
                    
                    // 从头开始按钮
                    tip.querySelector('#start-over-btn').addEventListener('click', () => {
                        video.currentTime = 0;
                        progressRestored = true;
                        tip.remove();
                    });
                }
            };
            
            // 监听元数据加载
            if (video.readyState >= 1) {
                handleLoadedMetadata();
            } else {
                video.addEventListener('loadedmetadata', handleLoadedMetadata);
            }
            
            // 保存播放进度(防抖处理)
            const saveProgressDebounced = () => {
                if (saveTimeout) {
                    clearTimeout(saveTimeout);
                }
                saveTimeout = setTimeout(() => {
                    if (video.currentTime > 0 && video.duration > 0) {
                        saveProgress(videoId, video.currentTime, video.duration);
                    }
                }, 1000);
            };
            
            // 监听播放进度变化
            video.addEventListener('timeupdate', saveProgressDebounced);
            
            // 页面卸载前保存
            window.addEventListener('beforeunload', () => {
                if (video.currentTime > 0 && video.duration > 0) {
                    saveProgress(videoId, video.currentTime, video.duration);
                }
            });
            
            // 视频暂停时保存
            video.addEventListener('pause', () => {
                if (video.currentTime > 0 && video.duration > 0) {
                    saveProgress(videoId, video.currentTime, video.duration);
                }
            });
            
            console.log('Jable视频进度记录脚本已启动');
        });
    }
    
    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initVideoProgress);
    } else {
        initVideoProgress();
    }
    
    // 处理单页应用的路由变化
    let currentUrl = window.location.href;
    const observer = new MutationObserver(() => {
        if (currentUrl !== window.location.href) {
            currentUrl = window.location.href;
            setTimeout(initVideoProgress, 1000); // 延迟执行,等待页面更新
        }
    });
    
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

})();