PoC: separated tidal interface controller into separate controllers

This commit is contained in:
Rick van Lieshout 2024-10-27 23:05:55 +01:00
parent b2afd44dd6
commit 01c5ec7ebb
4 changed files with 216 additions and 16 deletions

View File

@ -0,0 +1,168 @@
import { TidalController } from "./MediaController";
export class DomTidalController implements TidalController {
public elements = {
play: '*[data-test="play"]',
pause: '*[data-test="pause"]',
next: '*[data-test="next"]',
previous: 'button[data-test="previous"]',
title: '*[data-test^="footer-track-title"]',
artists: '*[data-test^="grid-item-detail-text-title-artist"]',
home: '*[data-test="menu--home"]',
back: '[title^="Back"]',
forward: '[title^="Next"]',
search: '[class^="searchField"]',
shuffle: '*[data-test="shuffle"]',
repeat: '*[data-test="repeat"]',
account: '*[data-test^="profile-image-button"]',
settings: '*[data-test^="sidebar-menu-button"]',
openSettings: '*[data-test^="open-settings"]',
media: '*[data-test="current-media-imagery"]',
image: "img",
current: '*[data-test="current-time"]',
duration: '*[class^=playbackControlsContainer] *[data-test="duration"]',
bar: '*[data-test="progress-bar"]',
footer: "#footerPlayer",
mediaItem: "[data-type='mediaItem']",
album_header_title: '*[class^="playingFrom"] span:nth-child(2)',
playing_from: '*[class^="playingFrom"] span:nth-child(2)',
queue_album: "*[class^=playQueueItemsContainer] *[class^=groupTitle] span:nth-child(2)",
currentlyPlaying: "[class^='isPlayingIcon'], [data-test-is-playing='true']",
album_name_cell: '[class^="album"]',
tracklist_row: '[data-test="tracklist-row"]',
volume: '*[data-test="volume"]',
favorite: '*[data-test="footer-favorite-button"]',
/**
* Get an element from the dom
* @param {*} key key in elements object to fetch
*/
get: function (key: string) {
return globalThis.document.querySelector(this[key.toLowerCase()]);
},
/**
* Get the icon of the current media
*/
getSongIcon: function () {
const figure = this.get("media");
if (figure) {
const mediaElement = figure.querySelector(this["image"]);
if (mediaElement) {
return mediaElement.src.replace("80x80", "640x640");
}
}
return "";
},
/**
* returns an array of all artists in the current media
* @returns {Array} artists
*/
getArtistsArray: function () {
const footer = this.get("footer");
if (footer) {
const artists = footer.querySelectorAll(this.artists);
if (artists)
return Array.from(artists).map((artist) => (artist as HTMLElement).textContent);
}
return [];
},
/**
* unify the artists array into a string separated by commas
* @param {Array} artistsArray
* @returns {String} artists
*/
getArtistsString: function (artistsArray: string[]) {
if (artistsArray.length > 0) return artistsArray.join(", ");
return "unknown artist(s)";
},
getAlbumName: function () {
//If listening to an album, get its name from the header title
if (globalThis.location.href.includes("/album/")) {
const albumName = globalThis.document.querySelector(this.album_header_title);
if (albumName) {
return albumName.textContent;
}
//If listening to a playlist or a mix, get album name from the list
} else if (
globalThis.location.href.includes("/playlist/") ||
globalThis.location.href.includes("/mix/")
) {
// TODO: fix
// if (currentPlayStatus === MediaStatus.playing) {
// // find the currently playing element from the list (which might be in an album icon), traverse back up to the mediaItem (row) and select the album cell.
// // document.querySelector("[class^='isPlayingIcon'], [data-test-is-playing='true']").closest('[data-type="mediaItem"]').querySelector('[class^="album"]').textContent
// const row = window.document.querySelector(this.currentlyPlaying).closest(this.mediaItem);
// if (row) {
// return row.querySelector(this.album_name_cell).textContent;
// }
// }
}
// see whether we're on the queue page and get it from there
const queueAlbumName = this.getText("queue_album");
if (queueAlbumName) {
return queueAlbumName;
}
return "";
},
isMuted: function () {
return this.get("volume").getAttribute("aria-checked") === "false"; // it's muted if aria-checked is false
},
isFavorite: function () {
return this.get("favorite").getAttribute("aria-checked") === "true";
},
/**
* Shorthand function to get the text of a dom element
* @param {*} key key in elements object to fetch
*/
getText: function (key: string) {
const element = this.get(key);
return element ? element.textContent : "";
},
/**
* Shorthand function to click a dom element
* @param {*} key key in elements object to fetch
*/
click: function (key: string) {
this.get(key).click();
return this;
},
/**
* Shorthand function to focus a dom element
* @param {*} key key in elements object to fetch
*/
focus: function (key: string) {
return this.get(key).focus();
},
};
playPause = (): void => {
const play = this.elements.get("play");
if (play) {
this.elements.click("play");
} else {
this.elements.click("pause");
}
};
goToHome(): void {
this.elements.click("home");
}
hookup = (): void => {
throw new Error("Method not implemented.");
};
}

