mirror of
https://github.com/Mastermindzh/tidal-hifi.git
synced 2024-11-21 21:13:00 +01:00
commit
c6dff0b0e5
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -1,10 +1,16 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"Brainz",
|
||||||
|
"Castlabs",
|
||||||
"flac",
|
"flac",
|
||||||
|
"Flatpak",
|
||||||
"geqnfr",
|
"geqnfr",
|
||||||
"hifi",
|
"hifi",
|
||||||
|
"listenbrainz",
|
||||||
"playpause",
|
"playpause",
|
||||||
"rescrobbler",
|
"rescrobbler",
|
||||||
|
"scrobble",
|
||||||
|
"scrobbling",
|
||||||
"Songwhip",
|
"Songwhip",
|
||||||
"trackid",
|
"trackid",
|
||||||
"tracklist",
|
"tracklist",
|
||||||
|
@ -4,6 +4,10 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## 5.5.0
|
||||||
|
|
||||||
|
- ListenBrainz integration added (thanks @Mar0xy)
|
||||||
|
|
||||||
## 5.4.0
|
## 5.4.0
|
||||||
|
|
||||||
- Removed Windows builds (from publishes) as they don't work anymore.
|
- Removed Windows builds (from publishes) as they don't work anymore.
|
||||||
|
@ -48,6 +48,7 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect
|
|||||||
- Custom [integrations](#integrations)
|
- Custom [integrations](#integrations)
|
||||||
- [Settings feature](./docs/images/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`)
|
- [Settings feature](./docs/images/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`)
|
||||||
- AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847))
|
- AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847))
|
||||||
|
- [ListenBrainz](https://listenbrainz.org/?redirect=false) integration
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
@ -133,7 +134,7 @@ To install and work with the code on this project follow these steps:
|
|||||||
|
|
||||||
## Integrations
|
## Integrations
|
||||||
|
|
||||||
Tidal-hifi comes with several integrations out of the box.
|
tidal-hifi comes with several integrations out of the box.
|
||||||
You can find these in the settings menu (`ctrl + =` by default) under the "integrations" tab.
|
You can find these in the settings menu (`ctrl + =` by default) under the "integrations" tab.
|
||||||
|
|
||||||
![integrations menu, showing a list of integrations](./docs/images/integrations.png)
|
![integrations menu, showing a list of integrations](./docs/images/integrations.png)
|
||||||
@ -153,11 +154,11 @@ Not included:
|
|||||||
|
|
||||||
The last.fm login doesn't work, as is evident from the following issue: [Last.fm login doesn't work](https://github.com/Mastermindzh/tidal-hifi/issues/4).
|
The last.fm login doesn't work, as is evident from the following issue: [Last.fm login doesn't work](https://github.com/Mastermindzh/tidal-hifi/issues/4).
|
||||||
However, in that same issue you can read about a workaround using [rescrobbler](https://github.com/InputUsername/rescrobbled).
|
However, in that same issue you can read about a workaround using [rescrobbler](https://github.com/InputUsername/rescrobbled).
|
||||||
For now that will be the default workaround.
|
For now, that will be the default workaround.
|
||||||
|
|
||||||
#### DRM not working on Windows
|
#### DRM not working on Windows
|
||||||
|
|
||||||
Most Windows users run into DRM issues when trying to use Tidal-hifi.
|
Most Windows users run into DRM issues when trying to use tidal-hifi.
|
||||||
Nothing I can do about that I'm afraid... Tidal is working on removing/changing DRM so when they finish with that we can give it another shot.
|
Nothing I can do about that I'm afraid... Tidal is working on removing/changing DRM so when they finish with that we can give it another shot.
|
||||||
|
|
||||||
## Special thanks to
|
## Special thanks to
|
||||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "tidal-hifi",
|
"name": "tidal-hifi",
|
||||||
"version": "5.4.0",
|
"version": "5.5.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "tidal-hifi",
|
"name": "tidal-hifi",
|
||||||
"version": "5.4.0",
|
"version": "5.5.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/remote": "^2.0.10",
|
"@electron/remote": "^2.0.10",
|
||||||
@ -9340,4 +9340,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tidal-hifi",
|
"name": "tidal-hifi",
|
||||||
"version": "5.4.0",
|
"version": "5.5.0",
|
||||||
"description": "Tidal on Electron with widevine(hifi) support",
|
"description": "Tidal on Electron with widevine(hifi) support",
|
||||||
"main": "ts-dist/main.js",
|
"main": "ts-dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -72,4 +72,4 @@
|
|||||||
"typescript": "^5.1.6"
|
"typescript": "^5.1.6"
|
||||||
},
|
},
|
||||||
"prettier": "@mastermindzh/prettier-config"
|
"prettier": "@mastermindzh/prettier-config"
|
||||||
}
|
}
|
@ -20,6 +20,12 @@ export const settings = {
|
|||||||
disableHardwareMediaKeys: "disableHardwareMediaKeys",
|
disableHardwareMediaKeys: "disableHardwareMediaKeys",
|
||||||
enableCustomHotkeys: "enableCustomHotkeys",
|
enableCustomHotkeys: "enableCustomHotkeys",
|
||||||
enableDiscord: "enableDiscord",
|
enableDiscord: "enableDiscord",
|
||||||
|
ListenBrainz: {
|
||||||
|
root: "ListenBrainz",
|
||||||
|
enabled: "ListenBrainz.enabled",
|
||||||
|
api: "ListenBrainz.api",
|
||||||
|
token: "ListenBrainz.token",
|
||||||
|
},
|
||||||
flags: {
|
flags: {
|
||||||
root: "flags",
|
root: "flags",
|
||||||
disableHardwareMediaKeys: "flags.disableHardwareMediaKeys",
|
disableHardwareMediaKeys: "flags.disableHardwareMediaKeys",
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
export const statuses = {
|
|
||||||
playing: "playing",
|
|
||||||
paused: "paused",
|
|
||||||
};
|
|
134
src/features/listenbrainz/listenbrainz.ts
Normal file
134
src/features/listenbrainz/listenbrainz.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { ipcRenderer } from "electron";
|
||||||
|
import Store from "electron-store";
|
||||||
|
import { settings } from "../../constants/settings";
|
||||||
|
import { MediaStatus } from "../../models/mediaStatus";
|
||||||
|
import { settingsStore } from "../../scripts/settings";
|
||||||
|
import { Logger } from "../logger";
|
||||||
|
import { StoreData } from "./models/storeData";
|
||||||
|
|
||||||
|
const ListenBrainzStore = new Store({ name: "listenbrainz" });
|
||||||
|
|
||||||
|
export const ListenBrainzConstants = {
|
||||||
|
oldData: "oldData",
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ListenBrainz {
|
||||||
|
/**
|
||||||
|
* Create the object to store old information in the Store :)
|
||||||
|
* @param title
|
||||||
|
* @param artists
|
||||||
|
* @param duration
|
||||||
|
* @returns data passed along in an object + a "listenedAt" key with the current time
|
||||||
|
*/
|
||||||
|
private static constructStoreData(title: string, artists: string, duration: number): StoreData {
|
||||||
|
return {
|
||||||
|
listenedAt: Math.floor(new Date().getTime() / 1000),
|
||||||
|
title,
|
||||||
|
artists,
|
||||||
|
duration,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call the ListenBrainz API and create playing now payload and scrobble old song
|
||||||
|
* @param title
|
||||||
|
* @param artists
|
||||||
|
* @param status
|
||||||
|
* @param duration
|
||||||
|
*/
|
||||||
|
public static async scrobble(
|
||||||
|
title: string,
|
||||||
|
artists: string,
|
||||||
|
status: string,
|
||||||
|
duration: number
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (status === MediaStatus.paused) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Fetches the oldData required for scrobbling and proceeds to construct a playing_now data payload for the Playing Now area
|
||||||
|
const oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData;
|
||||||
|
const playing_data = {
|
||||||
|
listen_type: "playing_now",
|
||||||
|
payload: [
|
||||||
|
{
|
||||||
|
track_metadata: {
|
||||||
|
additional_info: {
|
||||||
|
media_player: "Tidal Hi-Fi",
|
||||||
|
submission_client: "Tidal Hi-Fi",
|
||||||
|
music_service: "tidal.com",
|
||||||
|
duration: duration,
|
||||||
|
},
|
||||||
|
artist_name: artists,
|
||||||
|
track_name: title,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
await axios.post(
|
||||||
|
`${settingsStore.get<string, string>(settings.ListenBrainz.api)}/1/submit-listens`,
|
||||||
|
playing_data,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Token ${settingsStore.get<string, string>(
|
||||||
|
settings.ListenBrainz.token
|
||||||
|
)}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!oldData) {
|
||||||
|
ListenBrainzStore.set(
|
||||||
|
ListenBrainzConstants.oldData,
|
||||||
|
this.constructStoreData(title, artists, duration)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (oldData.title !== title) {
|
||||||
|
// This constructs the data required to scrobble the data after the song finishes
|
||||||
|
const scrobble_data = {
|
||||||
|
listen_type: "single",
|
||||||
|
payload: [
|
||||||
|
{
|
||||||
|
listened_at: oldData.listenedAt,
|
||||||
|
track_metadata: {
|
||||||
|
additional_info: {
|
||||||
|
media_player: "Tidal Hi-Fi",
|
||||||
|
submission_client: "Tidal Hi-Fi",
|
||||||
|
music_service: "listen.tidal.com",
|
||||||
|
duration: oldData.duration,
|
||||||
|
},
|
||||||
|
artist_name: oldData.artists,
|
||||||
|
track_name: oldData.title,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
await axios.post(
|
||||||
|
`${settingsStore.get<string, string>(settings.ListenBrainz.api)}/1/submit-listens`,
|
||||||
|
scrobble_data,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Token ${settingsStore.get<string, string>(
|
||||||
|
settings.ListenBrainz.token
|
||||||
|
)}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ListenBrainzStore.set(
|
||||||
|
ListenBrainzConstants.oldData,
|
||||||
|
this.constructStoreData(title, artists, duration)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const logger = new Logger(ipcRenderer);
|
||||||
|
logger.log(JSON.stringify(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ListenBrainzStore };
|
9
src/features/listenbrainz/models/storeData.ts
Normal file
9
src/features/listenbrainz/models/storeData.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Data saved for ListenBrainz
|
||||||
|
*/
|
||||||
|
export interface StoreData {
|
||||||
|
listenedAt: number;
|
||||||
|
title: string;
|
||||||
|
artists: string;
|
||||||
|
duration: number;
|
||||||
|
}
|
@ -26,8 +26,7 @@ export class Logger {
|
|||||||
public log(content: string, object: object = {}) {
|
public log(content: string, object: object = {}) {
|
||||||
if (this.ipcRenderer) {
|
if (this.ipcRenderer) {
|
||||||
this.ipcRenderer.send(globalEvents.log, { content, object });
|
this.ipcRenderer.send(globalEvents.log, { content, object });
|
||||||
} else {
|
|
||||||
console.log(`${content} \n ${JSON.stringify(object, null, 2)}`);
|
|
||||||
}
|
}
|
||||||
|
console.log(`${content} \n ${JSON.stringify(object, null, 2)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ function setFlags() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the menuBarVisbility according to the store value
|
* Update the menuBarVisibility according to the store value
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function syncMenuBarWithStore() {
|
function syncMenuBarWithStore() {
|
||||||
|
@ -25,7 +25,11 @@ let adBlock: HTMLInputElement,
|
|||||||
skippedArtists: HTMLInputElement,
|
skippedArtists: HTMLInputElement,
|
||||||
theme: HTMLSelectElement,
|
theme: HTMLSelectElement,
|
||||||
trayIcon: HTMLInputElement,
|
trayIcon: HTMLInputElement,
|
||||||
updateFrequency: HTMLInputElement;
|
updateFrequency: HTMLInputElement,
|
||||||
|
enableListenBrainz: HTMLInputElement,
|
||||||
|
ListenBrainzAPI: HTMLInputElement,
|
||||||
|
ListenBrainzToken: HTMLInputElement;
|
||||||
|
|
||||||
function getThemeFiles() {
|
function getThemeFiles() {
|
||||||
const selectElement = document.getElementById("themesList") as HTMLSelectElement;
|
const selectElement = document.getElementById("themesList") as HTMLSelectElement;
|
||||||
const builtInThemes = getThemeListFromDirectory(process.resourcesPath);
|
const builtInThemes = getThemeListFromDirectory(process.resourcesPath);
|
||||||
@ -87,6 +91,9 @@ function refreshSettings() {
|
|||||||
skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n");
|
skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n");
|
||||||
trayIcon.checked = settingsStore.get(settings.trayIcon);
|
trayIcon.checked = settingsStore.get(settings.trayIcon);
|
||||||
updateFrequency.value = settingsStore.get(settings.updateFrequency);
|
updateFrequency.value = settingsStore.get(settings.updateFrequency);
|
||||||
|
enableListenBrainz.checked = settingsStore.get(settings.ListenBrainz.enabled);
|
||||||
|
ListenBrainzAPI.value = settingsStore.get(settings.ListenBrainz.api);
|
||||||
|
ListenBrainzToken.value = settingsStore.get(settings.ListenBrainz.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -137,6 +144,10 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||||||
} else {
|
} else {
|
||||||
settingsStore.set(key, source.value);
|
settingsStore.set(key, source.value);
|
||||||
}
|
}
|
||||||
|
// Live update the view for ListenBrainz input, hide if disabled/show if enabled
|
||||||
|
if (source.value === "on" && source.id === "enableListenBrainz") {
|
||||||
|
source.checked ? document.getElementById("listenbrainz__options").removeAttribute("hidden") : document.getElementById("listenbrainz__options").setAttribute("hidden", "true");
|
||||||
|
}
|
||||||
ipcRenderer.send(globalEvents.storeChanged);
|
ipcRenderer.send(globalEvents.storeChanged);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -183,8 +194,12 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||||||
skippedArtists = get("skippedArtists");
|
skippedArtists = get("skippedArtists");
|
||||||
singleInstance = get("singleInstance");
|
singleInstance = get("singleInstance");
|
||||||
updateFrequency = get("updateFrequency");
|
updateFrequency = get("updateFrequency");
|
||||||
|
enableListenBrainz = get("enableListenBrainz");
|
||||||
|
ListenBrainzAPI = get("ListenBrainzAPI");
|
||||||
|
ListenBrainzToken = get("ListenBrainzToken");
|
||||||
|
|
||||||
refreshSettings();
|
refreshSettings();
|
||||||
|
enableListenBrainz.checked ? document.getElementById("listenbrainz__options").removeAttribute("hidden") : document.getElementById("listenbrainz__options").setAttribute("hidden", "true");
|
||||||
|
|
||||||
addInputListener(adBlock, settings.adBlock);
|
addInputListener(adBlock, settings.adBlock);
|
||||||
addInputListener(api, settings.api);
|
addInputListener(api, settings.api);
|
||||||
@ -206,4 +221,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||||||
addSelectListener(theme, settings.theme);
|
addSelectListener(theme, settings.theme);
|
||||||
addInputListener(trayIcon, settings.trayIcon);
|
addInputListener(trayIcon, settings.trayIcon);
|
||||||
addInputListener(updateFrequency, settings.updateFrequency);
|
addInputListener(updateFrequency, settings.updateFrequency);
|
||||||
|
addInputListener(enableListenBrainz, settings.ListenBrainz.enabled);
|
||||||
|
addTextAreaListener(ListenBrainzAPI, settings.ListenBrainz.api);
|
||||||
|
addTextAreaListener(ListenBrainzToken, settings.ListenBrainz.token);
|
||||||
});
|
});
|
||||||
|
@ -212,6 +212,35 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="group">
|
||||||
|
<p class="group__title">ListenBrainz</p>
|
||||||
|
<div class="group__option">
|
||||||
|
<div class="group__description">
|
||||||
|
<h4>Enable ListenBrainz</h4>
|
||||||
|
<p>Scrobble your listens directly to ListenBrainz.</p>
|
||||||
|
</div>
|
||||||
|
<label class="switch">
|
||||||
|
<input id="enableListenBrainz" type="checkbox" />
|
||||||
|
<span class="switch__slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div id="listenbrainz__options" hidden="true">
|
||||||
|
<div class="group__option">
|
||||||
|
<div class="group__description">
|
||||||
|
<h4>ListenBrainz API Url</h4>
|
||||||
|
<p>There are multiple instances for ListenBrainz you can set the corresponding API url below.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<textarea id="ListenBrainzAPI" class="textarea" cols="1" rows="1" spellcheck="false"></textarea>
|
||||||
|
<div class="group__option">
|
||||||
|
<div class="group__description">
|
||||||
|
<h4>ListenBrainz User Token</h4>
|
||||||
|
<p>Provide the user token you can get from the settings page.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<textarea id="ListenBrainzToken" class="textarea" cols="1" rows="1" spellcheck="false"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="advanced-section" class="tabs__section">
|
<section id="advanced-section" class="tabs__section">
|
||||||
|
@ -4,19 +4,25 @@ import fs from "fs";
|
|||||||
import Player from "mpris-service";
|
import Player from "mpris-service";
|
||||||
import { globalEvents } from "./constants/globalEvents";
|
import { globalEvents } from "./constants/globalEvents";
|
||||||
import { settings } from "./constants/settings";
|
import { settings } from "./constants/settings";
|
||||||
import { statuses } from "./constants/statuses";
|
|
||||||
import { Songwhip } from "./features/songwhip/songwhip";
|
import { Songwhip } from "./features/songwhip/songwhip";
|
||||||
|
import {
|
||||||
|
ListenBrainz,
|
||||||
|
ListenBrainzConstants,
|
||||||
|
ListenBrainzStore,
|
||||||
|
} from "./features/listenbrainz/listenbrainz";
|
||||||
import { Options } from "./models/options";
|
import { Options } from "./models/options";
|
||||||
import { downloadFile } from "./scripts/download";
|
import { downloadFile } from "./scripts/download";
|
||||||
import { addHotkey } from "./scripts/hotkeys";
|
import { addHotkey } from "./scripts/hotkeys";
|
||||||
import { settingsStore } from "./scripts/settings";
|
import { settingsStore } from "./scripts/settings";
|
||||||
import { setTitle } from "./scripts/window-functions";
|
import { setTitle } from "./scripts/window-functions";
|
||||||
|
import { StoreData } from "./features/listenbrainz/models/storeData";
|
||||||
|
import { MediaStatus } from "./models/mediaStatus";
|
||||||
|
|
||||||
const notificationPath = `${app.getPath("userData")}/notification.jpg`;
|
const notificationPath = `${app.getPath("userData")}/notification.jpg`;
|
||||||
const appName = "Tidal Hifi";
|
const appName = "Tidal Hifi";
|
||||||
let currentSong = "";
|
let currentSong = "";
|
||||||
let player: Player;
|
let player: Player;
|
||||||
let currentPlayStatus = statuses.paused;
|
let currentPlayStatus = MediaStatus.paused;
|
||||||
|
|
||||||
const elements = {
|
const elements = {
|
||||||
play: '*[data-test="play"]',
|
play: '*[data-test="play"]',
|
||||||
@ -105,7 +111,7 @@ const elements = {
|
|||||||
window.location.href.includes("/playlist/") ||
|
window.location.href.includes("/playlist/") ||
|
||||||
window.location.href.includes("/mix/")
|
window.location.href.includes("/mix/")
|
||||||
) {
|
) {
|
||||||
if (currentPlayStatus === statuses.playing) {
|
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.
|
// 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
|
// 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);
|
const row = window.document.querySelector(this.currentlyPlaying).closest(this.mediaItem);
|
||||||
@ -178,7 +184,7 @@ function addCustomCss() {
|
|||||||
* make sure it returns a number, if not use the default
|
* make sure it returns a number, if not use the default
|
||||||
*/
|
*/
|
||||||
function getUpdateFrequency() {
|
function getUpdateFrequency() {
|
||||||
const storeValue = settingsStore.get(settings.updateFrequency) as number;
|
const storeValue = settingsStore.get<string, number>(settings.updateFrequency);
|
||||||
const defaultValue = 500;
|
const defaultValue = 500;
|
||||||
|
|
||||||
if (!isNaN(storeValue)) {
|
if (!isNaN(storeValue)) {
|
||||||
@ -201,6 +207,11 @@ function playPause() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the old listenbrainz data on launch
|
||||||
|
*/
|
||||||
|
ListenBrainzStore.clear();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add hotkeys for when tidal is focused
|
* Add hotkeys for when tidal is focused
|
||||||
* Reflects the desktop hotkeys found on:
|
* Reflects the desktop hotkeys found on:
|
||||||
@ -274,7 +285,7 @@ function handleLogout() {
|
|||||||
defaultId: 2,
|
defaultId: 2,
|
||||||
})
|
})
|
||||||
.then((result: { response: number }) => {
|
.then((result: { response: number }) => {
|
||||||
if (logoutOptions.indexOf("Yes, please") == result.response) {
|
if (logoutOptions.indexOf("Yes, please") === result.response) {
|
||||||
for (let i = 0; i < window.localStorage.length; i++) {
|
for (let i = 0; i < window.localStorage.length; i++) {
|
||||||
const key = window.localStorage.key(i);
|
const key = window.localStorage.key(i);
|
||||||
if (key.startsWith("_TIDAL_activeSession")) {
|
if (key.startsWith("_TIDAL_activeSession")) {
|
||||||
@ -316,6 +327,8 @@ function addIPCEventListeners() {
|
|||||||
case globalEvents.pause:
|
case globalEvents.pause:
|
||||||
elements.click("pause");
|
elements.click("pause");
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -330,9 +343,9 @@ function getCurrentlyPlayingStatus() {
|
|||||||
|
|
||||||
// if pause button is visible tidal is playing
|
// if pause button is visible tidal is playing
|
||||||
if (pause) {
|
if (pause) {
|
||||||
status = statuses.playing;
|
status = MediaStatus.playing;
|
||||||
} else {
|
} else {
|
||||||
status = statuses.paused;
|
status = MediaStatus.paused;
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@ -357,19 +370,41 @@ function updateMediaInfo(options: Options, notify: boolean) {
|
|||||||
if (settingsStore.get(settings.notifications) && notify) {
|
if (settingsStore.get(settings.notifications) && notify) {
|
||||||
new Notification({ title: options.title, body: options.artists, icon: options.icon }).show();
|
new Notification({ title: options.title, body: options.artists, icon: options.icon }).show();
|
||||||
}
|
}
|
||||||
if (player) {
|
updateMpris(options);
|
||||||
player.metadata = {
|
updateListenBrainz(options);
|
||||||
...player.metadata,
|
}
|
||||||
...{
|
}
|
||||||
"xesam:title": options.title,
|
|
||||||
"xesam:artist": [options.artists],
|
function updateMpris(options: Options) {
|
||||||
"xesam:album": options.album,
|
if (player) {
|
||||||
"mpris:artUrl": options.image,
|
player.metadata = {
|
||||||
"mpris:length": convertDuration(options.duration) * 1000 * 1000,
|
...player.metadata,
|
||||||
"mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(),
|
...{
|
||||||
},
|
"xesam:title": options.title,
|
||||||
};
|
"xesam:artist": [options.artists],
|
||||||
player.playbackStatus = options.status == statuses.paused ? "Paused" : "Playing";
|
"xesam:album": options.album,
|
||||||
|
"mpris:artUrl": options.image,
|
||||||
|
"mpris:length": convertDuration(options.duration) * 1000 * 1000,
|
||||||
|
"mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
player.playbackStatus = options.status === MediaStatus.paused ? "Paused" : "Playing";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateListenBrainz(options: Options) {
|
||||||
|
if (settingsStore.get(settings.ListenBrainz.enabled)) {
|
||||||
|
const oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData;
|
||||||
|
if (
|
||||||
|
(!oldData && options.status === MediaStatus.playing) ||
|
||||||
|
(oldData && oldData.title !== options.title)
|
||||||
|
) {
|
||||||
|
ListenBrainz.scrobble(
|
||||||
|
options.title,
|
||||||
|
options.artists,
|
||||||
|
options.status,
|
||||||
|
convertDuration(options.duration)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { BrowserWindow, dialog } from "electron";
|
import { BrowserWindow, dialog } from "electron";
|
||||||
import express, { Response } from "express";
|
import express, { Response } from "express";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import { settings } from "../constants/settings";
|
||||||
|
import { MediaStatus } from "../models/mediaStatus";
|
||||||
import { globalEvents } from "./../constants/globalEvents";
|
import { globalEvents } from "./../constants/globalEvents";
|
||||||
import { statuses } from "./../constants/statuses";
|
|
||||||
import { mediaInfo } from "./mediaInfo";
|
import { mediaInfo } from "./mediaInfo";
|
||||||
import { settingsStore } from "./settings";
|
import { settingsStore } from "./settings";
|
||||||
import { settings } from "../constants/settings";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to enable tidal-hifi's express api
|
* Function to enable tidal-hifi's express api
|
||||||
@ -44,7 +44,7 @@ export const startExpress = (mainWindow: BrowserWindow) => {
|
|||||||
expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next));
|
expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next));
|
||||||
expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous));
|
expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous));
|
||||||
expressApp.get("/playpause", (req, res) => {
|
expressApp.get("/playpause", (req, res) => {
|
||||||
if (mediaInfo.status == statuses.playing) {
|
if (mediaInfo.status === MediaStatus.playing) {
|
||||||
handleGlobalEvent(res, globalEvents.pause);
|
handleGlobalEvent(res, globalEvents.pause);
|
||||||
} else {
|
} else {
|
||||||
handleGlobalEvent(res, globalEvents.play);
|
handleGlobalEvent(res, globalEvents.play);
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { MediaInfo } from "../models/mediaInfo";
|
import { MediaInfo } from "../models/mediaInfo";
|
||||||
import { statuses } from "./../constants/statuses";
|
import { MediaStatus } from "../models/mediaStatus";
|
||||||
|
|
||||||
export const mediaInfo = {
|
export const mediaInfo = {
|
||||||
title: "",
|
title: "",
|
||||||
artists: "",
|
artists: "",
|
||||||
album: "",
|
album: "",
|
||||||
icon: "",
|
icon: "",
|
||||||
status: statuses.paused,
|
status: MediaStatus.paused as string,
|
||||||
url: "",
|
url: "",
|
||||||
current: "",
|
current: "",
|
||||||
duration: "",
|
duration: "",
|
||||||
|
@ -18,6 +18,11 @@ export const settingsStore = new Store({
|
|||||||
disableHardwareMediaKeys: false,
|
disableHardwareMediaKeys: false,
|
||||||
enableCustomHotkeys: false,
|
enableCustomHotkeys: false,
|
||||||
enableDiscord: false,
|
enableDiscord: false,
|
||||||
|
ListenBrainz: {
|
||||||
|
enabled: false,
|
||||||
|
api: "https://api.listenbrainz.org",
|
||||||
|
token: "",
|
||||||
|
},
|
||||||
flags: {
|
flags: {
|
||||||
gpuRasterization: true,
|
gpuRasterization: true,
|
||||||
disableHardwareMediaKeys: false,
|
disableHardwareMediaKeys: false,
|
||||||
|
Loading…
Reference in New Issue
Block a user