Merge pull request #343 from Mastermindzh/5.9.0

5.9.0
This commit is contained in:
Rick van Lieshout 2024-02-12 22:56:05 +01:00 committed by GitHub
commit 21cb0ea79d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 251 additions and 123 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/), 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.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] ## [5.8.0]
- Updated Electron to 28.1.1 (fixes [325](https://github.com/Mastermindzh/tidal-hifi/issues/325)) - Updated Electron to 28.1.1 (fixes [325](https://github.com/Mastermindzh/tidal-hifi/issues/325))

View File

@ -11,7 +11,7 @@ extraResources:
- "themes/**" - "themes/**"
linux: linux:
category: AudioVideo category: AudioVideo
icon: assets/icons icon: build/icons/256x256.png
target: target:
- dir - dir
executableName: tidal-hifi executableName: tidal-hifi

BIN
build/icons/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
build/icons/icon.icns Normal file

Binary file not shown.

BIN
build/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -1,6 +1,6 @@
{ {
"name": "tidal-hifi", "name": "tidal-hifi",
"version": "5.8.0", "version": "5.9.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": {

View File

@ -24,6 +24,9 @@ export const settings = {
detailsPrefix: "discord.detailsPrefix", detailsPrefix: "discord.detailsPrefix",
buttonText: "discord.buttonText", buttonText: "discord.buttonText",
includeTimestamps: "discord.includeTimestamps", includeTimestamps: "discord.includeTimestamps",
showSong: "discord.showSong",
idleText: "discord.idleText",
usingText: "discord.usingText",
}, },
ListenBrainz: { ListenBrainz: {
root: "ListenBrainz", root: "ListenBrainz",

View File

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

View File

@ -20,6 +20,11 @@ const switchesWithSettings = {
classToHide: "discord_options", classToHide: "discord_options",
settingsKey: settings.enableDiscord, settingsKey: settings.enableDiscord,
}, },
discord_show_song: {
switch: "discord_show_song",
classToHide: "discord_show_song_options",
settingsKey: settings.discord.showSong,
}
}; };
let adBlock: HTMLInputElement, let adBlock: HTMLInputElement,
@ -49,7 +54,10 @@ let adBlock: HTMLInputElement,
enableWaylandSupport: HTMLInputElement, enableWaylandSupport: HTMLInputElement,
discord_details_prefix: HTMLInputElement, discord_details_prefix: HTMLInputElement,
discord_include_timestamps: 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); addCustomCss(app);
@ -138,6 +146,9 @@ function refreshSettings() {
discord_details_prefix.value = settingsStore.get(settings.discord.detailsPrefix); discord_details_prefix.value = settingsStore.get(settings.discord.detailsPrefix);
discord_include_timestamps.checked = settingsStore.get(settings.discord.includeTimestamps); discord_include_timestamps.checked = settingsStore.get(settings.discord.includeTimestamps);
discord_button_text.value = settingsStore.get(settings.discord.buttonText); 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 // set state of all switches with additional settings
Object.values(switchesWithSettings).forEach((settingSwitch) => { Object.values(switchesWithSettings).forEach((settingSwitch) => {
@ -251,6 +262,9 @@ window.addEventListener("DOMContentLoaded", () => {
discord_include_timestamps = get("discord_include_timestamps"); discord_include_timestamps = get("discord_include_timestamps");
listenbrainz_delay = get("listenbrainz_delay"); listenbrainz_delay = get("listenbrainz_delay");
discord_button_text = get("discord_button_text"); 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(); refreshSettings();
addInputListener(adBlock, settings.adBlock); addInputListener(adBlock, settings.adBlock);
@ -285,4 +299,7 @@ window.addEventListener("DOMContentLoaded", () => {
addInputListener(discord_details_prefix, settings.discord.detailsPrefix); addInputListener(discord_details_prefix, settings.discord.detailsPrefix);
addInputListener(discord_include_timestamps, settings.discord.includeTimestamps); addInputListener(discord_include_timestamps, settings.discord.includeTimestamps);
addInputListener(discord_button_text, settings.discord.buttonText); 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);
}); });

View File

@ -216,32 +216,64 @@
</label> </label>
</div> </div>
<div id="discord_options"> <div id="discord_options">
<div class="group__option" class="hidden"> <div class="group__option" class="hidden">
<div class="group__description"> <div class="group__description">
<h4>Include timestamps</h4> <h4>Idle Text</h4>
<p>Show current/end playtime in the Discord client</p> <p>The text displayed on Discord's rich presence while idling in the app.</p>
<input id="discord_idle_text" type="text" class="text-input" name="discord_idle_text" />
</div>
</div>
<div class="group__option" class="hidden">
<div class="group__description">
<h4>Using Tidal Text</h4>
<p>The text displayed on Discord's rich presence while "showSong" is turned off</p>
<input id="discord_using_text" type="text" class="text-input" name="discord_using_text" />
</div>
</div>
<div class="group__option" class="hidden">
<div class="group__description">
<h4>Show song</h4>
<p>Show the current song in the Discord client</p>
</div> </div>
<label class="switch"> <label class="switch">
<input id="discord_include_timestamps" type="checkbox" /> <input id="discord_show_song" type="checkbox" />
<span class="switch__slider"></span> <span class="switch__slider"></span>
</label> </label>
</div> </div>
<div class="group__option" class="hidden"> <div id="discord_show_song_options" class="hidden">
<div class="group__description">
<h4>Details prefix</h4> <div class="group__option" class="hidden">
<p>Prefix for the "details" field of Discord's rich presence.</p> <div class="group__description">
<input id="discord_details_prefix" type="text" class="text-input" name="discord_details_prefix" /> <h4>Include timestamps</h4>
<p>Show current/end playtime in the Discord client</p>
</div>
<label class="switch">
<input id="discord_include_timestamps" type="checkbox" />
<span class="switch__slider"></span>
</label>
</div>
<div class="group__option" class="hidden">
<div class="group__description">
<h4>Details prefix</h4>
<p>Prefix for the "details" field of Discord's rich presence.</p>
<input id="discord_details_prefix" type="text" class="text-input" name="discord_details_prefix" />
</div>
</div>
<div class="group__option">
<div class="group__description">
<h4>Button text</h4>
<p>Text to display on the button below the song information.</p>
<input id="discord_button_text" type="text" class="text-input" name="discord_button_text" />
</div>
</div> </div>
</div> </div>
<div class="group__option">
<div class="group__description">
<h4>Button text</h4>
<p>Text to display on the button below the song information.</p>
<input id="discord_button_text" type="text" class="text-input" name="discord_button_text" />
</div>
</div>
</div> </div>
</div> </div>
<div class="group"> <div class="group">
@ -400,7 +432,7 @@
<img alt="tidal icon" class="about-section__icon" src="./icon.png" /> <img alt="tidal icon" class="about-section__icon" src="./icon.png" />
<h4>TIDAL Hi-Fi</h4> <h4>TIDAL Hi-Fi</h4>
<div class="about-section__version"> <div class="about-section__version">
<a href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.8.0">5.8.0</a> <a href="https://github.com/Mastermindzh/tidal-hifi/releases/tag/5.9.0">5.9.0</a>
</div> </div>
<div class="about-section__links"> <div class="about-section__links">
<a href="https://github.com/mastermindzh/tidal-hifi/" class="about-section__button">Github <i <a href="https://github.com/mastermindzh/tidal-hifi/" class="about-section__button">Github <i

View File

@ -26,6 +26,8 @@ let player: Player;
let currentPlayStatus = MediaStatus.paused; let currentPlayStatus = MediaStatus.paused;
let currentListenBrainzDelayId: ReturnType<typeof setTimeout>; let currentListenBrainzDelayId: ReturnType<typeof setTimeout>;
let scrobbleWaitingForDelay = false; let scrobbleWaitingForDelay = false;
let wasJustPausedOrResumed = false;
let currentMediaInfo: Options;
const elements = { const elements = {
play: '*[data-test="play"]', play: '*[data-test="play"]',
@ -182,6 +184,7 @@ function getUpdateFrequency() {
* Play or pause the current song * Play or pause the current song
*/ */
function playPause() { function playPause() {
wasJustPausedOrResumed = true;
const play = elements.get("play"); const play = elements.get("play");
if (play) { if (play) {
@ -301,6 +304,8 @@ function addIPCEventListeners() {
ipcRenderer.on("globalEvent", (_event, args) => { ipcRenderer.on("globalEvent", (_event, args) => {
switch (args) { switch (args) {
case globalEvents.playPause: case globalEvents.playPause:
case globalEvents.play:
case globalEvents.pause:
playPause(); playPause();
break; break;
case globalEvents.next: case globalEvents.next:
@ -309,12 +314,6 @@ function addIPCEventListeners() {
case globalEvents.previous: case globalEvents.previous:
elements.click("previous"); elements.click("previous");
break; break;
case globalEvents.play:
elements.click("play");
break;
case globalEvents.pause:
elements.click("pause");
break;
case globalEvents.toggleFavorite: case globalEvents.toggleFavorite:
elements.click("favorite"); elements.click("favorite");
break; break;
@ -357,6 +356,7 @@ function convertDuration(duration: string) {
*/ */
function updateMediaInfo(options: Options, notify: boolean) { function updateMediaInfo(options: Options, notify: boolean) {
if (options) { if (options) {
currentMediaInfo = options;
ipcRenderer.send(globalEvents.updateInfo, options); ipcRenderer.send(globalEvents.updateInfo, options);
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();
@ -510,60 +510,69 @@ setInterval(function () {
const title = elements.getText("title"); const title = elements.getText("title");
const artistsArray = elements.getArtistsArray(); const artistsArray = elements.getArtistsArray();
const artistsString = elements.getArtistsString(artistsArray); 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 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 titleOrArtistsChanged = currentSong !== songDashArtistTitle;
const current = elements.getText("current");
const currentStatus = getCurrentlyPlayingStatus();
// update title, url and play info with new info // update info if song changed or was just paused/resumed
setTitle(songDashArtistTitle); if (titleOrArtistsChanged || wasJustPausedOrResumed) {
getTrackURL(); if (wasJustPausedOrResumed) {
currentSong = songDashArtistTitle; wasJustPausedOrResumed = false;
currentPlayStatus = currentStatus;
const image = elements.getSongIcon();
new Promise<void>((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(() => { skipArtistsIfFoundInSkippedArtistsList(artistsArray);
updateMediaInfo(options, titleOrArtistsChanged);
if (titleOrArtistsChanged) { const album = elements.getAlbumName();
updateMediaSession(options); 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<void>((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 * automatically skip a song if the artists are found in the list of artists to skip

View File

@ -1,4 +1,4 @@
import { Client } from "discord-rpc"; import { Client, Presence } from "discord-rpc";
import { app, ipcMain } from "electron"; import { app, ipcMain } from "electron";
import { globalEvents } from "../constants/globalEvents"; import { globalEvents } from "../constants/globalEvents";
import { settings } from "../constants/settings"; import { settings } from "../constants/settings";
@ -18,58 +18,73 @@ function timeToSeconds(timeArray: string[]) {
export let rpc: Client; export let rpc: Client;
const observer = () => { const observer = () => {
if (mediaInfo.status === MediaStatus.paused && rpc) { if (rpc) {
rpc.setActivity(idleStatus); rpc.setActivity(getActivity());
} else if (rpc) { }
const currentSeconds = timeToSeconds(mediaInfo.current.split(":")); };
const durationSeconds = timeToSeconds(mediaInfo.duration.split(":"));
const date = new Date(); const defaultPresence = {
const now = (date.getTime() / 1000) | 0; largeImageKey: "tidal-hifi-icon",
const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds)); largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`,
instance: false,
};
const getActivity = (): Presence => {
const presence: Presence = { ...defaultPresence };
if (mediaInfo.status === MediaStatus.paused) {
presence.details =
settingsStore.get<string, string>(settings.discord.idleText) ?? "Browsing Tidal";
} else {
const showSong = settingsStore.get<string, boolean>(settings.discord.showSong) ?? false;
if (showSong) {
const { includeTimestamps, detailsPrefix, buttonText } = getFromStore();
includeTimeStamps(includeTimestamps);
setPresenceFromMediaInfo(detailsPrefix, buttonText);
} else {
presence.details =
settingsStore.get<string, string>(settings.discord.usingText) ?? "Playing media on TIDAL";
}
}
return presence;
function getFromStore() {
const includeTimestamps =
settingsStore.get<string, boolean>(settings.discord.includeTimestamps) ?? true;
const detailsPrefix = const detailsPrefix =
settingsStore.get<string, string>(settings.discord.detailsPrefix) ?? "Listening to "; settingsStore.get<string, string>(settings.discord.detailsPrefix) ?? "Listening to ";
const buttonText = const buttonText =
settingsStore.get<string, string>(settings.discord.buttonText) ?? "Play on TIDAL"; settingsStore.get<string, string>(settings.discord.buttonText) ?? "Play on TIDAL";
const includeTimestamps =
settingsStore.get<string, boolean>(settings.discord.includeTimestamps) ?? true;
let activity = { return { includeTimestamps, detailsPrefix, buttonText };
...idleStatus,
...{
startTimestamp: includeTimestamps ? now : undefined,
endTimestamp: includeTimestamps ? remaining : undefined,
},
};
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 }],
},
};
} else {
activity = {
...activity,
...{
details: `Watching ${mediaInfo.title}`,
state: mediaInfo.artists,
},
};
}
rpc.setActivity(activity);
} }
};
const idleStatus = { function setPresenceFromMediaInfo(detailsPrefix: any, buttonText: any) {
details: `Browsing Tidal`, if (mediaInfo.url) {
largeImageKey: "tidal-hifi-icon", presence.details = `${detailsPrefix}${mediaInfo.title}`;
largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`, presence.state = mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)";
instance: false, presence.largeImageKey = mediaInfo.image;
if (mediaInfo.album) {
presence.largeImageText = mediaInfo.album;
}
presence.buttons = [{ label: buttonText, url: mediaInfo.url }];
} else {
presence.details = `Watching ${mediaInfo.title}`;
presence.state = mediaInfo.artists;
}
}
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.login({ clientId }).then(
() => { () => {
rpc.on("ready", () => { rpc.on("ready", () => {
rpc.setActivity(idleStatus); rpc.setActivity(getActivity());
}); });
ipcMain.on(globalEvents.updateInfo, observer); ipcMain.on(globalEvents.updateInfo, observer);
}, },

View File

@ -5,6 +5,25 @@ import path from "path";
import { settings } from "../constants/settings"; import { settings } from "../constants/settings";
let settingsWindow: BrowserWindow; 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({ export const settingsStore = new Store({
defaults: { defaults: {
@ -19,9 +38,12 @@ export const settingsStore = new Store({
enableCustomHotkeys: false, enableCustomHotkeys: false,
enableDiscord: false, enableDiscord: false,
discord: { discord: {
showSong: true,
idleText: "Browsing Tidal",
usingText: "Playing media on TIDAL",
includeTimestamps: true,
detailsPrefix: "Listening to ", detailsPrefix: "Listening to ",
buttonText: "Play on Tidal", buttonText: "Play on Tidal",
includeTimestamps: true,
}, },
ListenBrainz: { ListenBrainz: {
enabled: false, enabled: false,
@ -69,6 +91,16 @@ export const settingsStore = new Store({
migrationStore.get(settings.discord.includeTimestamps) ?? true 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") { 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); settingsWindow.webContents.send("goToTab", tab);
// refresh data just before showing the window // refresh data just before showing the window