Compare commits

..

19 Commits

Author SHA1 Message Date
f95f13b44a chore: doc fix 2024-05-05 20:35:58 +02:00
f911564d8a chore: version increase 2024-05-05 20:34:58 +02:00
db8a2c2741 feat: reworked the api, added duration/current in seconds + shuffle & repeat 2024-05-05 20:12:10 +02:00
000853414e feat: switched to TIDAL's universal link format in the entire app 2024-05-05 15:09:54 +02:00
53603c4cad
Merge pull request #392 from TheRockYT/MediaSessionService
Media session service
2024-05-05 14:25:02 +02:00
TheRockYT
04ec850005
Merge branch 'Mastermindzh:master' into MediaSessionService 2024-05-01 20:55:39 +02:00
943d9b5bd8
Merge pull request #386 from Mastermindzh/snyk-upgrade-2399381ec4ed8a254535fddca6093bb9
[Snyk] Upgrade sass from 1.72.0 to 1.74.1
2024-04-25 10:26:22 +02:00
snyk-bot
755816c2b8
fix: upgrade sass from 1.72.0 to 1.74.1
Snyk has created this PR to upgrade sass from 1.72.0 to 1.74.1.

See this package in npm:
https://www.npmjs.com/package/sass

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-25 04:29:39 +00:00
6e5024742a
Merge pull request #382 from Mastermindzh/snyk-upgrade-e8583a1804fc58a60a199bdcdfddb237
[Snyk] Upgrade sass from 1.71.1 to 1.72.0
2024-04-18 12:34:41 +02:00
417afaab85
Merge pull request #381 from Mastermindzh/snyk-upgrade-eddeada86cf707578042602e7a9acfd4
[Snyk] Upgrade electron-store from 8.1.0 to 8.2.0
2024-04-18 12:34:27 +02:00
d225c0056b
Merge pull request #380 from Mastermindzh/snyk-upgrade-636acae2850bc80d0d46d524748be780
[Snyk] Upgrade axios from 1.6.5 to 1.6.8
2024-04-18 12:34:18 +02:00
snyk-bot
a75b0336db
fix: upgrade sass from 1.71.1 to 1.72.0
Snyk has created this PR to upgrade sass from 1.71.1 to 1.72.0.

See this package in npm:
https://www.npmjs.com/package/sass

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-16 20:25:03 +00:00
snyk-bot
29465ce13a
fix: upgrade electron-store from 8.1.0 to 8.2.0
Snyk has created this PR to upgrade electron-store from 8.1.0 to 8.2.0.

See this package in npm:
https://www.npmjs.com/package/electron-store

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-16 20:25:00 +00:00
snyk-bot
d333047269
fix: upgrade axios from 1.6.5 to 1.6.8
Snyk has created this PR to upgrade axios from 1.6.5 to 1.6.8.

See this package in npm:
https://www.npmjs.com/package/axios

See this project in Snyk:
https://app.snyk.io/org/mastermindzh/project/dade8f03-2064-49a3-8957-edbacec3887c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-16 20:24:56 +00:00
TheRockYT
712330f8f1 Enable MediaSessionService flag to allow listen.tidal.com to control it. 2024-04-04 23:08:18 +02:00
TheRockYT
84fd35ce0e Remove implementation of global shortcuts for media control. 2024-04-04 23:06:29 +02:00
TheRockYT
326038f262 Remove custom implementation of MediaSessionService. It is disabled anyway. 2024-04-04 23:05:09 +02:00
a6c1d35a60
Merge pull request #375 from Mastermindzh/snyk-fix-a6b2d7614f87b9d818d7aaf7e6d7650d
[Snyk] Security upgrade express from 4.18.3 to 4.19.2
2024-03-28 14:09:29 +01:00
snyk-bot
c09a4bc4a8
fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-EXPRESS-6474509
2024-03-26 21:37:31 +00:00
19 changed files with 255 additions and 158 deletions

View File

