diff --git a/CHANGELOG.md b/CHANGELOG.md
index 248b23de3..9fdb26fc1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.9.0]
+
+- More Discord options:
+ - Added the ability to hide the current song from the discord activity and display a custom text instead
+ - Added the ability to customize the text that is shown when no song is playing
+ - Discord now reacts to pausing/unpausing events
+- Refactored media info updates so it only updates the required info, fixes #342, #306
+- Added 5.9.0 logs/versions/migrations
+
+### Fixed
+
+- Fixed chromium mediaSession instance showing up. fixes #338 #198
+- Set a new icon, should fix #302
+- Made sure settingsWindow exists before operating on it. fixes #344
+
## [5.8.0]
- Updated Electron to 28.1.1 (fixes [325](https://github.com/Mastermindzh/tidal-hifi/issues/325))
diff --git a/build/electron-builder.base.yml b/build/electron-builder.base.yml
index 9c4e0b4ef..5465feb51 100644
--- a/build/electron-builder.base.yml
+++ b/build/electron-builder.base.yml
@@ -11,7 +11,7 @@ extraResources:
- "themes/**"
linux:
category: AudioVideo
- icon: assets/icons
+ icon: build/icons/256x256.png
target:
- dir
executableName: tidal-hifi
diff --git a/build/icons/256x256.png b/build/icons/256x256.png
new file mode 100644
index 000000000..bd008f041
Binary files /dev/null and b/build/icons/256x256.png differ
diff --git a/build/icons/icon-inverted.png b/build/icons/icon-inverted.png
new file mode 100644
index 000000000..c3d1e3871
Binary files /dev/null and b/build/icons/icon-inverted.png differ
diff --git a/build/icons/icon.icns b/build/icons/icon.icns
new file mode 100644
index 000000000..5e7587327
Binary files /dev/null and b/build/icons/icon.icns differ
diff --git a/build/icons/icon.png b/build/icons/icon.png
new file mode 100644
index 000000000..0ff0d5b9d
Binary files /dev/null and b/build/icons/icon.png differ
diff --git a/package.json b/package.json
index 8a1d81317..b54ea277b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "tidal-hifi",
- "version": "5.8.0",
+ "version": "5.9.0",
"description": "Tidal on Electron with widevine(hifi) support",
"main": "ts-dist/main.js",
"scripts": {
diff --git a/src/constants/settings.ts b/src/constants/settings.ts
index 8750dadfc..c435f76dd 100644
--- a/src/constants/settings.ts
+++ b/src/constants/settings.ts
@@ -24,6 +24,9 @@ export const settings = {
detailsPrefix: "discord.detailsPrefix",
buttonText: "discord.buttonText",
includeTimestamps: "discord.includeTimestamps",
+ showSong: "discord.showSong",
+ idleText: "discord.idleText",
+ usingText: "discord.usingText",
},
ListenBrainz: {
root: "ListenBrainz",
diff --git a/src/features/flags/flags.ts b/src/features/flags/flags.ts
index df5dbef5e..8b156e2c3 100644
--- a/src/features/flags/flags.ts
+++ b/src/features/flags/flags.ts
@@ -9,6 +9,7 @@ import { Logger } from "../logger";
*/
export function setDefaultFlags(app: App) {
setFlag(app, "disable-seccomp-filter-sandbox");
+ setFlag(app, "disable-features", "MediaSessionService");
}
/**
diff --git a/src/pages/settings/preload.ts b/src/pages/settings/preload.ts
index 7898a3dcf..c8573e561 100644
--- a/src/pages/settings/preload.ts
+++ b/src/pages/settings/preload.ts
@@ -20,6 +20,11 @@ const switchesWithSettings = {
classToHide: "discord_options",
settingsKey: settings.enableDiscord,
},
+ discord_show_song: {
+ switch: "discord_show_song",
+ classToHide: "discord_show_song_options",
+ settingsKey: settings.discord.showSong,
+ }
};
let adBlock: HTMLInputElement,
@@ -49,7 +54,10 @@ let adBlock: HTMLInputElement,
enableWaylandSupport: HTMLInputElement,
discord_details_prefix: HTMLInputElement,
discord_include_timestamps: HTMLInputElement,
- discord_button_text: HTMLInputElement;
+ discord_button_text: HTMLInputElement,
+ discord_show_song: HTMLInputElement,
+ discord_idle_text: HTMLInputElement,
+ discord_using_text: HTMLInputElement;
addCustomCss(app);
@@ -138,6 +146,9 @@ function refreshSettings() {
discord_details_prefix.value = settingsStore.get(settings.discord.detailsPrefix);
discord_include_timestamps.checked = settingsStore.get(settings.discord.includeTimestamps);
discord_button_text.value = settingsStore.get(settings.discord.buttonText);
+ discord_show_song.checked = settingsStore.get(settings.discord.showSong);
+ discord_idle_text.value = settingsStore.get(settings.discord.idleText);
+ discord_using_text.value = settingsStore.get(settings.discord.usingText);
// set state of all switches with additional settings
Object.values(switchesWithSettings).forEach((settingSwitch) => {
@@ -251,6 +262,9 @@ window.addEventListener("DOMContentLoaded", () => {
discord_include_timestamps = get("discord_include_timestamps");
listenbrainz_delay = get("listenbrainz_delay");
discord_button_text = get("discord_button_text");
+ discord_show_song = get("discord_show_song");
+ discord_using_text = get("discord_using_text");
+ discord_idle_text = get("discord_idle_text")
refreshSettings();
addInputListener(adBlock, settings.adBlock);
@@ -285,4 +299,7 @@ window.addEventListener("DOMContentLoaded", () => {
addInputListener(discord_details_prefix, settings.discord.detailsPrefix);
addInputListener(discord_include_timestamps, settings.discord.includeTimestamps);
addInputListener(discord_button_text, settings.discord.buttonText);
+ addInputListener(discord_show_song, settings.discord.showSong, switchesWithSettings.discord_show_song);
+ addInputListener(discord_idle_text, settings.discord.idleText);
+ addInputListener(discord_using_text, settings.discord.usingText);
});
diff --git a/src/pages/settings/settings.html b/src/pages/settings/settings.html
index b250a89f0..f9b521ac6 100644
--- a/src/pages/settings/settings.html
+++ b/src/pages/settings/settings.html
@@ -216,32 +216,64 @@
Discord RPC
+
-
Include timestamps
-
Show current/end playtime in the Discord client
+
Idle Text
+
The text displayed on Discord's rich presence while idling in the app.
+
-
-
Details prefix
-
Prefix for the "details" field of Discord's rich presence.
-
+
Using Tidal Text
+
The text displayed on Discord's rich presence while "showSong" is turned off
+
-
+
-
Button text
-
Text to display on the button below the song information.
-
+
Show song
+
Show the current song in the Discord client
+
+
+
+
+
+
+
Include timestamps
+
Show current/end playtime in the Discord client
+
+
+
+
+
+
+
Details prefix
+
Prefix for the "details" field of Discord's rich presence.
+
+
+
+
+
+
+
Button text
+
Text to display on the button below the song information.
+
+
+
+
+
@@ -400,7 +432,7 @@
Upload new themes
TIDAL Hi-Fi
Github ;
let scrobbleWaitingForDelay = false;
+let wasJustPausedOrResumed = false;
+let currentMediaInfo: Options;
const elements = {
play: '*[data-test="play"]',
@@ -182,6 +184,7 @@ function getUpdateFrequency() {
* Play or pause the current song
*/
function playPause() {
+ wasJustPausedOrResumed = true;
const play = elements.get("play");
if (play) {
@@ -301,6 +304,8 @@ function addIPCEventListeners() {
ipcRenderer.on("globalEvent", (_event, args) => {
switch (args) {
case globalEvents.playPause:
+ case globalEvents.play:
+ case globalEvents.pause:
playPause();
break;
case globalEvents.next:
@@ -309,12 +314,6 @@ function addIPCEventListeners() {
case globalEvents.previous:
elements.click("previous");
break;
- case globalEvents.play:
- elements.click("play");
- break;
- case globalEvents.pause:
- elements.click("pause");
- break;
case globalEvents.toggleFavorite:
elements.click("favorite");
break;
@@ -357,6 +356,7 @@ function convertDuration(duration: string) {
*/
function updateMediaInfo(options: Options, notify: boolean) {
if (options) {
+ currentMediaInfo = options;
ipcRenderer.send(globalEvents.updateInfo, options);
if (settingsStore.get(settings.notifications) && notify) {
new Notification({ title: options.title, body: options.artists, icon: options.icon }).show();
@@ -510,60 +510,69 @@ setInterval(function () {
const title = elements.getText("title");
const artistsArray = elements.getArtistsArray();
const artistsString = elements.getArtistsString(artistsArray);
- skipArtistsIfFoundInSkippedArtistsList(artistsArray);
-
- const album = elements.getAlbumName();
- const current = elements.getText("current");
- const duration = elements.getText("duration");
const songDashArtistTitle = `${title} - ${artistsString}`;
- const currentStatus = getCurrentlyPlayingStatus();
- const options = {
- title,
- artists: artistsString,
- album: album,
- status: currentStatus,
- url: getTrackURL(),
- current,
- duration,
- "app-name": appName,
- image: "",
- icon: "",
- favorite: elements.isFavorite(),
- };
-
const titleOrArtistsChanged = currentSong !== songDashArtistTitle;
+ const current = elements.getText("current");
+ const currentStatus = getCurrentlyPlayingStatus();
- // update title, url and play info with new info
- setTitle(songDashArtistTitle);
- getTrackURL();
- currentSong = songDashArtistTitle;
- currentPlayStatus = currentStatus;
-
- const image = elements.getSongIcon();
-
- new Promise((resolve) => {
- if (image.startsWith("http")) {
- options.image = image;
- downloadFile(image, notificationPath).then(
- () => {
- options.icon = notificationPath;
- resolve();
- },
- () => {
- // if the image can't be downloaded then continue without it
- resolve();
- }
- );
- } else {
- // if the image can't be found on the page continue without it
- resolve();
- }
- }).then(() => {
- updateMediaInfo(options, titleOrArtistsChanged);
- if (titleOrArtistsChanged) {
- updateMediaSession(options);
+ // update info if song changed or was just paused/resumed
+ if (titleOrArtistsChanged || wasJustPausedOrResumed) {
+ if (wasJustPausedOrResumed) {
+ wasJustPausedOrResumed = false;
}
- });
+ skipArtistsIfFoundInSkippedArtistsList(artistsArray);
+
+ const album = elements.getAlbumName();
+ const duration = elements.getText("duration");
+ const options = {
+ title,
+ artists: artistsString,
+ album: album,
+ status: currentStatus,
+ url: getTrackURL(),
+ current,
+ duration,
+ "app-name": appName,
+ image: "",
+ icon: "",
+ favorite: elements.isFavorite(),
+ };
+
+ // update title, url and play info with new info
+ setTitle(songDashArtistTitle);
+ getTrackURL();
+ currentSong = songDashArtistTitle;
+ currentPlayStatus = currentStatus;
+
+ const image = elements.getSongIcon();
+
+ new Promise((resolve) => {
+ if (image.startsWith("http")) {
+ options.image = image;
+ downloadFile(image, notificationPath).then(
+ () => {
+ options.icon = notificationPath;
+ resolve();
+ },
+ () => {
+ // if the image can't be downloaded then continue without it
+ resolve();
+ }
+ );
+ } else {
+ // if the image can't be found on the page continue without it
+ resolve();
+ }
+ }).then(() => {
+ updateMediaInfo(options, titleOrArtistsChanged);
+ if (titleOrArtistsChanged) {
+ updateMediaSession(options);
+ }
+ });
+ } else {
+ // just update the time
+ updateMediaInfo({ ...currentMediaInfo, ...{ current } }, false);
+ }
/**
* automatically skip a song if the artists are found in the list of artists to skip
diff --git a/src/scripts/discord.ts b/src/scripts/discord.ts
index 31088c70e..3f4ca6ae3 100644
--- a/src/scripts/discord.ts
+++ b/src/scripts/discord.ts
@@ -1,4 +1,4 @@
-import { Client } from "discord-rpc";
+import { Client, Presence } from "discord-rpc";
import { app, ipcMain } from "electron";
import { globalEvents } from "../constants/globalEvents";
import { settings } from "../constants/settings";
@@ -18,58 +18,73 @@ function timeToSeconds(timeArray: string[]) {
export let rpc: Client;
const observer = () => {
- if (mediaInfo.status === MediaStatus.paused && rpc) {
- rpc.setActivity(idleStatus);
- } else if (rpc) {
- const currentSeconds = timeToSeconds(mediaInfo.current.split(":"));
- const durationSeconds = timeToSeconds(mediaInfo.duration.split(":"));
- const date = new Date();
- const now = (date.getTime() / 1000) | 0;
- const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds));
+ if (rpc) {
+ rpc.setActivity(getActivity());
+ }
+};
+
+const defaultPresence = {
+ largeImageKey: "tidal-hifi-icon",
+ largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`,
+ instance: false,
+};
+
+const getActivity = (): Presence => {
+ const presence: Presence = { ...defaultPresence };
+
+ if (mediaInfo.status === MediaStatus.paused) {
+ presence.details =
+ settingsStore.get(settings.discord.idleText) ?? "Browsing Tidal";
+ } else {
+ const showSong = settingsStore.get(settings.discord.showSong) ?? false;
+ if (showSong) {
+ const { includeTimestamps, detailsPrefix, buttonText } = getFromStore();
+ includeTimeStamps(includeTimestamps);
+ setPresenceFromMediaInfo(detailsPrefix, buttonText);
+ } else {
+ presence.details =
+ settingsStore.get(settings.discord.usingText) ?? "Playing media on TIDAL";
+ }
+ }
+ return presence;
+
+ function getFromStore() {
+ const includeTimestamps =
+ settingsStore.get(settings.discord.includeTimestamps) ?? true;
const detailsPrefix =
settingsStore.get(settings.discord.detailsPrefix) ?? "Listening to ";
const buttonText =
settingsStore.get(settings.discord.buttonText) ?? "Play on TIDAL";
- const includeTimestamps =
- settingsStore.get(settings.discord.includeTimestamps) ?? true;
- let activity = {
- ...idleStatus,
- ...{
- startTimestamp: includeTimestamps ? now : undefined,
- endTimestamp: includeTimestamps ? remaining : undefined,
- },
- };
+ return { includeTimestamps, detailsPrefix, buttonText };
+ }
+
+ function setPresenceFromMediaInfo(detailsPrefix: any, buttonText: any) {
if (mediaInfo.url) {
- activity = {
- ...activity,
- ...{
- details: `${detailsPrefix}${mediaInfo.title}`,
- state: mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)",
- largeImageKey: mediaInfo.image,
- largeImageText: mediaInfo.album ? mediaInfo.album : `${idleStatus.largeImageText}`,
- buttons: [{ label: buttonText, url: mediaInfo.url }],
- },
- };
+ presence.details = `${detailsPrefix}${mediaInfo.title}`;
+ presence.state = mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)";
+ presence.largeImageKey = mediaInfo.image;
+ if (mediaInfo.album) {
+ presence.largeImageText = mediaInfo.album;
+ }
+ presence.buttons = [{ label: buttonText, url: mediaInfo.url }];
} else {
- activity = {
- ...activity,
- ...{
- details: `Watching ${mediaInfo.title}`,
- state: mediaInfo.artists,
- },
- };
+ presence.details = `Watching ${mediaInfo.title}`;
+ presence.state = mediaInfo.artists;
}
-
- rpc.setActivity(activity);
}
-};
-const idleStatus = {
- details: `Browsing Tidal`,
- largeImageKey: "tidal-hifi-icon",
- largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`,
- instance: false,
+ function includeTimeStamps(includeTimestamps: any) {
+ if (includeTimestamps) {
+ const currentSeconds = timeToSeconds(mediaInfo.current.split(":"));
+ const durationSeconds = timeToSeconds(mediaInfo.duration.split(":"));
+ const date = new Date();
+ const now = (date.getTime() / 1000) | 0;
+ const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds));
+ presence.startTimestamp = now;
+ presence.endTimestamp = remaining;
+ }
+ }
};
/**
@@ -80,7 +95,7 @@ export const initRPC = () => {
rpc.login({ clientId }).then(
() => {
rpc.on("ready", () => {
- rpc.setActivity(idleStatus);
+ rpc.setActivity(getActivity());
});
ipcMain.on(globalEvents.updateInfo, observer);
},
diff --git a/src/scripts/settings.ts b/src/scripts/settings.ts
index 10e08bc29..49e199602 100644
--- a/src/scripts/settings.ts
+++ b/src/scripts/settings.ts
@@ -5,6 +5,25 @@ import path from "path";
import { settings } from "../constants/settings";
let settingsWindow: BrowserWindow;
+/**
+ * Build a migration step for several settings.
+ * All settings will be checked and set to the default if non-existent.
+ * @param version
+ * @param migrationStore
+ * @param options
+ */
+const buildMigration = (
+ version: string,
+ migrationStore: { get: (str: string) => string; set: (str: string, val: unknown) => void },
+ options: Array<{ key: string; value: unknown }>
+) => {
+ console.log(`running migrations for ${version}`);
+ options.forEach(({ key, value }) => {
+ const valueToSet = migrationStore.get(key) ?? value;
+ console.log(` - setting ${key} to ${value}`);
+ migrationStore.set(key, valueToSet);
+ });
+};
export const settingsStore = new Store({
defaults: {
@@ -19,9 +38,12 @@ export const settingsStore = new Store({
enableCustomHotkeys: false,
enableDiscord: false,
discord: {
+ showSong: true,
+ idleText: "Browsing Tidal",
+ usingText: "Playing media on TIDAL",
+ includeTimestamps: true,
detailsPrefix: "Listening to ",
buttonText: "Play on Tidal",
- includeTimestamps: true,
},
ListenBrainz: {
enabled: false,
@@ -69,6 +91,16 @@ export const settingsStore = new Store({
migrationStore.get(settings.discord.includeTimestamps) ?? true
);
},
+ "5.9.0": (migrationStore) => {
+ buildMigration("5.9.0", migrationStore, [
+ { key: settings.discord.showSong, value: "true" },
+ { key: settings.discord.idleText, value: "Browsing Tidal" },
+ {
+ key: settings.discord.usingText,
+ value: "Playing media on TIDAL",
+ },
+ ]);
+ },
},
});
@@ -106,6 +138,10 @@ export const createSettingsWindow = function () {
};
export const showSettingsWindow = function (tab = "general") {
+ if (!settingsWindow) {
+ console.log("Settings window is not initialized. Attempting to create it.");
+ createSettingsWindow();
+ }
settingsWindow.webContents.send("goToTab", tab);
// refresh data just before showing the window