Merge pull request #223 from Mastermindzh/release/5.1.0

Release/5.1.0
This commit is contained in:
Rick van Lieshout 2023-04-27 15:30:09 +02:00 committed by GitHub
commit 8d44ff8afb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 215 additions and 105 deletions

View File

@ -4,9 +4,24 @@ 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.1.0
### New features
- Added proper updates through the MediaSession API
- You can now add custom CSS in the "advanced" settings tab
- You can now configure the updateFrequency in the settings window
- Default value is set to 500 and will overwrite the hardcoded value of 100
### Fixes
- Any songs **including** an artist listed in the `skipped artists` setting will now be skipped even if the song is a collaboration.
- Linux desktop icons have been fixed. See [#222](https://github.com/Mastermindzh/tidal-hifi/pull/222) for details.
## 5.0.0
- Replaced "muting artists" with a full implementation of an Adblock mechanism
> Disabled audio & visual ads, unlocked lyrics, suggested track, track info, unlimited skips thanks to uBlockOrigin custom filters ([source](https://github.com/uBlockOrigin/uAssets/issues/17495))
- @thanasistrisp updated Electron to 24.1.2 and fixed the tray bug :)

BIN
assets/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/icons/16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
assets/icons/22x22.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
assets/icons/24x24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
assets/icons/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
assets/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
assets/icons/384x384.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
assets/icons/48x48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/icons/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -9,7 +9,7 @@ snap:
- screen-inhibit-control
linux:
category: AudioVideo
icon: icon.png
icon: assets/icons
target:
- dir
executableName: tidal-hifi
@ -18,7 +18,7 @@ linux:
Name: TIDAL Hi-Fi
GenericName: TIDAL Hi-Fi
Comment: The web version of listen.tidal.com running in electron with hifi support thanks to widevine.
Icon: icon.png
Icon: tidal-hifi
StartupNotify: true
Terminal: false
Type: Application

View File

@ -1,5 +1,4 @@
extends: ./build/electron-builder.base.yml
linux:
icon: icon.png
target:
- pacman

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "tidal-hifi",
"version": "5.0.0",
"version": "5.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tidal-hifi",
"version": "5.0.0",
"version": "5.1.0",
"license": "MIT",
"dependencies": {
"@electron/remote": "^2.0.9",

View File

@ -1,6 +1,6 @@
{
"name": "tidal-hifi",
"version": "5.0.0",
"version": "5.1.0",
"description": "Tidal on Electron with widevine(hifi) support",
"main": "src/main.js",
"scripts": {
@ -51,4 +51,4 @@
"sass-lint-auto-fix": "^0.21.2"
},
"prettier": "@mastermindzh/prettier-config"
}
}

View File

@ -9,34 +9,36 @@
* windowBounds: { width: 800, height: 600 },
*/
const settings = {
notifications: "notifications",
api: "api",
menuBar: "menuBar",
playBackControl: "playBackControl",
skipArtists: "skipArtists",
skippedArtists: "skippedArtists",
adBlock: "adBlock",
disableBackgroundThrottle: "disableBackgroundThrottle",
api: "api",
apiSettings: {
root: "apiSettings",
port: "apiSettings.port",
},
singleInstance: "singleInstance",
customCSS: "customCSS",
disableBackgroundThrottle: "disableBackgroundThrottle",
disableHardwareMediaKeys: "disableHardwareMediaKeys",
enableCustomHotkeys: "enableCustomHotkeys",
enableDiscord: "enableDiscord",
flags: {
disableHardwareMediaKeys: "flags.disableHardwareMediaKeys",
gpuRasterization: "flags.gpuRasterization",
},
menuBar: "menuBar",
minimizeOnClose: "minimizeOnClose",
mpris: "mpris",
enableCustomHotkeys: "enableCustomHotkeys",
notifications: "notifications",
playBackControl: "playBackControl",
singleInstance: "singleInstance",
skipArtists: "skipArtists",
skippedArtists: "skippedArtists",
trayIcon: "trayIcon",
enableDiscord: "enableDiscord",
updateFrequency: "updateFrequency",
windowBounds: {
root: "windowBounds",
width: "windowBounds.width",
height: "windowBounds.height",
},
minimizeOnClose: "minimizeOnClose",
};
module.exports = settings;

View File

@ -172,7 +172,6 @@ app.on("ready", async () => {
store.get(settings.trayIcon) && addTray(mainWindow, { icon }) && refreshTray();
store.get(settings.api) && expressModule.run(mainWindow);
store.get(settings.enableDiscord) && discordModule.initRPC();
// mainWindow.webContents.openDevTools();
} else {
app.quit();
}

View File

@ -1,20 +1,22 @@
let trayIcon,
minimizeOnClose,
mpris,
let adBlock,
api,
customCSS,
disableBackgroundThrottle,
disableHardwareMediaKeys,
enableCustomHotkeys,
enableDiscord,
skipArtists,
gpuRasterization,
menuBar,
minimizeOnClose,
mpris,
notifications,
playBackControl,
api,
port,
menuBar,
skippedArtists,
adBlock,
disableBackgroundThrottle,
singleInstance,
disableHardwareMediaKeys,
gpuRasterization;
skipArtists,
skippedArtists,
trayIcon,
updateFrequency;
const { store, settings } = require("./../../scripts/settings");
const { ipcRenderer } = require("electron");
@ -25,23 +27,25 @@ const { app } = remote;
* Sync the UI forms with the current settings
*/
function refreshSettings() {
notifications.checked = store.get(settings.notifications);
playBackControl.checked = store.get(settings.playBackControl);
adBlock.checked = store.get(settings.adBlock);
api.checked = store.get(settings.api);
port.value = store.get(settings.apiSettings.port);
menuBar.checked = store.get(settings.menuBar);
trayIcon.checked = store.get(settings.trayIcon);
mpris.checked = store.get(settings.mpris);
customCSS.value = store.get(settings.customCSS);
disableBackgroundThrottle.checked = store.get("disableBackgroundThrottle");
disableHardwareMediaKeys.checked = store.get(settings.flags.disableHardwareMediaKeys);
enableCustomHotkeys.checked = store.get(settings.enableCustomHotkeys);
enableDiscord.checked = store.get(settings.enableDiscord);
gpuRasterization.checked = store.get(settings.flags.gpuRasterization);
menuBar.checked = store.get(settings.menuBar);
minimizeOnClose.checked = store.get(settings.minimizeOnClose);
mpris.checked = store.get(settings.mpris);
notifications.checked = store.get(settings.notifications);
playBackControl.checked = store.get(settings.playBackControl);
port.value = store.get(settings.apiSettings.port);
singleInstance.checked = store.get(settings.singleInstance);
skipArtists.checked = store.get(settings.skipArtists);
skippedArtists.value = store.get(settings.skippedArtists).join("\n");
adBlock.checked = store.get(settings.adBlock);
singleInstance.checked = store.get(settings.singleInstance);
disableHardwareMediaKeys.checked = store.get(settings.flags.disableHardwareMediaKeys);
gpuRasterization.checked = store.get(settings.flags.gpuRasterization);
disableBackgroundThrottle.checked = store.get("disableBackgroundThrottle");
trayIcon.checked = store.get(settings.trayIcon);
updateFrequency.value = store.get(settings.updateFrequency);
}
/**
@ -109,41 +113,45 @@ window.addEventListener("DOMContentLoaded", () => {
document.getElementById(tab).click();
});
notifications = get("notifications");
playBackControl = get("playBackControl");
adBlock = get("adBlock");
api = get("apiCheckbox");
port = get("port");
menuBar = get("menuBar");
trayIcon = get("trayIcon");
minimizeOnClose = get("minimizeOnClose");
mpris = get("mprisCheckbox");
customCSS = get("customCSS");
disableBackgroundThrottle = get("disableBackgroundThrottle");
disableHardwareMediaKeys = get("disableHardwareMediaKeys");
enableCustomHotkeys = get("enableCustomHotkeys");
enableDiscord = get("enableDiscord");
gpuRasterization = get("gpuRasterization");
menuBar = get("menuBar");
minimizeOnClose = get("minimizeOnClose");
mpris = get("mprisCheckbox");
notifications = get("notifications");
playBackControl = get("playBackControl");
port = get("port");
trayIcon = get("trayIcon");
skipArtists = get("skipArtists");
skippedArtists = get("skippedArtists");
adBlock = get("adBlock");
disableBackgroundThrottle = get("disableBackgroundThrottle");
singleInstance = get("singleInstance");
disableHardwareMediaKeys = get("disableHardwareMediaKeys");
gpuRasterization = get("gpuRasterization");
updateFrequency = get("updateFrequency");
refreshSettings();
addInputListener(notifications, settings.notifications);
addInputListener(playBackControl, settings.playBackControl);
addInputListener(adBlock, settings.adBlock);
addInputListener(api, settings.api);
addInputListener(port, settings.apiSettings.port);
addInputListener(menuBar, settings.menuBar);
addInputListener(trayIcon, settings.trayIcon);
addInputListener(mpris, settings.mpris);
addTextAreaListener(customCSS, settings.customCSS);
addInputListener(disableBackgroundThrottle, settings.disableBackgroundThrottle);
addInputListener(disableHardwareMediaKeys, settings.flags.disableHardwareMediaKeys);
addInputListener(enableCustomHotkeys, settings.enableCustomHotkeys);
addInputListener(enableDiscord, settings.enableDiscord);
addInputListener(gpuRasterization, settings.flags.gpuRasterization);
addInputListener(menuBar, settings.menuBar);
addInputListener(minimizeOnClose, settings.minimizeOnClose);
addInputListener(mpris, settings.mpris);
addInputListener(notifications, settings.notifications);
addInputListener(playBackControl, settings.playBackControl);
addInputListener(port, settings.apiSettings.port);
addInputListener(skipArtists, settings.skipArtists);
addTextAreaListener(skippedArtists, settings.skippedArtists);
addInputListener(adBlock, settings.adBlock);
addInputListener(disableBackgroundThrottle, settings.disableBackgroundThrottle);
addInputListener(singleInstance, settings.singleInstance);
addInputListener(disableHardwareMediaKeys, settings.flags.disableHardwareMediaKeys);
addInputListener(gpuRasterization, settings.flags.gpuRasterization);
addInputListener(trayIcon, settings.trayIcon);
addInputListener(updateFrequency, settings.updateFrequency);
});

View File

@ -160,7 +160,7 @@
<div class="group__option">
<div class="group__description">
<label for="port">API port</label>
<input id="port" type="text" class="text-input" name="port" />
<input id="port" type="number" class="text-input" name="port" />
</div>
</div>
<div class="group__option">
@ -212,6 +212,31 @@
</section>
<section id="advanced-section" class="tabs__section">
<div class="group">
<p class="group__title">Settings</p>
<div class="group__option">
<div class="group__description">
<h4>Update frequency</h4>
<p>
The amount of time, in milliseconds, that tidal-hifi will refresh its playback info by scraping the
website.
The default of 500 seems to work in more cases but if you are fine with a bit more resource usage you
can decrease it as well.
</p>
<input id="updateFrequency" type="number" class="text-input" name="updateFrequency" />
</div>
</div>
<div class="group__option">
<div class="group__description">
<h4>Custom CSS</h4>
<p>
The css that you put in here will be injected into a style tag in the head of the document.
</p>
</div>
</div>
</div>
<textarea id="customCSS" class="textarea" cols="40" rows="8" spellcheck="false"></textarea>
<div class="group">
<p class="group__title">Flags</p>
<div class="group__option">

View File

@ -7,7 +7,7 @@ const { downloadFile } = require("./scripts/download");
const statuses = require("./constants/statuses");
const hotkeys = require("./scripts/hotkeys");
const globalEvents = require("./constants/globalEvents");
const { skipArtists } = require("./constants/settings");
const { skipArtists, updateFrequency, customCSS } = require("./constants/settings");
const notificationPath = `${app.getPath("userData")}/notification.jpg`;
const appName = "Tidal Hifi";
let currentSong = "";
@ -65,16 +65,27 @@ const elements = {
return "";
},
getArtists: function () {
/**
* returns an array of all artists in the current song
* @returns {Array} artists
*/
getArtistsArray: function () {
const footer = this.get("footer");
if (footer) {
const artists = footer.querySelector(this["artists"]);
if (artists) {
return artists.innerText;
}
const artists = footer.querySelectorAll(this.artists);
if (artists) return Array.from(artists).map((artist) => artist.textContent);
}
return [];
},
/**
* unify the artists array into a string separated by commas
* @param {Array} artistsArray
* @returns {String} artists
*/
getArtistsString: function (artistsArray) {
if (artistsArray.length > 0) return artistsArray.join(", ");
return "unknown artist(s)";
},
@ -132,6 +143,29 @@ const elements = {
},
};
function addCustomCss() {
window.addEventListener("DOMContentLoaded", () => {
const style = document.createElement("style");
style.innerHTML = store.get(customCSS);
document.head.appendChild(style);
});
}
/**
* Get the update frequency from the store
* make sure it returns a number, if not use the default
*/
function getUpdateFrequency() {
const storeValue = store.get(updateFrequency);
const defaultValue = 500;
if (!isNaN(storeValue)) {
return storeValue;
} else {
return defaultValue;
}
}
/**
* Play or pause the current song
*/
@ -289,14 +323,14 @@ function updateMediaInfo(options, notify) {
if (options) {
ipcRenderer.send(globalEvents.updateInfo, options);
if (store.get(settings.notifications) && notify) {
new Notification({ title: options.title, body: options.message, icon: options.icon }).show();
new Notification({ title: options.title, body: options.artists, icon: options.icon }).show();
}
if (player) {
player.metadata = {
...player.metadata,
...{
"xesam:title": options.title,
"xesam:artist": [options.message],
"xesam:artist": [options.artists],
"xesam:album": options.album,
"mpris:artUrl": options.image,
"mpris:length": convertDuration(options.duration) * 1000 * 1000,
@ -327,22 +361,40 @@ function getTrackID() {
return window.location;
}
function updateMediaSession(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
*/
setInterval(function () {
const title = elements.getText("title");
const artists = elements.getArtists();
skipArtistsIfFoundInSkippedArtistsList(artists);
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} - ${artists}`;
const songDashArtistTitle = `${title} - ${artistsString}`;
const currentStatus = getCurrentlyPlayingStatus();
const options = {
title,
message: artists,
artists: artistsString,
album: album,
status: currentStatus,
url: getTrackURL(),
@ -351,7 +403,7 @@ setInterval(function () {
"app-name": appName,
};
const titleOrArtistChanged = currentSong !== songDashArtistTitle;
const titleOrArtistsChanged = currentSong !== songDashArtistTitle;
// update title, url and play info with new info
setTitle(songDashArtistTitle);
@ -380,24 +432,32 @@ setInterval(function () {
}
}).then(
() => {
updateMediaInfo(options, titleOrArtistChanged);
updateMediaInfo(options, titleOrArtistsChanged);
if (titleOrArtistsChanged) {
updateMediaSession(options);
}
},
() => {}
);
/**
* automatically skip a song if the artists are found in the list of artists to skip
* @param {*} artists list of artists to skip
* @param {*} artists array of artists
*/
function skipArtistsIfFoundInSkippedArtistsList(artists) {
if (store.get(skipArtists)) {
const skippedArtists = store.get(settings.skippedArtists);
if (skippedArtists.find((artist) => artist === artists) !== undefined) {
elements.click("next");
if (skippedArtists.length > 0) {
const artistsToSkip = skippedArtists.map((artist) => artist);
const artistNames = Object.values(artists).map((artist) => artist);
const foundArtist = artistNames.some((artist) => artistsToSkip.includes(artist));
if (foundArtist) {
elements.click("next");
}
}
}
}
}, 100);
}, getUpdateFrequency());
if (process.platform === "linux" && store.get(settings.mpris)) {
try {
@ -454,7 +514,7 @@ if (process.platform === "linux" && store.get(settings.mpris)) {
console.log("player api not working");
}
}
addCustomCss();
addHotKeys();
addIPCEventListeners();
addFullScreenListeners();

View File

@ -26,8 +26,8 @@ const observer = (event, arg) => {
...idleStatus,
...{
details: `Listening to ${mediaInfoModule.mediaInfo.title}`,
state: mediaInfoModule.mediaInfo.artist
? mediaInfoModule.mediaInfo.artist
state: mediaInfoModule.mediaInfo.artists
? mediaInfoModule.mediaInfo.artists
: "unknown artist(s)",
startTimestamp: parseInt(now),
endTimestamp: parseInt(remaining),
@ -43,7 +43,7 @@ const observer = (event, arg) => {
...idleStatus,
...{
details: `Watching ${mediaInfoModule.mediaInfo.title}`,
state: mediaInfoModule.mediaInfo.artist,
state: mediaInfoModule.mediaInfo.artists,
startTimestamp: parseInt(now),
endTimestamp: parseInt(remaining),
},

View File

@ -11,7 +11,7 @@ let expressInstance;
/**
* Function to enable tidal-hifi's express api
*/
expressModule.run = function(mainWindow) {
expressModule.run = function (mainWindow) {
/**
* Shorthand to handle a fire and forget global event
* @param {*} res
@ -24,14 +24,14 @@ expressModule.run = function(mainWindow) {
const expressApp = express();
expressApp.get("/", (req, res) => res.send("Hello World!"));
expressApp.get("/current", (req, res) => res.json(mediaInfo));
expressApp.get("/current", (req, res) => res.json({ ...mediaInfo, artist: mediaInfo.artists }));
expressApp.get("/image", (req, res) => {
var stream = fs.createReadStream(mediaInfo.icon);
stream.on("open", function() {
stream.on("open", function () {
res.set("Content-Type", "image/png");
stream.pipe(res);
});
stream.on("error", function() {
stream.on("error", function () {
res.set("Content-Type", "text/plain");
res.status(404).end("Not found");
});
@ -54,7 +54,7 @@ expressModule.run = function(mainWindow) {
let port = store.get(settings.apiSettings.port);
expressInstance = expressApp.listen(port, "127.0.0.1", () => {});
expressInstance.on("error", function(e) {
expressInstance.on("error", function (e) {
let message = e.code;
if (e.code === "EADDRINUSE") {
message = `Port ${port} in use.`;

View File

@ -2,14 +2,14 @@ const statuses = require("./../constants/statuses");
const mediaInfo = {
title: "",
artist: "",
artists: "",
album: "",
icon: "",
status: statuses.paused,
url: "",
current: "",
duration: "",
image: "tidal-hifi-icon"
image: "tidal-hifi-icon",
};
const mediaInfoModule = {
mediaInfo,
@ -20,7 +20,7 @@ const mediaInfoModule = {
*/
mediaInfoModule.update = function (arg) {
mediaInfo.title = propOrDefault(arg.title);
mediaInfo.artist = propOrDefault(arg.message);
mediaInfo.artists = propOrDefault(arg.artists);
mediaInfo.album = propOrDefault(arg.album);
mediaInfo.icon = propOrDefault(arg.icon);
mediaInfo.url = propOrDefault(arg.url);

View File

@ -7,29 +7,31 @@ let settingsWindow;
const store = new Store({
defaults: {
notifications: true,
api: true,
playBackControl: true,
skipArtists: false,
skippedArtists: [""],
adBlock: false,
disableBackgroundThrottle: true,
menuBar: true,
api: true,
apiSettings: {
port: 47836,
},
singleInstance: true,
customCSS: "",
disableBackgroundThrottle: true,
disableHardwareMediaKeys: false,
trayIcon: true,
minimizeOnClose: false,
mpris: false,
enableCustomHotkeys: false,
enableDiscord: false,
windowBounds: { width: 800, height: 600 },
flags: {
gpuRasterization: true,
disableHardwareMediaKeys: false,
},
menuBar: true,
minimizeOnClose: false,
mpris: false,
notifications: true,
playBackControl: true,
singleInstance: true,
skipArtists: false,
skippedArtists: [""],
trayIcon: true,
updateFrequency: 500,
windowBounds: { width: 800, height: 600 },
},
migrations: {
"3.1.0": (migrationStore) => {