mirror of
https://github.com/Mastermindzh/tidal-hifi.git
synced 2025-08-05 12:26:41 +02:00
feat: reworked the api, added duration/current in seconds + shuffle & repeat
This commit is contained in:
20
src/features/api/features/current.ts
Normal file
20
src/features/api/features/current.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Request, Response, Router } from "express";
|
||||
import fs from "fs";
|
||||
import { mediaInfo } from "../../../scripts/mediaInfo";
|
||||
|
||||
export const addCurrentInfo = (expressApp: Router) => {
|
||||
expressApp.get("/current", (req, res) => res.json({ ...mediaInfo, artist: mediaInfo.artists }));
|
||||
expressApp.get("/current/image", getCurrentImage);
|
||||
};
|
||||
|
||||
export const getCurrentImage = (req: Request, res: Response) => {
|
||||
const stream = fs.createReadStream(mediaInfo.icon);
|
||||
stream.on("open", function () {
|
||||
res.set("Content-Type", "image/png");
|
||||
stream.pipe(res);
|
||||
});
|
||||
stream.on("error", function () {
|
||||
res.set("Content-Type", "text/plain");
|
||||
res.status(404).end("Not found");
|
||||
});
|
||||
};
|
36
src/features/api/features/player.ts
Normal file
36
src/features/api/features/player.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { BrowserWindow } from "electron";
|
||||
import { Router } from "express";
|
||||
import { globalEvents } from "../../../constants/globalEvents";
|
||||
import { settings } from "../../../constants/settings";
|
||||
import { MediaStatus } from "../../../models/mediaStatus";
|
||||
import { mediaInfo } from "../../../scripts/mediaInfo";
|
||||
import { settingsStore } from "../../../scripts/settings";
|
||||
import { handleWindowEvent } from "../helpers/handleWindowEvent";
|
||||
|
||||
export const addPlaybackControl = (expressApp: Router, mainWindow: BrowserWindow) => {
|
||||
const windowEvent = handleWindowEvent(mainWindow);
|
||||
const createRoute = (route: string) => `/player${route}`;
|
||||
|
||||
const createPlayerAction = (route: string, action: string) => {
|
||||
expressApp.post(createRoute(route), (req, res) => windowEvent(res, action));
|
||||
};
|
||||
|
||||
if (settingsStore.get(settings.playBackControl)) {
|
||||
createPlayerAction("/play", globalEvents.play);
|
||||
createPlayerAction("/favorite/toggle", globalEvents.toggleFavorite);
|
||||
createPlayerAction("/pause", globalEvents.pause);
|
||||
createPlayerAction("/next", globalEvents.next);
|
||||
createPlayerAction("/previous", globalEvents.previous);
|
||||
createPlayerAction("/shuffle/toggle", globalEvents.toggleShuffle);
|
||||
createPlayerAction("/repeat/toggle", globalEvents.toggleRepeat);
|
||||
|
||||
expressApp.post(createRoute("/playpause"), (req, res) => {
|
||||
if (mediaInfo.status === MediaStatus.playing) {
|
||||
windowEvent(res, globalEvents.pause);
|
||||
} else {
|
||||
windowEvent(res, globalEvents.play);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
12
src/features/api/helpers/handleWindowEvent.ts
Normal file
12
src/features/api/helpers/handleWindowEvent.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { BrowserWindow } from "electron";
|
||||
import { Response } from "express";
|
||||
|
||||
/**
|
||||
* Shorthand to handle a fire and forget global event
|
||||
* @param {*} res
|
||||
* @param {*} action
|
||||
*/
|
||||
export const handleWindowEvent = (mainWindow: BrowserWindow) => (res: Response, action: string) => {
|
||||
mainWindow.webContents.send("globalEvent", action);
|
||||
res.sendStatus(200);
|
||||
};
|
31
src/features/api/index.ts
Normal file
31
src/features/api/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { BrowserWindow, dialog } from "electron";
|
||||
import express from "express";
|
||||
import { settings } from "../../constants/settings";
|
||||
import { settingsStore } from "../../scripts/settings";
|
||||
import { addCurrentInfo } from "./features/current";
|
||||
import { addPlaybackControl } from "./features/player";
|
||||
import { addLegacyApi } from "./legacy";
|
||||
|
||||
/**
|
||||
* Function to enable TIDAL Hi-Fi's express api
|
||||
*/
|
||||
export const startApi = (mainWindow: BrowserWindow) => {
|
||||
const expressApp = express();
|
||||
expressApp.get("/", (req, res) => res.send("Hello World!"));
|
||||
|
||||
// add features
|
||||
addLegacyApi(expressApp, mainWindow);
|
||||
addPlaybackControl(expressApp, mainWindow);
|
||||
addCurrentInfo(expressApp);
|
||||
|
||||
const port = settingsStore.get<string, number>(settings.apiSettings.port);
|
||||
const expressInstance = expressApp.listen(port, "127.0.0.1");
|
||||
expressInstance.on("error", function (e: { code: string }) {
|
||||
let message = e.code;
|
||||
if (e.code === "EADDRINUSE") {
|
||||
message = `Port ${port} in use.`;
|
||||
}
|
||||
|
||||
dialog.showErrorBox("Api failed to start.", message);
|
||||
});
|
||||
};
|
47
src/features/api/legacy.ts
Normal file
47
src/features/api/legacy.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { BrowserWindow } from "electron";
|
||||
import { Response, Router } from "express";
|
||||
import { globalEvents } from "../../constants/globalEvents";
|
||||
import { settings } from "../../constants/settings";
|
||||
import { MediaStatus } from "../../models/mediaStatus";
|
||||
import { mediaInfo } from "../../scripts/mediaInfo";
|
||||
import { settingsStore } from "../../scripts/settings";
|
||||
import { getCurrentImage } from "./features/current";
|
||||
|
||||
/**
|
||||
* The legacy API, this will not be maintained and probably has duplicate code :)
|
||||
* @param expressApp
|
||||
* @param mainWindow
|
||||
*/
|
||||
export const addLegacyApi = (expressApp: Router, mainWindow: BrowserWindow) => {
|
||||
expressApp.get("/image", getCurrentImage);
|
||||
|
||||
if (settingsStore.get(settings.playBackControl)) {
|
||||
addLegacyControls();
|
||||
}
|
||||
function addLegacyControls() {
|
||||
expressApp.get("/play", ({ res }) => handleGlobalEvent(res, globalEvents.play));
|
||||
expressApp.post("/favorite/toggle", (req, res) =>
|
||||
handleGlobalEvent(res, globalEvents.toggleFavorite)
|
||||
);
|
||||
expressApp.get("/pause", (req, res) => handleGlobalEvent(res, globalEvents.pause));
|
||||
expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next));
|
||||
expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous));
|
||||
expressApp.get("/playpause", (req, res) => {
|
||||
if (mediaInfo.status === MediaStatus.playing) {
|
||||
handleGlobalEvent(res, globalEvents.pause);
|
||||
} else {
|
||||
handleGlobalEvent(res, globalEvents.play);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand to handle a fire and forget global event
|
||||
* @param {*} res
|
||||
* @param {*} action
|
||||
*/
|
||||
function handleGlobalEvent(res: Response, action: string) {
|
||||
mainWindow.webContents.send("globalEvent", action);
|
||||
res.sendStatus(200);
|
||||
}
|
||||
};
|
14
src/features/time/parse.ts
Normal file
14
src/features/time/parse.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Convert a HH:MM:SS string (or variants such as MM:SS or SS) to plain seconds
|
||||
* @param duration in HH:MM:SS format
|
||||
* @returns number of seconds in duration
|
||||
*/
|
||||
export const convertDurationToSeconds = (duration: string) => {
|
||||
return duration
|
||||
.split(":")
|
||||
.reverse()
|
||||
.map((val) => Number(val))
|
||||
.reduce((previous, current, index) => {
|
||||
return index === 0 ? current : previous + current * Math.pow(60, index);
|
||||
}, 0);
|
||||
};
|
Reference in New Issue
Block a user