Explorar o código

feat: advanced settings for remember song time feature

Sven hai 1 ano
pai
achega
a4dece2190
Modificáronse 6 ficheiros con 171 adicións e 51 borrados
  1. 86 10
      assets/translations/README.md
  2. 4 0
      assets/translations/en_US.json
  3. 2 0
      src/config.ts
  4. 14 23
      src/features/behavior.ts
  5. 57 16
      src/features/index.ts
  6. 8 2
      src/types.ts

+ 86 - 10
assets/translations/README.md

@@ -20,15 +20,15 @@ To submit or edit a translation, please follow [this guide](../../contributing.m
 ### Translation progress:
 |   | Locale | Translated keys | Based on |
 | :----: | ------ | --------------- | :------: |
-| ─ | [`en_US`](./en_US.json) | 164 (default locale) |  |
-| ✅ | [`de_DE`](./de_DE.json) | `164/164` (100%) | ─ |
-| ─ | [`en_UK`](./en_UK.json) | `164/164` (100%) | `en_US` |
-| ✅ | [`es_ES`](./es_ES.json) | `164/164` (100%) | ─ |
-| ✅ | [`fr_FR`](./fr_FR.json) | `164/164` (100%) | ─ |
-| ✅ | [`hi_IN`](./hi_IN.json) | `164/164` (100%) | ─ |
-| ✅ | [`ja_JA`](./ja_JA.json) | `164/164` (100%) | ─ |
-| ✅ | [`pt_BR`](./pt_BR.json) | `164/164` (100%) | ─ |
-| ✅ | [`zh_CN`](./zh_CN.json) | `164/164` (100%) | ─ |
+| ─ | [`en_US`](./en_US.json) | 168 (default locale) |  |
+| ⚠ | [`de_DE`](./de_DE.json) | `164/168` (97.6%) | ─ |
+| ─ | [`en_UK`](./en_UK.json) | `168/168` (100%) | `en_US` |
+| ⚠ | [`es_ES`](./es_ES.json) | `164/168` (97.6%) | ─ |
+| ⚠ | [`fr_FR`](./fr_FR.json) | `164/168` (97.6%) | ─ |
+| ⚠ | [`hi_IN`](./hi_IN.json) | `164/168` (97.6%) | ─ |
+| ⚠ | [`ja_JA`](./ja_JA.json) | `164/168` (97.6%) | ─ |
+| ⚠ | [`pt_BR`](./pt_BR.json) | `164/168` (97.6%) | ─ |
+| ⚠ | [`zh_CN`](./zh_CN.json) | `164/168` (97.6%) | ─ |
 
 <sub>
 ✅ - Fully translated
@@ -48,4 +48,80 @@ This means to figure out which keys are untranslated, you will need to manually
 <br>
 
 ### Missing keys:
-No missing keys
+
+<details><summary><code>de_DE</code> - 4 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_desc_rememberSongTimeDuration` | `How long in seconds to remember the song's time for after it was last played` |
+| `feature_desc_rememberSongTimeReduction` | `How many seconds to subtract when restoring the time of a remembered song` |
+| `feature_helptext_rememberSongTimeReduction` | `When restoring the time of a song that was remembered, this amount of seconds will be subtracted from the remembered time so you can re-listen to the part that was interrupted.` |
+| `feature_desc_rememberSongTimeMinPlayTime` | `Minimum amount of seconds a song needs to be played for its time to be remembered` |
+
+<br></details>
+
+<details><summary><code>es_ES</code> - 4 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_desc_rememberSongTimeDuration` | `How long in seconds to remember the song's time for after it was last played` |
+| `feature_desc_rememberSongTimeReduction` | `How many seconds to subtract when restoring the time of a remembered song` |
+| `feature_helptext_rememberSongTimeReduction` | `When restoring the time of a song that was remembered, this amount of seconds will be subtracted from the remembered time so you can re-listen to the part that was interrupted.` |
+| `feature_desc_rememberSongTimeMinPlayTime` | `Minimum amount of seconds a song needs to be played for its time to be remembered` |
+
+<br></details>
+
+<details><summary><code>fr_FR</code> - 4 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_desc_rememberSongTimeDuration` | `How long in seconds to remember the song's time for after it was last played` |
+| `feature_desc_rememberSongTimeReduction` | `How many seconds to subtract when restoring the time of a remembered song` |
+| `feature_helptext_rememberSongTimeReduction` | `When restoring the time of a song that was remembered, this amount of seconds will be subtracted from the remembered time so you can re-listen to the part that was interrupted.` |
+| `feature_desc_rememberSongTimeMinPlayTime` | `Minimum amount of seconds a song needs to be played for its time to be remembered` |
+
+<br></details>
+
+<details><summary><code>hi_IN</code> - 4 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_desc_rememberSongTimeDuration` | `How long in seconds to remember the song's time for after it was last played` |
+| `feature_desc_rememberSongTimeReduction` | `How many seconds to subtract when restoring the time of a remembered song` |
+| `feature_helptext_rememberSongTimeReduction` | `When restoring the time of a song that was remembered, this amount of seconds will be subtracted from the remembered time so you can re-listen to the part that was interrupted.` |
+| `feature_desc_rememberSongTimeMinPlayTime` | `Minimum amount of seconds a song needs to be played for its time to be remembered` |
+
+<br></details>
+
+<details><summary><code>ja_JA</code> - 4 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_desc_rememberSongTimeDuration` | `How long in seconds to remember the song's time for after it was last played` |
+| `feature_desc_rememberSongTimeReduction` | `How many seconds to subtract when restoring the time of a remembered song` |
+| `feature_helptext_rememberSongTimeReduction` | `When restoring the time of a song that was remembered, this amount of seconds will be subtracted from the remembered time so you can re-listen to the part that was interrupted.` |
+| `feature_desc_rememberSongTimeMinPlayTime` | `Minimum amount of seconds a song needs to be played for its time to be remembered` |
+
+<br></details>
+
+<details><summary><code>pt_BR</code> - 4 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_desc_rememberSongTimeDuration` | `How long in seconds to remember the song's time for after it was last played` |
+| `feature_desc_rememberSongTimeReduction` | `How many seconds to subtract when restoring the time of a remembered song` |
+| `feature_helptext_rememberSongTimeReduction` | `When restoring the time of a song that was remembered, this amount of seconds will be subtracted from the remembered time so you can re-listen to the part that was interrupted.` |
+| `feature_desc_rememberSongTimeMinPlayTime` | `Minimum amount of seconds a song needs to be played for its time to be remembered` |
+
+<br></details>
+
+<details><summary><code>zh_CN</code> - 4 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `feature_desc_rememberSongTimeDuration` | `How long in seconds to remember the song's time for after it was last played` |
+| `feature_desc_rememberSongTimeReduction` | `How many seconds to subtract when restoring the time of a remembered song` |
+| `feature_helptext_rememberSongTimeReduction` | `When restoring the time of a song that was remembered, this amount of seconds will be subtracted from the remembered time so you can re-listen to the part that was interrupted.` |
+| `feature_desc_rememberSongTimeMinPlayTime` | `Minimum amount of seconds a song needs to be played for its time to be remembered` |
+
+<br></details>

+ 4 - 0
assets/translations/en_US.json

@@ -143,6 +143,10 @@
     "feature_helptext_rememberSongTime-1": "Sometimes when reloading the page or restoring it after accidentally closing it, you want to resume listening at the same point. This feature allows you to do that.\nIn order to record the song's time, you need to play it for %1 second, then its time will be remembered and be restorable for a short while.",
     "feature_helptext_rememberSongTime-n": "Sometimes when reloading the page or restoring it after accidentally closing it, you want to resume listening at the same point. This feature allows you to do that.\nIn order to record the song's time, you need to play it for %1 seconds, then its time will be remembered and be restorable for a short while.",
     "feature_desc_rememberSongTimeSites": "On which sites should the song time be remembered and restored?",
+    "feature_desc_rememberSongTimeDuration": "How long in seconds to remember the song's time for after it was last played",
+    "feature_desc_rememberSongTimeReduction": "How many seconds to subtract when restoring the time of a remembered song",
+    "feature_helptext_rememberSongTimeReduction": "When restoring the time of a song that was remembered, this amount of seconds will be subtracted from the remembered time so you can re-listen to the part that was interrupted.",
+    "feature_desc_rememberSongTimeMinPlayTime": "Minimum amount of seconds a song needs to be played for its time to be remembered",
     "feature_desc_lockVolume": "Force the volume slider to stay at a specific level",
     "feature_desc_lockVolumeLevel": "What volume level to lock the volume slider at",
 

+ 2 - 0
src/config.ts

@@ -50,6 +50,8 @@ export const migrations: ConfigMigrationsDict = {
     "clearLyricsCache", "advancedMode",
     "lockVolume", "lockVolumeLevel",
     "checkVersionNow", "advancedLyricsFilter",
+    "rememberSongTimeDuration", "rememberSongTimeReduction",
+    "rememberSongTimeMinPlayTime",
   ], oldData),
 };
 

