Browse Source

ref: rename global observers & add more siteEvents

Sv443 1 year ago
parent
commit
3f74b9da34
2 changed files with 71 additions and 16 deletions
  1. 12 11
      src/observers.ts
  2. 59 5
      src/siteEvents.ts

+ 12 - 11
src/observers.ts

@@ -8,47 +8,48 @@ const defaultObserverOptions: SelectorObserverOptions = {
   defaultDebounce: 100,
 };
 
-export const observers = {} as Record<ObserverName, SelectorObserver>;
+/** Global SelectorObserver instances usable throughout the script for improved performance */
+export const globservers = {} as Record<ObserverName, SelectorObserver>;
 
 /** Call after DOM load to initialize all SelectorObserver instances */
 export function initObservers() {
   try {
     // #SECTION body = the entire <body> element - use sparingly due to performance impacts!
-    observers.body = new SelectorObserver(document.body, {
+    globservers.body = new SelectorObserver(document.body, {
       ...defaultObserverOptions,
       subtree: false,
     });
-    observers.body.enable();
+    globservers.body.enable();
 
     // #SECTION playerBar = media controls bar at the bottom of the page
     const playerBarSelector = "ytmusic-app-layout ytmusic-player-bar.ytmusic-app";
-    observers.playerBar = new SelectorObserver(playerBarSelector, {
+    globservers.playerBar = new SelectorObserver(playerBarSelector, {
       ...defaultObserverOptions,
       defaultDebounce: 200,
     });
-    observers.body.addListener(playerBarSelector, {
+    globservers.body.addListener(playerBarSelector, {
       listener: () => {
         log("#DBG-UU enabling playerBar observer");
-        observers.playerBar.enable();
+        globservers.playerBar.enable();
       },
     });
 
     // #SECTION playerBarInfo = song title, artist, album, etc. inside the player bar
     const playerBarInfoSelector = `${playerBarSelector} .middle-controls .content-info-wrapper`;
-    observers.playerBarInfo = new SelectorObserver(playerBarInfoSelector, {
+    globservers.playerBarInfo = new SelectorObserver(playerBarInfoSelector, {
       ...defaultObserverOptions,
       attributes: true,
       attributeFilter: ["title"],
     });
-    observers.playerBarInfo.addListener(playerBarInfoSelector, {
+    globservers.playerBarInfo.addListener(playerBarInfoSelector, {
       listener: () => {
         log("#DBG-UU enabling playerBarTitle observer");
-        observers.playerBarInfo.enable();
+        globservers.playerBarInfo.enable();
       },
     });
 
     // #DEBUG example: listen for title change:
-    observers.playerBarInfo.addListener("yt-formatted-string.title", {
+    globservers.playerBarInfo.addListener("yt-formatted-string.title", {
       continuous: true,
       listener: (titleElem) => {
         log("#DBG-UU >>>>> title changed", titleElem.title);
@@ -64,5 +65,5 @@ export function initObservers() {
 
 /** Interface function for adding listeners to the already present observers */
 export function addSelectorListener<TElem extends Element>(observerName: ObserverName, selector: string, options: SelectorListenerOptions<TElem>) {
-  observers[observerName].addListener(selector, options);
+  globservers[observerName].addListener(selector, options);
 }

+ 59 - 5
src/siteEvents.ts

@@ -1,7 +1,8 @@
 import { createNanoEvents } from "nanoevents";
-import { error, info } from "./utils";
+import { error, info, onSelectorOld } from "./utils";
 import { FeatureConfig } from "./types";
 import { emitInterface } from "./interface";
+import { globservers } from "./observers";
 
 export interface SiteEventsMap {
   // misc:
@@ -24,6 +25,15 @@ export interface SiteEventsMap {
   queueChanged: (queueElement: HTMLElement) => void;
   /** Emitted whenever child nodes are added to or removed from the autoplay queue underneath the song queue */
   autoplayQueueChanged: (queueElement: HTMLElement) => void;
+  /**
+   * Emitted whenever the current song title changes
+   * @param newTitle The new song title
+   * @param oldTitle The old song title, or `null` if no previous title was found
+   * @param initialPlay Whether this is the first played song
+   */
+  songTitleChanged: (newTitle: string, oldTitle: string | null, initialPlay: boolean) => void;
+  /** Emitted whenever the current song's watch ID changes */
+  watchIdChanged: (newId: string, oldId: string | null) => void;
 }
 
 /** Array of all site events */
@@ -36,6 +46,8 @@ export const allSiteEvents = [
   "hotkeyInputActive",
   "queueChanged",
   "autoplayQueueChanged",
+  "songTitleChanged",
+  "watchIdChanged",
 ] as const;
 
 /** EventEmitter instance that is used to detect changes to the site */
@@ -65,8 +77,12 @@ export async function initSiteEvents() {
     });
 
     // only observe added or removed elements
-    queueObs.observe(document.querySelector("#side-panel #contents.ytmusic-player-queue")!, {
-      childList: true,
+    onSelectorOld("#side-panel #contents.ytmusic-player-queue", {
+      listener: (el) => {
+        queueObs.observe(el, {
+          childList: true,
+        });
+      },
     });
 
     const autoplayObs = new MutationObserver(([ { addedNodes, removedNodes, target } ]) => {
@@ -76,8 +92,31 @@ export async function initSiteEvents() {
       }
     });
 
-    autoplayObs.observe(document.querySelector("#side-panel ytmusic-player-queue #automix-contents")!, {
-      childList: true,
+    onSelectorOld("#side-panel ytmusic-player-queue #automix-contents", {
+      listener: (el) => {
+        autoplayObs.observe(el, {
+          childList: true,
+        });
+      },
+    });
+
+    //#SECTION player bar
+
+    let lastTitle: string | null = null;
+    let initialPlay = true;
+
+    globservers.playerBarInfo.addListener("yt-formatted-string.title", {
+      continuous: true,
+      listener: (titleElem) => {
+        const oldTitle = lastTitle;
+        const newTitle = titleElem.textContent;
+        if(newTitle === lastTitle || !newTitle)
+          return;
+        lastTitle = newTitle;
+        info(`Detected song change - old title: "${oldTitle}" - new title: "${newTitle}" - initial play: ${initialPlay}`);
+        emitSiteEvent("songTitleChanged", newTitle, oldTitle, initialPlay);
+        initialPlay = false;
+      },
     });
 
     info("Successfully initialized SiteEvents observers");
@@ -86,6 +125,21 @@ export async function initSiteEvents() {
       queueObs,
       autoplayObs,
     ]);
+
+    //#SECTION other
+
+    let lastWatchId: string | null = null;
+
+    setInterval(() => {
+      if(location.pathname.startsWith("/watch")) {
+        const newWatchId = new URL(location.href).searchParams.get("v");
+        if(newWatchId && newWatchId !== lastWatchId) {
+          info(`Detected watch ID change - old ID: "${lastWatchId}" - new ID: "${newWatchId}"`);
+          emitSiteEvent("watchIdChanged", newWatchId, lastWatchId);
+          lastWatchId = newWatchId;
+        }
+      }
+    }, 200);
   }
   catch(err) {
     error("Couldn't initialize SiteEvents observers due to an error:\n", err);