@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [5.11.0]
- Re-implemented the API, added support for duration/current in seconds & shuffle+repeat
- made the original API "legacy" (still works the same)
- Now using the correct HTTP verb for all new endpoints
- Implemented TIDAL's universal links. All links are now universal.
- Custom `tidal://` protocol fixed - By [TheRockYT](https://github.com/TheRockYT)
- Global media shortcuts removed since TIDAL includes them by default - By [TheRockYT](https://github.com/TheRockYT)
- Fixes
- [#390](https://github.com/Mastermindzh/tidal-hifi/issues/390)
- [#376](https://github.com/Mastermindzh/tidal-hifi/issues/376)
- [#383](https://github.com/Mastermindzh/tidal-hifi/issues/383)
- [#393](https://github.com/Mastermindzh/tidal-hifi/issues/393)
## [5.10.0]
- TIDAL will now close the previous notification if a new one is sent whilst the old is still visible. [#364](https://github.com/Mastermindzh/tidal-hifi/pull/364)

48
package-lock.json generated
View File

@ -1,23 +1,23 @@
{
"name": "tidal-hifi",
"version": "5.10.0",
"version": "5.11.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tidal-hifi",
"version": "5.10.0",
"version": "5.11.0",
"license": "MIT",
"dependencies": {
"@electron/remote": "^2.1.2",
"axios": "^1.6.5",
"axios": "^1.6.8",
"discord-rpc": "^4.0.1",
"electron-store": "^8.1.0",
"express": "^4.18.3",
"electron-store": "^8.2.0",
"express": "^4.19.2",
"hotkeys-js": "^3.13.7",
"mpris-service": "^2.1.2",
"request": "^2.88.2",
"sass": "^1.71.1"
"sass": "^1.74.1"
},
"devDependencies": {
"@mastermindzh/prettier-config": "^1.0.0",
@ -1890,11 +1890,11 @@
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg=="
},
"node_modules/axios": {
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz",
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
"dependencies": {
"follow-redirects": "^1.15.4",
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@ -2620,9 +2620,9 @@
}
},
"node_modules/cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": {
"node": ">= 0.6"
}
@ -3429,9 +3429,9 @@
}
},
"node_modules/electron-store": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/electron-store/-/electron-store-8.1.0.tgz",
"integrity": "sha512-2clHg/juMjOH0GT9cQ6qtmIvK183B39ZXR0bUoPwKwYHJsEF3quqyDzMFUAu+0OP8ijmN2CbPRAelhNbWUbzwA==",
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/electron-store/-/electron-store-8.2.0.tgz",
"integrity": "sha512-ukLL5Bevdil6oieAOXz3CMy+OgaItMiVBg701MNlG6W5RaC0AHN7rvlqTCmeb6O7jP0Qa1KKYTE0xV0xbhF4Hw==",
"dependencies": {
"conf": "^10.2.0",
"type-fest": "^2.17.0"
@ -3756,16 +3756,16 @@
}
},
"node_modules/express": {
"version": "4.18.3",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz",
"integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@ -7053,9 +7053,9 @@
}
},
"node_modules/sass": {
"version": "1.71.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
"integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==",
"version": "1.74.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.74.1.tgz",
"integrity": "sha512-w0Z9p/rWZWelb88ISOLyvqTWGmtmu2QJICqDBGyNnfG4OUnPX9BBjjYIXUpXCMOOg5MQWNpqzt876la1fsTvUA==",
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@ -8564,4 +8564,4 @@
}
}
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "tidal-hifi",
"version": "5.10.0",
"version": "5.11.0",
"description": "Tidal on Electron with widevine(hifi) support",
"main": "ts-dist/main.js",
"scripts": {
@ -40,14 +40,14 @@
"license": "MIT",
"dependencies": {
"@electron/remote": "^2.1.2",
"axios": "^1.6.5",
"axios": "^1.6.8",
"discord-rpc": "^4.0.1",
"electron-store": "^8.1.0",
"express": "^4.18.3",
"electron-store": "^8.2.0",
"express": "^4.19.2",
"hotkeys-js": "^3.13.7",
"mpris-service": "^2.1.2",
"request": "^2.88.2",
"sass": "^1.71.1"
"sass": "^1.74.1"
},
"devDependencies": {
"@mastermindzh/prettier-config": "^1.0.0",

View File

@ -13,4 +13,6 @@ export const globalEvents = {
whip: "whip",
log: "log",
toggleFavorite: "toggleFavorite",
toggleShuffle: "toggleShuffle",
toggleRepeat: "toggleRepeat",
};

View 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");
});
};

View 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);
}
});
}
};

View 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
View 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);
});
};

View 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);
}
};

View File

