mirror of
https://github.com/Mastermindzh/tidal-hifi.git
synced 2025-08-03 19:41:28 +02:00
finished interface for TidalController and moved most domLogic to the domController
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
export type DomControllerOptions = {
|
||||
/**
|
||||
* Interval that tidal-hifi scrapes the dom for information
|
||||
*/
|
||||
refreshInterval: number;
|
||||
};
|
@@ -1,18 +1,15 @@
|
||||
import { MediaStatus } from "../models/mediaStatus";
|
||||
import { RepeatState } from "../models/repeatState";
|
||||
import { TidalController } from "./TidalController";
|
||||
import { convertDurationToSeconds } from "../../features/time/parse";
|
||||
import { MediaInfo } from "../../models/mediaInfo";
|
||||
import { MediaStatus } from "../../models/mediaStatus";
|
||||
import { RepeatState } from "../../models/repeatState";
|
||||
import { TidalController } from "../TidalController";
|
||||
import { DomControllerOptions } from "./DomControllerOptions";
|
||||
|
||||
export class DomTidalController implements TidalController {
|
||||
private currentPlayStatus = MediaStatus.paused;
|
||||
|
||||
/**
|
||||
* Convert the duration from MM:SS to seconds
|
||||
* @param {*} duration
|
||||
*/
|
||||
private convertDuration(duration: string) {
|
||||
const parts = duration.split(":");
|
||||
return parseInt(parts[1]) + 60 * parseInt(parts[0]);
|
||||
}
|
||||
export class DomTidalController implements TidalController<DomControllerOptions> {
|
||||
private updateSubscriber: (state: Partial<MediaInfo>) => void;
|
||||
private currentlyPlaying = MediaStatus.paused;
|
||||
private currentRepeatState: RepeatState = RepeatState.off;
|
||||
private currentShuffleState = false;
|
||||
|
||||
private readonly elements = {
|
||||
play: '*[data-test="play"]',
|
||||
@@ -106,7 +103,7 @@ export class DomTidalController implements TidalController {
|
||||
globalThis.location.href.includes("/playlist/") ||
|
||||
globalThis.location.href.includes("/mix/")
|
||||
) {
|
||||
if (this.currentPlayStatus === MediaStatus.playing) {
|
||||
if (this.getCurrentlyPlayingStatus() === 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);
|
||||
@@ -160,6 +157,65 @@ export class DomTidalController implements TidalController {
|
||||
},
|
||||
};
|
||||
|
||||
onMediaInfoUpdate(callback: (state: Partial<MediaInfo>) => void): void {
|
||||
this.updateSubscriber = callback;
|
||||
}
|
||||
|
||||
bootstrap(options: DomControllerOptions): void {
|
||||
/**
|
||||
* Checks if Tidal is playing a video or song by grabbing the "a" element from the title.
|
||||
* If it's a song it returns the track URL, if not it will return undefined
|
||||
*/
|
||||
const getTrackURL = () => {
|
||||
const id = this.getTrackId();
|
||||
return `https://tidal.com/browse/track/${id}`;
|
||||
};
|
||||
|
||||
setInterval(async () => {
|
||||
const title = this.getTitle();
|
||||
const artistsArray = this.getArtists();
|
||||
const artistsString = this.getArtistsString();
|
||||
|
||||
const current = this.getCurrentTime();
|
||||
const currentStatus = this.getCurrentlyPlayingStatus();
|
||||
const shuffleState = this.getCurrentShuffleState();
|
||||
const repeatState = this.getCurrentRepeatState();
|
||||
|
||||
const playStateChanged = currentStatus != this.currentlyPlaying;
|
||||
const shuffleStateChanged = shuffleState != this.currentShuffleState;
|
||||
const repeatStateChanged = repeatState != this.currentRepeatState;
|
||||
|
||||
if (playStateChanged) this.currentlyPlaying = currentStatus;
|
||||
if (shuffleStateChanged) this.currentShuffleState = shuffleState;
|
||||
if (repeatStateChanged) this.currentRepeatState = repeatState;
|
||||
|
||||
const album = this.getAlbumName();
|
||||
const duration = this.getDuration();
|
||||
const updatedInfo = {
|
||||
title,
|
||||
artists: artistsString,
|
||||
artistsArray,
|
||||
album: album,
|
||||
playingFrom: this.getPlayingFrom(),
|
||||
status: currentStatus,
|
||||
url: getTrackURL(),
|
||||
current,
|
||||
currentInSeconds: convertDurationToSeconds(current),
|
||||
duration,
|
||||
durationInSeconds: convertDurationToSeconds(duration),
|
||||
image: this.getSongIcon(),
|
||||
favorite: this.isFavorite(),
|
||||
player: {
|
||||
status: currentStatus,
|
||||
shuffle: shuffleState,
|
||||
repeat: repeatState,
|
||||
},
|
||||
};
|
||||
|
||||
this.updateSubscriber(updatedInfo);
|
||||
}, options.refreshInterval);
|
||||
}
|
||||
|
||||
playPause = (): void => {
|
||||
const play = this.elements.get("play");
|
||||
|
||||
@@ -246,7 +302,7 @@ export class DomTidalController implements TidalController {
|
||||
return this.elements.getText("current");
|
||||
}
|
||||
getCurrentPositionInSeconds(): number {
|
||||
return this.convertDuration(this.getCurrentPosition()) * 1000 * 1000;
|
||||
return convertDurationToSeconds(this.getCurrentPosition());
|
||||
}
|
||||
|
||||
getTrackId(): string {
|
||||
@@ -289,7 +345,4 @@ export class DomTidalController implements TidalController {
|
||||
getSongIcon(): string {
|
||||
return this.elements.getSongIcon();
|
||||
}
|
||||
setPlayStatus(status: MediaStatus): void {
|
||||
this.currentPlayStatus = status;
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
import { MediaInfo } from "../models/mediaInfo";
|
||||
import { MediaStatus } from "../models/mediaStatus";
|
||||
import { RepeatState } from "../models/repeatState";
|
||||
import { DomTidalController } from "./DomTidalController";
|
||||
import { DomTidalController } from "./DomController/DomTidalController";
|
||||
import { TidalController } from "./TidalController";
|
||||
|
||||
export class MediaSessionTidalController implements TidalController {
|
||||
@@ -9,15 +10,20 @@ export class MediaSessionTidalController implements TidalController {
|
||||
constructor() {
|
||||
this.domMediaController = new DomTidalController();
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
onMediaInfoUpdate(callback: (state: Partial<MediaInfo>) => void): void {
|
||||
globalThis.alert("method not implemented");
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
bootstrap(): void {
|
||||
globalThis.alert("Method not implemented: ");
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
// example of using the original domMediaController as a fallback
|
||||
goToHome(): void {
|
||||
this.domMediaController.goToHome();
|
||||
}
|
||||
|
||||
setPlayStatus(status: MediaStatus): void {
|
||||
globalThis.alert("Method not implemented: " + status);
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
getDuration(): string {
|
||||
globalThis.alert("Method not implemented");
|
||||
throw new Error("Method not implemented.");
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { MediaInfo } from "../models/mediaInfo";
|
||||
import { MediaStatus } from "../models/mediaStatus";
|
||||
import { RepeatState } from "../models/repeatState";
|
||||
|
||||
export interface TidalController {
|
||||
export interface TidalController<TBootstrapOptions = object> {
|
||||
goToHome(): void;
|
||||
openSettings(): void;
|
||||
|
||||
@@ -20,6 +21,17 @@ export interface TidalController {
|
||||
previous(): void;
|
||||
toggleShuffle(): void;
|
||||
|
||||
/**
|
||||
* Optional setup/startup/bootstrap for this controller
|
||||
*/
|
||||
bootstrap(options: TBootstrapOptions): void;
|
||||
|
||||
/**
|
||||
* Method that triggers every time the MediaInfo updates
|
||||
* @param callback function that receives the updated media info
|
||||
*/
|
||||
onMediaInfoUpdate(callback: (state: Partial<MediaInfo>) => void): void;
|
||||
|
||||
/**
|
||||
* Update the current status of tidal (e.g playing or paused)
|
||||
*/
|
||||
@@ -37,11 +49,5 @@ export interface TidalController {
|
||||
getArtistsString(): string;
|
||||
getPlayingFrom(): string;
|
||||
getSongIcon(): string;
|
||||
|
||||
isFavorite(): boolean;
|
||||
// add an observable to react on instead of a hookup function
|
||||
// onMediaChange(): any;
|
||||
|
||||
// this can probably be removed after ^
|
||||
setPlayStatus(status: MediaStatus): void;
|
||||
}
|
||||
|
Reference in New Issue
Block a user