View File

@ -0,0 +1,13 @@
export interface TidalController {
/**
* Play or pause the current media
*/
playPause(): void;
/**
* Hook up the controller to the current web instance
*/
hookup(): void;
goToHome(): void;
}

View File

@ -0,0 +1,19 @@
import { DomTidalController } from "./DomTidalController";
import { TidalController } from "./MediaController";
export class MediaSessionTidalController implements TidalController {
public domMediaController: TidalController;
constructor() {
this.domMediaController = new DomTidalController();
}
goToHome(): void {
this.domMediaController.goToHome();
}
playPause(): void {
globalThis.alert("Method not implemented");
}
hookup(): void {
globalThis.alert("Method not implemented");
}
}

View File

@ -21,6 +21,9 @@ import { addHotkey } from "./scripts/hotkeys";
import { ObjectToDotNotation } from "./scripts/objectUtilities"; import { ObjectToDotNotation } from "./scripts/objectUtilities";
import { settingsStore } from "./scripts/settings"; import { settingsStore } from "./scripts/settings";
import { setTitle } from "./scripts/window-functions"; import { setTitle } from "./scripts/window-functions";
import { DomTidalController } from "./TidalControllers/DomTidalController";
import { MediaSessionTidalController } from "./TidalControllers/MediaSessionTidalController";
import { TidalController } from "./TidalControllers/MediaController";
const notificationPath = `${app.getPath("userData")}/notification.jpg`; const notificationPath = `${app.getPath("userData")}/notification.jpg`;
let currentSong = ""; let currentSong = "";
@ -35,6 +38,16 @@ let currentShuffleState = false;
let currentMediaInfo: MediaInfo; let currentMediaInfo: MediaInfo;
let currentNotification: Electron.Notification; let currentNotification: Electron.Notification;
let tidalController: TidalController;
// TODO: replace with setting
// eslint-disable-next-line no-constant-condition
if (true) {
tidalController = new DomTidalController();
} else {
tidalController = new MediaSessionTidalController();
}
const elements = { const elements = {
play: '*[data-test="play"]', play: '*[data-test="play"]',
pause: '*[data-test="pause"]', pause: '*[data-test="pause"]',
@ -195,19 +208,6 @@ function getUpdateFrequency() {
} }
} }
/**
* Play or pause the current media
*/
function playPause() {
const play = elements.get("play");
if (play) {
elements.click("play");
} else {
elements.click("pause");
}
}
/** /**
* Clears the old listenbrainz data on launch * Clears the old listenbrainz data on launch
*/ */
@ -235,7 +235,7 @@ function addHotKeys() {
}); });
addHotkey("Control+h", function () { addHotkey("Control+h", function () {
elements.click("home"); tidalController.goToHome();
}); });
addHotkey("backspace", function () { addHotkey("backspace", function () {
@ -319,7 +319,7 @@ function addIPCEventListeners() {
case globalEvents.playPause: case globalEvents.playPause:
case globalEvents.play: case globalEvents.play:
case globalEvents.pause: case globalEvents.pause:
playPause(); tidalController.playPause();
break; break;
case globalEvents.next: case globalEvents.next:
elements.click("next"); elements.click("next");
@ -455,7 +455,7 @@ function addMPRIS() {
const eventValue = events[eventName]; const eventValue = events[eventName];
switch (events[eventValue]) { switch (events[eventValue]) {
case events.playpause: case events.playpause:
playPause(); tidalController.playPause();
break; break;
default: default:
elements.click(eventValue); elements.click(eventValue);