记住Jable网站视频播放进度,刷新后自动恢复
// ==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
});
})();