@ -9,7 +9,6 @@ import { Logger } from "../logger";
*/
export function setDefaultFlags(app: App) {
setFlag(app, "disable-seccomp-filter-sandbox");
setFlag(app, "disable-features", "MediaSessionService");
}
/**

View 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);
};

View File

@ -1,16 +1,9 @@
import { enable, initialize } from "@electron/remote/main";
import {
app,
BrowserWindow,
components,
globalShortcut,
ipcMain,
session,
} from "electron";
import { BrowserWindow, app, components, ipcMain, session } from "electron";
import path from "path";
import { globalEvents } from "./constants/globalEvents";
import { mediaKeys } from "./constants/mediaKeys";
import { settings } from "./constants/settings";
import { startApi } from "./features/api";
import { setDefaultFlags, setManagedFlagsFromSettings } from "./features/flags/flags";
import {
acquireInhibitorIfInactive,
@ -21,7 +14,6 @@ import { Songwhip } from "./features/songwhip/songwhip";
import { MediaInfo } from "./models/mediaInfo";
import { MediaStatus } from "./models/mediaStatus";
import { initRPC, rpc, unRPC } from "./scripts/discord";
import { startExpress } from "./scripts/express";
import { updateMediaInfo } from "./scripts/mediaInfo";
import { addMenu } from "./scripts/menu";
import {
@ -63,7 +55,7 @@ function syncMenuBarWithStore() {
* @returns true/false based on whether the current window is the main window
*/
function isMainInstance() {
return app.requestSingleInstanceLock();
return app.requestSingleInstanceLock();
}
/**
@ -84,7 +76,7 @@ function getCustomProtocolUrl(args: string[]) {
return null;
}
return tidalUrl + "/" + customProtocolArg.substring(PROTOCOL_PREFIX.length + 3)
return tidalUrl + "/" + customProtocolArg.substring(PROTOCOL_PREFIX.length + 3);
}
function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
@ -111,7 +103,7 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
// find the custom protocol argument
const customProtocolUrl = getCustomProtocolUrl(process.argv);
if(customProtocolUrl) {
if (customProtocolUrl) {
// load the url received from the custom protocol
mainWindow.loadURL(customProtocolUrl);
} else {
@ -162,22 +154,13 @@ function registerHttpProtocols() {
}
}
function addGlobalShortcuts() {
Object.keys(mediaKeys).forEach((key) => {
globalShortcut.register(`${key}`, () => {
mainWindow.webContents.send("globalEvent", `${(mediaKeys as any)[key]}`);
});
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", async () => {
// check if the app is the main instance and multiple instances are not allowed
if (isMainInstance() && !isMultipleInstancesAllowed()) {
app.on('second-instance', (_, commandLine) => {
app.on("second-instance", (_, commandLine) => {
const customProtocolUrl = getCustomProtocolUrl(commandLine);
if (customProtocolUrl) {
@ -206,12 +189,11 @@ app.on("ready", async () => {
createWindow();
addMenu(mainWindow);
createSettingsWindow();
addGlobalShortcuts();
if (settingsStore.get(settings.trayIcon)) {
addTray(mainWindow, { icon });
refreshTray(mainWindow);
}
settingsStore.get(settings.api) && startExpress(mainWindow);
settingsStore.get(settings.api) && startApi(mainWindow);
settingsStore.get(settings.enableDiscord) && initRPC();
} else {
app.quit();

View File

@ -8,7 +8,9 @@ export interface MediaInfo {
status: MediaStatus;
url: string;
current: string;
currentInSeconds?: number;
duration: string;
durationInSeconds?: number;
image: string;
favorite: boolean;
}

View File

@ -5,7 +5,9 @@ export interface Options {
status: string;
url: string;
current: string;
currentInSeconds: number;
duration: string;
durationInSeconds: number;
"app-name": string;
image: string;
icon: string;

View File

@ -433,7 +433,7 @@
<h4>TIDAL Hi-Fi</h4>
<div class="about-section__version">
<a target="_blank" rel="noopener"
href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.10.0">5.10.0</a>
href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.11.0">5.11.0</a>
</div>
<div class="about-section__links">
<a target="_blank" rel="noopener" href="https://github.com/mastermindzh/tidal-hifi/"

View File

@ -12,6 +12,7 @@ import { StoreData } from "./features/listenbrainz/models/storeData";
import { Logger } from "./features/logger";
import { Songwhip } from "./features/songwhip/songwhip";
import { addCustomCss } from "./features/theming/theming";
import { convertDurationToSeconds } from "./features/time/parse";
import { MediaStatus } from "./models/mediaStatus";
import { Options } from "./models/options";
import { downloadFile } from "./scripts/download";
@ -318,6 +319,12 @@ function addIPCEventListeners() {
case globalEvents.toggleFavorite:
elements.click("favorite");
break;
case globalEvents.toggleShuffle:
elements.click("shuffle");
break;
case globalEvents.toggleRepeat:
elements.click("repeat");
break;
default:
break;
}
@ -493,23 +500,6 @@ function getTrackID() {
return window.location;
}
function updateMediaSession(options: Options) {
if ("mediaSession" in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: options.title,
artist: options.artists,
album: options.album,
artwork: [
{
src: options.icon,
sizes: "640x640",
type: "image/png",
},
],
});
}
}
/**
* Watch for song changes and update title + notify
*/
@ -540,7 +530,9 @@ setInterval(function () {
status: currentStatus,
url: getTrackURL(),
current,
currentInSeconds: convertDurationToSeconds(current),
duration,
durationInSeconds: convertDurationToSeconds(duration),
"app-name": appName,
image: "",
icon: "",
@ -574,13 +566,13 @@ setInterval(function () {
}
}).then(() => {
updateMediaInfo(options, titleOrArtistsChanged);
if (titleOrArtistsChanged) {
updateMediaSession(options);
}
});
} else {
// just update the time
updateMediaInfo({ ...currentMediaInfo, ...{ current } }, false);
updateMediaInfo(
{ ...currentMediaInfo, ...{ current, currentInSeconds: convertDurationToSeconds(current) } },
false
);
}
/**

View File

@ -3,18 +3,13 @@ import { app, ipcMain } from "electron";
import { globalEvents } from "../constants/globalEvents";
import { settings } from "../constants/settings";
import { Logger } from "../features/logger";
import { convertDurationToSeconds } from "../features/time/parse";
import { MediaStatus } from "../models/mediaStatus";
import { mediaInfo } from "./mediaInfo";
import { settingsStore } from "./settings";
const clientId = "833617820704440341";
function timeToSeconds(timeArray: string[]) {
const minutes = parseInt(timeArray[0]) * 1;
const seconds = minutes * 60 + parseInt(timeArray[1]) * 1;
return seconds;
}
export let rpc: Client;
const observer = () => {
@ -59,7 +54,7 @@ const getActivity = (): Presence => {
return { includeTimestamps, detailsPrefix, buttonText };
}
function setPresenceFromMediaInfo(detailsPrefix: any, buttonText: any) {
function setPresenceFromMediaInfo(detailsPrefix: string, buttonText: string) {
if (mediaInfo.url) {
presence.details = `${detailsPrefix}${mediaInfo.title}`;
presence.state = mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)";
@ -74,10 +69,10 @@ const getActivity = (): Presence => {
}
}
function includeTimeStamps(includeTimestamps: any) {
function includeTimeStamps(includeTimestamps: boolean) {
if (includeTimestamps) {
const currentSeconds = timeToSeconds(mediaInfo.current.split(":"));
const durationSeconds = timeToSeconds(mediaInfo.duration.split(":"));
const currentSeconds = convertDurationToSeconds(mediaInfo.current);
const durationSeconds = convertDurationToSeconds(mediaInfo.duration);
const date = new Date();
const now = (date.getTime() / 1000) | 0;
const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds));

View File

@ -1,69 +0,0 @@
import { BrowserWindow, dialog } from "electron";
import express, { Response } from "express";
import fs from "fs";
import { settings } from "../constants/settings";
import { MediaStatus } from "../models/mediaStatus";
import { globalEvents } from "./../constants/globalEvents";
import { mediaInfo } from "./mediaInfo";
import { settingsStore } from "./settings";
/**
* Function to enable TIDAL Hi-Fi's express api
*/
// expressModule.run = function (mainWindow)
export const startExpress = (mainWindow: BrowserWindow) => {
/**
* 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);
}
const expressApp = express();
expressApp.get("/", (req, res) => res.send("Hello World!"));
expressApp.get("/current", (req, res) => res.json({ ...mediaInfo, artist: mediaInfo.artists }));
expressApp.get("/image", (req, res) => {
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");
});
});
if (settingsStore.get(settings.playBackControl)) {
expressApp.get("/play", (req, 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);
}
});
}
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);
});
};

View File

@ -9,7 +9,9 @@ export const mediaInfo = {
status: MediaStatus.paused as string,
url: "",
current: "",
currentInSeconds: 0,
duration: "",
durationInSeconds: 0,
image: "tidal-hifi-icon",
favorite: false,
};
@ -19,10 +21,12 @@ export const updateMediaInfo = (arg: MediaInfo) => {
mediaInfo.artists = propOrDefault(arg.artists);
mediaInfo.album = propOrDefault(arg.album);
mediaInfo.icon = propOrDefault(arg.icon);
mediaInfo.url = propOrDefault(arg.url);
mediaInfo.url = toUniversalUrl(propOrDefault(arg.url));
mediaInfo.status = propOrDefault(arg.status);
mediaInfo.current = propOrDefault(arg.current);
mediaInfo.currentInSeconds = arg.currentInSeconds ?? 0;
mediaInfo.duration = propOrDefault(arg.duration);
mediaInfo.durationInSeconds = arg.durationInSeconds ?? 0;
mediaInfo.image = propOrDefault(arg.image);
mediaInfo.favorite = arg.favorite;
};
@ -33,5 +37,18 @@ export const updateMediaInfo = (arg: MediaInfo) => {
* @param {*} defaultValue defaults to ""
*/
function propOrDefault(prop: string, defaultValue = "") {
return prop ? prop : defaultValue;
return prop || defaultValue;
}
/**
* Append the universal link syntax (?u) to any url
* @param url url to append the universal link syntax to
* @returns url with `?u` appended, or the original value of url if falsy
*/
function toUniversalUrl(url: string) {
if (url) {
const queryParamsSet = url.indexOf("?");
return queryParamsSet > -1 ? `${url}&u` : `${url}?u`;
}
return url;
}