Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 99 additions & 18 deletions RememberPlaybackSpeed.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ class RememberPlaybackSpeed {
this.STORAGE_KEY = 'emby_playback_speed';
this.isRestoring = false;
this.isTheater = isTheater;
this.normalSpeedDecisionTimer = null;
this.init();
}

init() {
console.log('[倍速记忆] 初始化');
this.bindSpeedControlMonitor();
this.waitForPlayer();
}

Expand All @@ -42,7 +44,7 @@ class RememberPlaybackSpeed {
if (this.isRestoring) return;

const savedSpeed = this.getSavedSpeed();
if (!savedSpeed || savedSpeed === 1) return;
if (!this.isValidSpeed(savedSpeed)) return;

this.isRestoring = true;
console.log(`[倍速记忆] 恢复倍速: ${savedSpeed}x`);
Expand All @@ -62,18 +64,6 @@ class RememberPlaybackSpeed {
if (success) {
console.log(`[倍速记忆] ✓ 恢复成功: ${savedSpeed}x`);
this.isRestoring = false;

// 保护期:防止被重置为 1x
let count = 0;
const protect = setInterval(() => {
document.querySelectorAll('video').forEach(video => {
if (video.playbackRate === 1) {
video.playbackRate = savedSpeed;
console.log(`[倍速记忆] 防止重置为 1x`);
}
});
if (++count >= 10) clearInterval(protect);
}, 500);
} else {
setTimeout(applySpeed, 300);
}
Expand All @@ -93,14 +83,24 @@ class RememberPlaybackSpeed {
// 监听倍速变化(保存用户修改)
video.addEventListener('ratechange', () => {
const speed = video.playbackRate;
const isPlaying = !video.paused || video.currentTime > 0;

if (isPlaying && speed !== 1 && !this.isRestoring) {
this.saveSpeed(speed);
console.log(`[倍速记忆] 保存倍速: ${speed}x`);
if (!this.isValidSpeed(speed) || this.isRestoring) return;

const savedSpeed = this.getSavedSpeed();
if (
this.isNormalSpeed(speed) &&
this.isValidSpeed(savedSpeed) &&
!this.isNormalSpeed(savedSpeed)
) {
this.handleNormalSpeedChange(video, restoreSpeed);
return;
}

this.cancelNormalSpeedDecision();
this.saveSpeed(speed);
console.log(`[倍速记忆] 保存倍速: ${speed}x`);
});

// 关键时机恢复倍速
['loadedmetadata', 'canplay', 'play'].forEach(event => {
video.addEventListener(event, () => {
Expand Down Expand Up @@ -134,6 +134,79 @@ class RememberPlaybackSpeed {
console.log('[倍速记忆] 监控已启动');
}

bindSpeedControlMonitor() {
document.addEventListener('click', event => {
const selectedSpeed = this.parseSpeedFromEvent(event);
if (this.isValidSpeed(selectedSpeed)) {
this.cancelNormalSpeedDecision();
this.saveSpeed(selectedSpeed);
console.log(`[倍速记忆] 检测到倍速菜单选择: ${selectedSpeed}x`);
}
}, true);
}

handleNormalSpeedChange(video, restoreSpeed) {
this.cancelNormalSpeedDecision();

this.normalSpeedDecisionTimer = setTimeout(() => {
this.normalSpeedDecisionTimer = null;

const savedSpeed = this.getSavedSpeed();
if (!this.isValidSpeed(savedSpeed) || this.isNormalSpeed(savedSpeed)) return;
if (!this.isNormalSpeed(video.playbackRate)) return;

console.log(`[倍速记忆] 检测到播放器自动重置为 1x,恢复 ${savedSpeed}x`);
restoreSpeed();
}, 400);
}

cancelNormalSpeedDecision() {
if (this.normalSpeedDecisionTimer) {
clearTimeout(this.normalSpeedDecisionTimer);
this.normalSpeedDecisionTimer = null;
}
}

parseSpeedFromEvent(event) {
const path = typeof event.composedPath === 'function' ? event.composedPath() : [event.target];

for (const element of path) {
if (!element || element === document || element === window) continue;

const candidates = [
element.getAttribute && element.getAttribute('aria-label'),
element.getAttribute && element.getAttribute('title'),
element.getAttribute && element.getAttribute('data-value'),
element.getAttribute && element.getAttribute('value'),
element.textContent
];

for (const candidate of candidates) {
const speed = this.parseSpeedLabel(candidate);
if (this.isValidSpeed(speed)) return speed;
}
}

return null;
}

parseSpeedLabel(text) {
if (!text) return null;

const label = text.trim().replace(/\s+/g, ' ');
if (!label || label.length > 24) return null;

if (/^(normal|default|标准|正常|原速)$/i.test(label)) return 1;

const match = label.match(/^(\d+(?:\.\d+)?)\s*(?:x|倍)$/i) ||
label.match(/^x\s*(\d+(?:\.\d+)?)$/i);

if (!match) return null;

const speed = parseFloat(match[1]);
return speed > 0 && speed <= 16 ? speed : null;
}

// localStorage 操作
saveSpeed(speed) {
if (this.isTheater) {
Expand All @@ -149,6 +222,14 @@ class RememberPlaybackSpeed {
const speed = localStorage.getItem(this.STORAGE_KEY);
return speed ? parseFloat(speed) : null;
}

isValidSpeed(speed) {
return Number.isFinite(speed) && speed > 0;
}

isNormalSpeed(speed) {
return Math.abs(speed - 1) < 0.01;
}
}

// 页面加载后自动启动
Expand Down