Döndüğünüzde Videolara Kaldığınız Yerden Devam Edin
// ==UserScript==
// @name YouTube Videoyu Otomatik Devam Ettir
// @namespace http://tampermonkey.net/
// @version 1
// @description Döndüğünüzde Videolara Kaldığınız Yerden Devam Edin
// @author Atilla
// @match https://www.youtube.com/*
// @icon https://www.gstatic.com/youtube/img/web/maskable/logo_512x512.png
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
(() => {
"use strict";
// ==========================================================================
// CONFIGURATION & CONSTANTS
// ==========================================================================
const CONFIG = {
STORAGE_KEY: "yt_auto_resume_data",
EXPIRATION_DAYS_KEY: "yt_auto_resume_exp_days",
DEFAULT_EXPIRATION_DAYS: 7,
END_THRESHOLD_SECONDS: 5, // Videonun bitimine 5 sn kala kaydı siler
SAVE_INTERVAL_MS: 1000, // Throttling: En fazla saniyede 1 kez yazar
};
// ==========================================================================
// STORAGE MANAGER (Tampermonkey API)
// ==========================================================================
const StorageManager = {
get ExpirationDays() {
return GM_getValue(
CONFIG.EXPIRATION_DAYS_KEY,
CONFIG.DEFAULT_EXPIRATION_DAYS,
);
},
set ExpirationDays(days) {
GM_setValue(CONFIG.EXPIRATION_DAYS_KEY, days);
},
getAllData() {
return GM_getValue(CONFIG.STORAGE_KEY, {});
},
saveTime(videoId, time) {
const data = this.getAllData();
data[videoId] = {
time: Math.floor(time),
updatedAt: Date.now(),
};
GM_setValue(CONFIG.STORAGE_KEY, data);
},
getTime(videoId) {
const data = this.getAllData();
return data[videoId] ? data[videoId].time : null;
},
remove(videoId) {
const data = this.getAllData();
if (data[videoId]) {
delete data[videoId];
GM_setValue(CONFIG.STORAGE_KEY, data);
}
},
cleanExpired() {
const data = this.getAllData();
const now = Date.now();
const maxAgeMs = this.ExpirationDays * 24 * 60 * 60 * 1000;
let updated = false;
Object.keys(data).forEach((id) => {
if (now - data[id].updatedAt > maxAgeMs) {
delete data[id];
updated = true;
}
});
if (updated) {
GM_setValue(CONFIG.STORAGE_KEY, data);
}
},
};
// ==========================================================================
// CONTROLLER
// ==========================================================================
class YouTubeResumeController {
constructor() {
this.currentVideoId = null;
this.videoElement = null;
this.lastSaveTime = 0;
this.isResumed = false;
this.initMenu();
this.initObserver();
StorageManager.cleanExpired();
}
// Tampermonkey menüsüne buton ekler
initMenu() {
GM_registerMenuCommand(
`Kayıt Saklama Süresini Ayarla (Şu an: ${StorageManager.ExpirationDays} Gün)`,
() => {
const current = StorageManager.ExpirationDays;
const input = prompt(
"Eski video kayıtları kaç gün sonra silinsin?",
current,
);
const days = parseInt(input, 10);
if (!isNaN(days) && days > 0) {
StorageManager.ExpirationDays = days;
alert(`Saklama süresi ${days} gün olarak güncellendi.`);
location.reload();
} else if (input !== null) {
alert("Lütfen geçerli bir pozitif sayı girin.");
}
},
);
}
// URL'den Video ID'sini çeker
getVideoId() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get("v");
}
// YouTube SPA sayfa değişimlerini ve DOM elementlerini izler
initObserver() {
const observer = new MutationObserver(() => {
if (!window.location.pathname.startsWith("/watch")) {
this.resetState();
return;
}
const videoId = this.getVideoId();
const video = document.querySelector("video.html5-main-video");
if (
videoId &&
video &&
(this.currentVideoId !== videoId || this.videoElement !== video)
) {
this.setupVideo(videoId, video);
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
setupVideo(videoId, video) {
this.detachListeners();
this.currentVideoId = videoId;
this.videoElement = video;
this.isResumed = false;
this.onTimeUpdate = this.handleTimeUpdate.bind(this);
this.onLoadedMetadata = this.handleLoadedMetadata.bind(this);
this.videoElement.addEventListener("timeupdate", this.onTimeUpdate);
if (this.videoElement.readyState >= 1) {
this.handleLoadedMetadata();
} else {
this.videoElement.addEventListener(
"loadedmetadata",
this.onLoadedMetadata,
{ once: true },
);
}
}
handleLoadedMetadata() {
if (this.isResumed || !this.videoElement) return;
const savedTime = StorageManager.getTime(this.currentVideoId);
const duration = this.videoElement.duration;
if (savedTime && duration) {
// Bitime 5 saniyeden fazla varsa kaldığı yere sar
if (
duration - savedTime > CONFIG.END_THRESHOLD_SECONDS &&
savedTime > 2
) {
this.videoElement.currentTime = savedTime;
} else {
// Bitiş sınırına yakınsa kaydı temizle
StorageManager.remove(this.currentVideoId);
}
}
this.isResumed = true;
}
handleTimeUpdate() {
if (!this.videoElement || !this.currentVideoId) return;
const currentTime = this.videoElement.currentTime;
const duration = this.videoElement.duration;
if (!duration || isNaN(duration)) return;
// Videonun bitimine 5 saniyeden az kalmışsa kaydı sil
if (duration - currentTime <= CONFIG.END_THRESHOLD_SECONDS) {
StorageManager.remove(this.currentVideoId);
return;
}
// Throttling: Performans için saniyede 1 kez Tampermonkey depolamasına yaz
const now = Date.now();
if (now - this.lastSaveTime >= CONFIG.SAVE_INTERVAL_MS) {
StorageManager.saveTime(this.currentVideoId, currentTime);
this.lastSaveTime = now;
}
}
detachListeners() {
if (this.videoElement) {
this.videoElement.removeEventListener("timeupdate", this.onTimeUpdate);
this.videoElement.removeEventListener(
"loadedmetadata",
this.onLoadedMetadata,
);
}
}
resetState() {
this.detachListeners();
this.currentVideoId = null;
this.videoElement = null;
this.isResumed = false;
}
}
// Betiği Başlat
new YouTubeResumeController();
})();