+ 14 - 23
src/features/behavior.ts

@@ -1,14 +1,8 @@
 import { clamp, pauseFor } from "@sv443-network/userutils";
 import { error, getDomain, getVideoTime, info, log, onSelectorOld, videoSelector } from "../utils";
-import { LogLevel, type FeatureConfig } from "../types";
+import { LogLevel } from "../types";
 import { getFeatures } from "src/config";
 
-let features: FeatureConfig;
-
-export function setBehaviorConfig(feats: FeatureConfig) {
-  features = feats;
-}
-
 //#MARKER beforeunload popup
 
 let beforeUnloadEnabled = true;
@@ -38,7 +32,7 @@ export async function initBeforeUnloadHook() {
       const origListener = typeof args[1] === "function" ? args[1] : args[1].handleEvent;
       args[1] = function(...a) {
         if(!beforeUnloadEnabled && args[0] === "beforeunload") {
-          info("Prevented beforeunload event listener from being called");
+          info("Prevented 'beforeunload' event listener");
           return false;
         }
         else
@@ -56,7 +50,7 @@ export async function initBeforeUnloadHook() {
 export async function initAutoCloseToasts() {
   try {
     const animTimeout = 300;
-    const closeTimeout = Math.max(features.closeToastsTimeout * 1000 + animTimeout, animTimeout);
+    const closeTimeout = Math.max(getFeatures().closeToastsTimeout * 1000 + animTimeout, animTimeout);
 
     onSelectorOld("tp-yt-paper-toast#toast", {
       all: true,
@@ -73,7 +67,7 @@ export async function initAutoCloseToasts() {
           await pauseFor(closeTimeout);
 
           toastElem.classList.remove("paper-toast-open");
-          log(`Automatically closed toast '${toastElem.querySelector<HTMLDivElement>("#text-container yt-formatted-string")?.textContent}' after ${features.closeToastsTimeout * 1000}ms`);
+          log(`Automatically closed toast '${toastElem.querySelector<HTMLDivElement>("#text-container yt-formatted-string")?.textContent}' after ${getFeatures().closeToastsTimeout * 1000}ms`);
 
           // wait for the transition to finish
           await pauseFor(animTimeout);
@@ -95,22 +89,17 @@ export async function initAutoCloseToasts() {
 interface RemSongObj {
   /** Watch ID */
   watchID: string;
-  /** Time of the song */
+  /** Time of the song in seconds */
   songTime: number;
   /** Timestamp this entry was last updated */
   updateTimestamp: number;
 }
 
-/** After how many milliseconds a remembered entry should expire */
-const remSongEntryExpiry = 1000 * 60 * 1;
-/** Minimum time a song has to be played before it is committed to GM storage */
-export const remSongMinPlayTime = 10;
-
 let remSongsCache: RemSongObj[] = [];
 
 /** Remembers the time of the last played song and resumes playback from that time */
 export async function initRememberSongTime() {
-  if(features.rememberSongTimeSites !== "all" && features.rememberSongTimeSites !== getDomain())
+  if(getFeatures().rememberSongTimeSites !== "all" && getFeatures().rememberSongTimeSites !== getDomain())
     return;
 
   const storedDataRaw = await GM.getValue("bytm-rem-songs");
@@ -138,7 +127,7 @@ async function restoreSongTime() {
 
     const entry = remSongsCache.find(entry => entry.watchID === watchID);
     if(entry) {
-      if(Date.now() - entry.updateTimestamp > remSongEntryExpiry) {
+      if(Date.now() - entry.updateTimestamp > getFeatures().rememberSongTimeDuration * 1000) {
         await delRemSongData(entry.watchID);
         return;
       }
@@ -149,11 +138,13 @@ async function restoreSongTime() {
               const applyTime = async () => {
                 if(isNaN(entry.songTime))
                   return;
-                vidElem.currentTime = clamp(Math.max(entry.songTime, 0), 0, vidElem.duration);
+                const vidRestoreTime = entry.songTime - (getFeatures().rememberSongTimeReduction ?? 0);
+                vidElem.currentTime = clamp(Math.max(vidRestoreTime, 0), 0, vidElem.duration);
                 await delRemSongData(entry.watchID);
-                info(`Restored song time to ${Math.floor(entry.songTime / 60)}m, ${(entry.songTime % 60).toFixed(1)}s`, LogLevel.Info);
+                info(`Restored song time to ${Math.floor(vidRestoreTime / 60)}m, ${(vidRestoreTime % 60).toFixed(1)}s`, LogLevel.Info);
               };
 
+              // jump to video time just after YT has finished doing their own shenanigans
               if(vidElem.readyState === 4)
                 applyTime();
               else
@@ -180,7 +171,7 @@ 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 > remSongMinPlayTime && !paused) {
+    if(songTime > getFeatures().rememberSongTimeMinPlayTime && !paused) {
       const entry = {
         watchID,
         songTime,
@@ -191,12 +182,12 @@ async function remSongUpdateEntry() {
     // if the song is rewound to the beginning, delete the entry
     else {
       const entry = remSongsCache.find(entry => entry.watchID === watchID);
-      if(entry && songTime <= remSongMinPlayTime)
+      if(entry && songTime <= getFeatures().rememberSongTimeMinPlayTime)
         await delRemSongData(entry.watchID);
     }
   }
 
-  const expiredEntries = remSongsCache.filter(entry => Date.now() - entry.updateTimestamp > remSongEntryExpiry);
+  const expiredEntries = remSongsCache.filter(entry => Date.now() - entry.updateTimestamp > getFeatures().rememberSongTimeDuration * 1000);
   for(const entry of expiredEntries)
     await delRemSongData(entry.watchID);
 }

+ 57 - 16
src/features/index.ts

@@ -1,7 +1,6 @@
 import { debounce } from "@sv443-network/userutils";
 import { getPreferredLocale, resourceToHTMLString, t, tp } from "../utils";
 import langMapping from "../../assets/locales.json" assert { type: "json" };
-import { remSongMinPlayTime } from "./behavior";
 import { clearLyricsCache, getLyricsCache } from "./lyricsCache";
 import { doVersionCheck } from "./versionCheck";
 import { mode } from "../constants";
@@ -31,7 +30,7 @@ const localeOptions = Object.entries(langMapping).reduce((a, [locale, { name }])
 
 /** Decoration elements that can be added next to the label */
 const adornments = {
-  advancedMode: async () => `<span class="bytm-advanced-mode-icon bytm-adorn-icon" title="${t("advanced_mode")}">${await resourceToHTMLString("icon-advanced_mode") ?? ""}</span>`,
+  advanced: async () => `<span class="bytm-advanced-mode-icon bytm-adorn-icon" title="${t("advanced_mode")}">${await resourceToHTMLString("icon-advanced_mode") ?? ""}</span>`,
   experimental: async () => `<span class="bytm-experimental-icon bytm-adorn-icon" title="${t("experimental_feature")}">${await resourceToHTMLString("icon-experimental") ?? ""}</span>`,
   globe: async () => await resourceToHTMLString("icon-globe") ?? "",
 };
@@ -57,7 +56,7 @@ const adornments = {
  * | `click: () => void`                               | for type `button` only - function that will be called when the button is clicked |
  * | `helpText: string / () => string`                 | function that returns an HTML string or the literal string itself that will be the help text for this feature - writing as function is useful for pluralizing or inserting values into the translation at runtime - if not set, translation with key `feature_helptext_featureKey` will be used instead, if available |
  * | `textAdornment: () => string / Promise<string>`   | function that returns an HTML string that will be appended to the text in the config menu as an adornment element - TODO: to be replaced in the big menu rework |
- * | `unit: string / (val: number) => string`          | Only if type is `number` or `slider` - The unit text that is displayed next to the input element, i.e. "px" |
+ * | `unit: string / (val: number) => string`          | Only if type is `number` or `slider` - The unit text that is displayed next to the input element, i.e. " px" - a leading space need to be added by hand! |
  * | `min: number`                                     | Only if type is `number` or `slider` - Overwrites the default of the `min` property of the HTML input element |
  * | `max: number`                                     | Only if type is `number` or `slider` - Overwrites the default of the `max` property of the HTML input element |
  * | `step: number`                                    | Only if type is `number` or `slider` - Overwrites the default of the `step` property of the HTML input element |
@@ -72,12 +71,6 @@ const adornments = {
  */
 export const featInfo = {
   //#SECTION layout
-  removeUpgradeTab: {
-    type: "toggle",
-    category: "layout",
-    default: true,
-    enable: noopTODO,
-  },
   volumeSliderLabel: {
     type: "toggle",
     category: "layout",
@@ -144,6 +137,12 @@ export const featInfo = {
     enable: noopTODO,
     disable: noopTODO,
   },
+  removeUpgradeTab: {
+    type: "toggle",
+    category: "layout",
+    default: true,
+    enable: noopTODO,
+  },
 
   //#SECTION song lists
   lyricsQueueButton: {
@@ -196,7 +195,7 @@ export const featInfo = {
     default: true,
     enable: noopTODO,
     disable: noopTODO, // TODO: feasible?
-    helpText: () => tp("feature_helptext_rememberSongTime", remSongMinPlayTime, remSongMinPlayTime)
+    helpText: () => tp("feature_helptext_rememberSongTime", getFeatures().rememberSongTimeMinPlayTime, getFeatures().rememberSongTimeMinPlayTime)
   },
   rememberSongTimeSites: {
     type: "select",
@@ -210,6 +209,48 @@ export const featInfo = {
     enable: noopTODO,
     change: noopTODO,
   },
+  rememberSongTimeDuration: {
+    type: "number",
+    category: "behavior",
+    min: 3,
+    max: 60 * 60 * 24 * 7,
+    step: 1,
+    default: 60,
+    unit: "s",
+    enable: noopTODO,
+    change: noopTODO,
+    advanced: true,
+    // TODO: to be reworked or removed in the big menu rework
+    textAdornment: adornments.advanced,
+  },
+  rememberSongTimeReduction: {
+    type: "number",
+    category: "behavior",
+    min: 0,
+    max: 30,
+    step: 0.1,
+    default: 0,
+    unit: "s",
+    enable: noopTODO,
+    change: noopTODO,
+    advanced: true,
+    // TODO: to be reworked or removed in the big menu rework
+    textAdornment: adornments.advanced,
+  },
+  rememberSongTimeMinPlayTime: {
+    type: "slider",
+    category: "behavior",
+    min: 1,
+    max: 30,
+    step: 0.5,
+    default: 10,
+    unit: "s",
+    enable: noopTODO,
+    change: noopTODO,
+    advanced: true,
+    // TODO: to be reworked or removed in the big menu rework
+    textAdornment: adornments.advanced,
+  },
   lockVolume: {
     type: "toggle",
     category: "behavior",
@@ -296,7 +337,7 @@ export const featInfo = {
     normalize: (val: string) => val.trim().replace(/\/+$/, ""),
     advanced: true,
     // TODO: to be reworked or removed in the big menu rework
-    textAdornment: adornments.advancedMode,
+    textAdornment: adornments.advanced,
   },
   geniUrlToken: {
     type: "text",
@@ -306,7 +347,7 @@ export const featInfo = {
     normalize: (val: string) => val.trim(),
     advanced: true,
     // TODO: to be reworked or removed in the big menu rework
-    textAdornment: adornments.advancedMode,
+    textAdornment: adornments.advanced,
   },
   lyricsCacheMaxSize: {
     type: "slider",
@@ -320,7 +361,7 @@ export const featInfo = {
     change: noopTODO,
     advanced: true,
     // TODO: to be reworked or removed in the big menu rework
-    textAdornment: adornments.advancedMode,
+    textAdornment: adornments.advanced,
   },
   lyricsCacheTTL: {
     type: "slider",
@@ -334,7 +375,7 @@ export const featInfo = {
     change: noopTODO,
     advanced: true,
     // TODO: to be reworked or removed in the big menu rework
-    textAdornment: adornments.advancedMode,
+    textAdornment: adornments.advanced,
   },
   clearLyricsCache: {
     type: "button",
@@ -349,7 +390,7 @@ export const featInfo = {
     },
     advanced: true,
     // TODO: to be reworked or removed in the big menu rework
-    textAdornment: adornments.advancedMode,
+    textAdornment: adornments.advanced,
   },
   advancedLyricsFilter: {
     type: "toggle",
@@ -404,7 +445,7 @@ export const featInfo = {
     enable: noopTODO,
     disable: noopTODO,
     // TODO: to be reworked or removed in the big menu rework
-    textAdornment: () => getFeatures().advancedMode ? adornments.advancedMode() : undefined,
+    textAdornment: () => getFeatures().advancedMode ? adornments.advanced() : undefined,
   },
 } as const satisfies FeatureInfo;
 

+ 8 - 2
src/types.ts

@@ -205,8 +205,6 @@ export type FeatureInfo = Record<
 /** Feature configuration */
 export interface FeatureConfig {
   //#SECTION layout
-  /** Remove the \"Upgrade\" / YT Music Premium tab */
-  removeUpgradeTab: boolean;
   /** Add a percentage label to the volume slider */
   volumeSliderLabel: boolean;
   /** The width of the volume slider in pixels */
@@ -223,6 +221,8 @@ export interface FeatureConfig {
   numKeysSkipToTime: boolean;
   /** Fix spacing issues in the layout */
   fixSpacing: boolean;
+  /** Remove the \"Upgrade\" / YT Music Premium tab */
+  removeUpgradeTab: boolean;
 
   //#SECTION song lists
   /** Add a button to each song in the queue to quickly open its lyrics page */
@@ -243,6 +243,12 @@ export interface FeatureConfig {
   rememberSongTime: boolean;
   /** Where to remember the song time */
   rememberSongTimeSites: Domain | "all";
+  /** Time in seconds to remember the song time for */
+  rememberSongTimeDuration: number;
+  /** Time in seconds to subtract from the remembered song time */
+  rememberSongTimeReduction: number;
+  /** Minimum time in seconds the song needs to be played before it is remembered */
+  rememberSongTimeMinPlayTime: number;
   /** Lock the volume slider at a specific level */
   lockVolume: boolean;
   /** The volume level to lock the slider at */