LOCUS 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         LOCUS Jable
// @namespace    http://tampermonkey.net/
// @version      1.5
// @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 initVideoProgress() {
        const videoId = getVideoId();
        if (!videoId) return;

        cleanExpiredData();

        waitForVideo((video) => {
            let progressRestored = false;
            let saveTimeout = null;

            // 检查是否有保存的进度
            const savedProgress = getProgress(videoId);

            // 视频元数据加载完成后处理
            const handleLoadedMetadata = () => {
                // 如果有记录,且记录大于3秒(避免开头误触),且未恢复过
                if (savedProgress && !progressRestored && savedProgress.currentTime > 3) {
                    // 确保目标时间不超过当前视频总时长
                    const targetTime = Math.min(savedProgress.currentTime, video.duration - 1);
                    video.currentTime = targetTime;
                    progressRestored = true;
                    // 这里没有任何UI提示,直接静默跳转
                }
            };

            // 监听元数据加载
            if (video.readyState >= 1) {
                handleLoadedMetadata();
            } else {
                video.addEventListener('loadedmetadata', handleLoadedMetadata);
                video.addEventListener('canplay', 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);
                }
            });
        });
    }

    // 页面加载完成后初始化
    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
    });
})();