|
@@ -2,6 +2,7 @@ import { clamp, interceptWindowEvent, pauseFor } from "@sv443-network/userutils"
|
|
|
import { domLoaded, error, getDomain, getVideoTime, getWatchId, info, log, getVideoSelector, waitVideoElementReady } from "../utils";
|
|
|
import { getFeatures } from "../config";
|
|
|
import { addSelectorListener } from "../observers";
|
|
|
+import { initialParams } from "../constants";
|
|
|
import { LogLevel } from "../types";
|
|
|
|
|
|
//#region beforeunload popup
|
|
@@ -29,13 +30,13 @@ export async function initBeforeUnloadHook() {
|
|
|
|
|
|
/** Closes toasts after a set amount of time */
|
|
|
export async function initAutoCloseToasts() {
|
|
|
- try {
|
|
|
- const animTimeout = 300;
|
|
|
+ const animTimeout = 300;
|
|
|
|
|
|
- addSelectorListener("popupContainer", "tp-yt-paper-toast#toast", {
|
|
|
- all: true,
|
|
|
- continuous: true,
|
|
|
- listener: async (toastElems) => {
|
|
|
+ addSelectorListener("popupContainer", "tp-yt-paper-toast#toast", {
|
|
|
+ all: true,
|
|
|
+ continuous: true,
|
|
|
+ listener: async (toastElems) => {
|
|
|
+ try {
|
|
|
for(const toastElem of toastElems) {
|
|
|
if(!toastElem.hasAttribute("allow-click-through"))
|
|
|
continue;
|
|
@@ -53,79 +54,84 @@ export async function initAutoCloseToasts() {
|
|
|
// wait for the transition to finish
|
|
|
await pauseFor(animTimeout);
|
|
|
|
|
|
+ toastElem.classList.remove("bytm-closing");
|
|
|
toastElem.style.display = "none";
|
|
|
}
|
|
|
- },
|
|
|
- });
|
|
|
+ }
|
|
|
+ catch(err) {
|
|
|
+ error("Error in automatic toast closing:", err);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
|
|
|
- log("Initialized automatic toast closing");
|
|
|
- }
|
|
|
- catch(err) {
|
|
|
- error("Error in automatic toast closing:", err);
|
|
|
- }
|
|
|
+ log("Initialized automatic toast closing");
|
|
|
}
|
|
|
|
|
|
//#region remember song time
|
|
|
|
|
|
-interface RemSongObj {
|
|
|
+interface RemVidObj {
|
|
|
/** Watch ID */
|
|
|
watchID: string;
|
|
|
- /** Time of the song in seconds */
|
|
|
+ /** Time of the song/video in seconds */
|
|
|
songTime: number;
|
|
|
/** Timestamp this entry was last updated */
|
|
|
updateTimestamp: number;
|
|
|
}
|
|
|
|
|
|
-let remSongsCache: RemSongObj[] = [];
|
|
|
+let remVidsCache: RemVidObj[] = [];
|
|
|
|
|
|
/**
|
|
|
- * Remembers the time of the last played song and resumes playback from that time
|
|
|
- * CALLED BEFORE DOM IS READY!
|
|
|
+ * Remembers the time of the last played video and resumes playback from that time.
|
|
|
+ * **Needs to be called *before* DOM is ready!**
|
|
|
*/
|
|
|
-export async function initRememberSongTime() {
|
|
|
- if(getFeatures().rememberSongTimeSites !== "all" && getFeatures().rememberSongTimeSites !== getDomain())
|
|
|
+export async function initRestoreVideoTime() {
|
|
|
+ if(getFeatures().restoreVideoTimeSites !== "all" && getFeatures().restoreVideoTimeSites !== getDomain())
|
|
|
return;
|
|
|
|
|
|
const storedDataRaw = await GM.getValue("bytm-rem-songs");
|
|
|
if(!storedDataRaw)
|
|
|
await GM.setValue("bytm-rem-songs", "[]");
|
|
|
|
|
|
- remSongsCache = JSON.parse(String(storedDataRaw ?? "[]")) as RemSongObj[];
|
|
|
+ remVidsCache = JSON.parse(String(storedDataRaw ?? "[]")) as RemVidObj[];
|
|
|
|
|
|
- log(`Initialized song time remembering with ${remSongsCache.length} initial entries`);
|
|
|
+ log(`Initialized video time restoring with ${remVidsCache.length} initial entr${remVidsCache.length === 1 ? "y" : "ies"}`);
|
|
|
|
|
|
if(location.pathname.startsWith("/watch"))
|
|
|
- await restoreSongTime();
|
|
|
+ await restVidRestoreTime();
|
|
|
|
|
|
if(!domLoaded)
|
|
|
- document.addEventListener("DOMContentLoaded", remSongUpdateEntry);
|
|
|
+ document.addEventListener("DOMContentLoaded", restVidStartUpdateLoop);
|
|
|
else
|
|
|
- remSongUpdateEntry();
|
|
|
+ restVidStartUpdateLoop();
|
|
|
}
|
|
|
|
|
|
-/** Tries to restore the time of the currently playing song */
|
|
|
-async function restoreSongTime() {
|
|
|
+/** Tries to restore the time of the currently playing video */
|
|
|
+async function restVidRestoreTime() {
|
|
|
if(location.pathname.startsWith("/watch")) {
|
|
|
const watchID = new URL(location.href).searchParams.get("v");
|
|
|
if(!watchID)
|
|
|
return;
|
|
|
|
|
|
- const entry = remSongsCache.find(entry => entry.watchID === watchID);
|
|
|
+ if(initialParams.has("t"))
|
|
|
+ return info("Not restoring song time because the URL has the '&t' parameter", LogLevel.Info);
|
|
|
+
|
|
|
+ const entry = remVidsCache.find(entry => entry.watchID === watchID);
|
|
|
if(entry) {
|
|
|
- if(Date.now() - entry.updateTimestamp > getFeatures().rememberSongTimeDuration * 1000) {
|
|
|
- await delRemSongData(entry.watchID);
|
|
|
+ if(Date.now() - entry.updateTimestamp > getFeatures().restoreVideoTimeDuration * 1000) {
|
|
|
+ await restVidDeleteEntry(entry.watchID);
|
|
|
return;
|
|
|
}
|
|
|
+ else if(isNaN(Number(entry.songTime)))
|
|
|
+ return;
|
|
|
else {
|
|
|
- if(isNaN(entry.songTime))
|
|
|
- return;
|
|
|
const doRestoreTime = async () => {
|
|
|
const vidElem = await waitVideoElementReady();
|
|
|
- const vidRestoreTime = entry.songTime - (getFeatures().rememberSongTimeReduction ?? 0);
|
|
|
+ const vidRestoreTime = entry.songTime - (getFeatures().restoreVideoTimeReduction ?? 0);
|
|
|
vidElem.currentTime = clamp(Math.max(vidRestoreTime, 0), 0, vidElem.duration);
|
|
|
- await delRemSongData(entry.watchID);
|
|
|
+ await restVidDeleteEntry(entry.watchID);
|
|
|
info(`Restored song time to ${Math.floor(vidRestoreTime / 60)}m, ${(vidRestoreTime % 60).toFixed(1)}s`, LogLevel.Info);
|
|
|
};
|
|
|
+
|
|
|
if(!domLoaded)
|
|
|
document.addEventListener("DOMContentLoaded", doRestoreTime);
|
|
|
else
|
|
@@ -136,9 +142,10 @@ async function restoreSongTime() {
|
|
|
}
|
|
|
|
|
|
let lastSongTime = -1;
|
|
|
+let remVidCheckTimeout: NodeJS.Timeout | undefined;
|
|
|
|
|
|
-/** Only call once as this calls itself after a timeout! - Updates the currently playing song's entry in GM storage */
|
|
|
-async function remSongUpdateEntry() {
|
|
|
+/** Only call once as this calls itself after a timeout! - Updates the currently playing video's entry in GM storage */
|
|
|
+async function restVidStartUpdateLoop() {
|
|
|
if(location.pathname.startsWith("/watch")) {
|
|
|
const songTime = await getVideoTime() ?? 0;
|
|
|
|
|
@@ -154,45 +161,47 @@ async function remSongUpdateEntry() {
|
|
|
|
|
|
// don't immediately update to reduce race conditions and only update if the video is playing
|
|
|
// also it just sounds better if the song starts at the beginning if only a couple seconds have passed
|
|
|
- if(songTime > getFeatures().rememberSongTimeMinPlayTime && !paused) {
|
|
|
+ if(songTime > getFeatures().restoreVideoTimeMinPlayTime && !paused) {
|
|
|
const entry = {
|
|
|
watchID,
|
|
|
songTime,
|
|
|
updateTimestamp: Date.now(),
|
|
|
};
|
|
|
- await setRemSongData(entry);
|
|
|
+ await restVidSetEntry(entry);
|
|
|
}
|
|
|
// if the song is rewound to the beginning, delete the entry
|
|
|
else {
|
|
|
- const entry = remSongsCache.find(entry => entry.watchID === watchID);
|
|
|
- if(entry && songTime <= getFeatures().rememberSongTimeMinPlayTime)
|
|
|
- await delRemSongData(entry.watchID);
|
|
|
+ const entry = remVidsCache.find(entry => entry.watchID === watchID);
|
|
|
+ if(entry && songTime <= getFeatures().restoreVideoTimeMinPlayTime)
|
|
|
+ await restVidDeleteEntry(entry.watchID);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const expiredEntries = remSongsCache.filter(entry => Date.now() - entry.updateTimestamp > getFeatures().rememberSongTimeDuration * 1000);
|
|
|
+ const expiredEntries = remVidsCache.filter(entry => Date.now() - entry.updateTimestamp > getFeatures().restoreVideoTimeDuration * 1000);
|
|
|
for(const entry of expiredEntries)
|
|
|
- await delRemSongData(entry.watchID);
|
|
|
+ await restVidDeleteEntry(entry.watchID);
|
|
|
|
|
|
- // for no overlapping calls and better error handling
|
|
|
- setTimeout(remSongUpdateEntry, 1000);
|
|
|
+ // for no overlapping calls and better error handling:
|
|
|
+ if(remVidCheckTimeout)
|
|
|
+ clearTimeout(remVidCheckTimeout);
|
|
|
+ remVidCheckTimeout = setTimeout(restVidStartUpdateLoop, 1000);
|
|
|
}
|
|
|
|
|
|
-/** Adds an entry or updates it if it already exists */
|
|
|
-async function setRemSongData(data: RemSongObj) {
|
|
|
- const foundIdx = remSongsCache.findIndex(entry => entry.watchID === data.watchID);
|
|
|
+/** Updates an existing or creates a new entry */
|
|
|
+async function restVidSetEntry(data: RemVidObj) {
|
|
|
+ const foundIdx = remVidsCache.findIndex(entry => entry.watchID === data.watchID);
|
|
|
if(foundIdx >= 0)
|
|
|
- remSongsCache[foundIdx] = data;
|
|
|
+ remVidsCache[foundIdx] = data;
|
|
|
else
|
|
|
- remSongsCache.push(data);
|
|
|
+ remVidsCache.push(data);
|
|
|
|
|
|
- await GM.setValue("bytm-rem-songs", JSON.stringify(remSongsCache));
|
|
|
+ await GM.setValue("bytm-rem-songs", JSON.stringify(remVidsCache));
|
|
|
}
|
|
|
|
|
|
/** Deletes an entry */
|
|
|
-async function delRemSongData(watchID: string) {
|
|
|
- remSongsCache = [...remSongsCache.filter(entry => entry.watchID !== watchID)];
|
|
|
- await GM.setValue("bytm-rem-songs", JSON.stringify(remSongsCache));
|
|
|
+async function restVidDeleteEntry(watchID: string) {
|
|
|
+ remVidsCache = [...remVidsCache.filter(entry => entry.watchID !== watchID)];
|
|
|
+ await GM.setValue("bytm-rem-songs", JSON.stringify(remVidsCache));
|
|
|
}
|
|
|
|
|
|
//#region disable darkreader
|