LOCUS Jable

记住Jable网站视频播放进度,自动静默恢复,无任何提示

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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