Feature/4.1.0 (#156)

* added protocol handler

* Switched icon strategies to fix bugs with icons

* fixed tray icon issues

* fixed about :)

* Fixed playback, mpris and API issues
This commit is contained in:
Rick van Lieshout 2022-08-07 16:05:48 +02:00 committed by GitHub
parent 3a3e0e1a2d
commit 1439a11969
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1030 additions and 1000 deletions

View File

@ -4,6 +4,17 @@ 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).
## 4.1.0
- Added `tidal://` protocol support
- Switched icon strategies to fix bugs with icons
- Fixed tray icon bugs
- Menu now shows in KDE as well
- Toggle window is supported from tray icon
- regular click is still ignored, see [this issue](https://github.com/electron/electron/issues/6773)
- Fixed about tab not showing
- Fixed playback, mpris and API issues
## 4.0.1
- Updated build config to make use of a base file that doesn't build anything.

View File

@ -17,17 +17,23 @@ linux:
Name: tidal-hifi
GenericName: tidal-hifi
Comment: The web version of listen.tidal.com running in electron with hifi support thanks to widevine.
Icon: assets/icon.png
Icon: icon.png
StartupNotify: true
Terminal: false
Type: Application
Categories: Network;Application;AudioVideo;Audio;Video
StartupWMClass: tidal-hifi
X-PulseAudio-Properties: media.role=music
MimeType: x-scheme-handler/tidal;
mac:
category: public.app-category.entertainment
win:
icon: build/icon.png
icon: icon.png
artifactName: "tidalhifi"
appId: com.rickvanlieshout.tidalhifi
executableName: tidalhifi
protocols:
name: "tidal"
role: "Viewer"
schemes: ["tidal"]

View File

@ -1,6 +1,6 @@
extends: ./build/electron-builder.base.yml
linux:
category: Audio
icon: ./assets/icon.png
icon: icon.png
target:
- deb

View File

@ -1,6 +1,6 @@
extends: ./build/electron-builder.base.yml
linux:
category: Audio
icon: ./assets/icon.png
icon: icon.png
target:
- pacman

View File

@ -1,6 +1,6 @@
extends: ./build/electron-builder.base.yml
linux:
category: Audio
icon: ./assets/TIDAL.icns
icon: icon.png
target:
- rpm

View File

@ -1,6 +1,6 @@
extends: ./build/electron-builder.base.yml
linux:
category: Audio
icon: ./assets/icon.png
icon: icon.png
target:
- snap

View File

@ -14,7 +14,7 @@ linux:
- freebsd
win:
target: msi
icon: build/icon.png
icon: icon.png
artifactName: "tidalhifi"
appId: com.rickvanlieshout.tidalhifi
executableName: tidalhifi

BIN
build/icon-inverted.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
build/icon.icns Executable file

Binary file not shown.

669
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "tidal-hifi",
"version": "4.0.1",
"version": "4.1.0",
"description": "Tidal on Electron with widevine(hifi) support",
"main": "src/main.js",
"scripts": {
@ -27,17 +27,17 @@
"dependencies": {
"@electron/remote": "^2.0.8",
"discord-rpc": "^4.0.1",
"electron-store": "^8.0.1",
"express": "^4.17.1",
"hotkeys-js": "^3.8.7",
"electron-store": "^8.1.0",
"express": "^4.18.1",
"hotkeys-js": "^3.9.4",
"mpris-service": "^2.1.2",
"request": "^2.88.2"
},
"devDependencies": {
"@mastermindzh/prettier-config": "^1.0.0",
"electron": "git+https://github.com/castlabs/electron-releases.git#v19.0.5+wvcus",
"electron-builder": "^23.2.0",
"prettier": "^2.5.0"
"electron-builder": "^23.3.3",
"prettier": "^2.7.1"
},
"prettier": "@mastermindzh/prettier-config"
}

3
src/constants/values.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
name: "tidal-hifi",
};

View File

@ -1,5 +1,5 @@
require("@electron/remote/main").initialize();
const { app, BrowserWindow, components, globalShortcut, ipcMain } = require("electron");
const { app, BrowserWindow, components, globalShortcut, ipcMain, protocol } = require("electron");
const {
settings,
store,
@ -21,6 +21,7 @@ const flagValues = require("./constants/flags");
let mainWindow;
let icon = path.join(__dirname, "../assets/icon.png");
const PROTOCOL_PREFIX = "tidal";
setFlags();
@ -84,7 +85,7 @@ function createWindow(options = {}) {
},
});
require("@electron/remote/main").enable(mainWindow.webContents);
registerHttpProtocols();
syncMenuBarWithStore();
// load the Tidal website
@ -113,6 +114,13 @@ function createWindow(options = {}) {
});
}
function registerHttpProtocols() {
protocol.registerHttpProtocol(PROTOCOL_PREFIX, (request, _callback) => {
mainWindow.loadURL(`${tidalUrl}/${request.url.substring(PROTOCOL_PREFIX.length + 3)}`);
});
app.setAsDefaultProtocolClient(PROTOCOL_PREFIX);
}
function addGlobalShortcuts() {
Object.keys(mediaKeys).forEach((key) => {
globalShortcut.register(`${key}`, () => {
@ -131,7 +139,7 @@ app.on("ready", async () => {
addMenu();
createSettingsWindow();
addGlobalShortcuts();
store.get(settings.trayIcon) && addTray({ icon }) && refreshTray();
store.get(settings.trayIcon) && addTray(mainWindow, { icon }) && refreshTray();
store.get(settings.api) && expressModule.run(mainWindow);
store.get(settings.enableDiscord) && discordModule.initRPC();
// mainWindow.webContents.openDevTools();

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
const { setTitle } = require("./scripts/window-functions");
const { dialog, process, Notification } = require('@electron/remote');
const { dialog, process, Notification } = require("@electron/remote");
const { store, settings } = require("./scripts/settings");
const { ipcRenderer } = require("electron");
const { app } = require('@electron/remote');
const { app } = require("@electron/remote");
const { downloadFile } = require("./scripts/download");
const statuses = require("./constants/statuses");
const hotkeys = require("./scripts/hotkeys");
@ -12,10 +12,6 @@ const appName = "Tidal Hifi";
let currentSong = "";
let player;
let currentPlayStatus = statuses.paused;
let progressBarTime;
let currentTimeChanged = false;
let currentTime;
let currentURL = undefined;
let isMutedArtist = false;
const elements = {
@ -313,19 +309,16 @@ function updateMediaInfo(options, notify) {
/**
* Checks if Tidal is playing a video or song by grabbing the "a" element from the title.
* If it's a song it sets the track URL as currentURL, If it's a video it will set currentURL to undefined.
* If it's a song it returns the track URL, if not it will return undefined
*/
function updateURL() {
function getTrackURL() {
const URLelement = elements.get("title").querySelector("a");
switch (URLelement) {
case null:
currentURL = undefined;
break;
default:
const id = URLelement.href.replace(/[^0-9]/g, "");
currentURL = `https://tidal.com/browse/track/${id}`;
break;
if (URLelement !== null) {
const id = URLelement.href.replace(/[^0-9]/g, "");
return `https://tidal.com/browse/track/${id}`;
}
return window.location;
}
/**
@ -337,7 +330,6 @@ setInterval(function () {
const album = elements.getAlbumName();
const current = elements.getText("current");
const duration = elements.getText("duration");
const progressBarcurrentTime = elements.get("bar").getAttribute("aria-valuenow");
const songDashArtistTitle = `${title} - ${artists}`;
const currentStatus = getCurrentlyPlayingStatus();
const options = {
@ -345,72 +337,47 @@ setInterval(function () {
message: artists,
album: album,
status: currentStatus,
url: currentURL,
current: current,
duration: duration,
url: getTrackURL(),
current,
duration,
"app-name": appName,
};
const playStatusChanged = currentStatus !== currentPlayStatus;
const progressBarTimeChanged = progressBarcurrentTime !== progressBarTime;
const titleOrArtistChanged = currentSong !== songDashArtistTitle;
muteArtistIfFoundInMutedArtistsList();
if (titleOrArtistChanged || playStatusChanged || progressBarTimeChanged || currentTimeChanged) {
// update title, url and play info with new info
setTitle(songDashArtistTitle);
updateURL();
currentSong = songDashArtistTitle;
currentPlayStatus = currentStatus;
// update title, url and play info with new info
setTitle(songDashArtistTitle);
getTrackURL();
currentSong = songDashArtistTitle;
currentPlayStatus = currentStatus;
// check progress bar value and make sure current stays up to date after switch
if (progressBarTime != progressBarcurrentTime && !titleOrArtistChanged) {
progressBarTime = progressBarcurrentTime;
currentTime = options.current;
options.duration = duration;
currentTimeChanged = true;
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();
}
if (currentTimeChanged) {
if (options.current == currentTime && currentStatus != "paused") return;
currentTime = options.current;
currentTimeChanged = false;
}
// make sure current is set to 0 if title changes
if (titleOrArtistChanged) {
options.current = "0:00";
currentTime = options.current;
progressBarTime = progressBarcurrentTime;
}
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, titleOrArtistChanged);
},
() => {}
);
}
}).then(
() => {
updateMediaInfo(options, titleOrArtistChanged);
},
() => {}
);
/**
* Checks whether the current artist is included in the "muted artists" list and if so it will automatically mute the player
@ -429,7 +396,7 @@ setInterval(function () {
}
}
}
}, 500);
}, 1000);
if (process.platform === "linux" && store.get(settings.mpris)) {
try {

View File

@ -1,6 +1,7 @@
const { Menu, app } = require("electron");
const { showSettingsWindow } = require("./settings");
const isMac = process.platform === "darwin";
const { name } = require("./../constants/values");
const settingsMenuEntry = {
label: "Settings",
@ -15,92 +16,102 @@ const quitMenuEntry = {
click() {
app.exit(0);
},
accelerator: "Control+Q"
accelerator: "Control+Q",
};
const mainMenu = [
...(isMac
? [
{
label: app.name,
submenu: [
{ role: "about" },
settingsMenuEntry,
{ type: "separator" },
{ role: "services" },
{ type: "separator" },
{ role: "hide" },
{ role: "hideothers" },
{ role: "unhide" },
{ type: "separator" },
quitMenuEntry,
],
},
]
: []),
{
label: "File",
submenu: [settingsMenuEntry, isMac ? { role: "close" } : quitMenuEntry],
},
{
label: "Edit",
submenu: [
{ role: "undo" },
{ role: "redo" },
{ type: "separator" },
{ role: "cut" },
{ role: "copy" },
{ role: "paste" },
...(isMac
? [
{ role: "pasteAndMatchStyle" },
{ role: "delete" },
{ role: "selectAll" },
{ type: "separator" },
{
label: "Speech",
submenu: [{ role: "startspeaking" }, { role: "stopspeaking" }],
},
]
: [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]),
{ type: "separator" },
settingsMenuEntry,
],
},
{
label: "View",
submenu: [
{ role: "reload" },
{ role: "forcereload" },
{ type: "separator" },
{ role: "resetzoom" },
{ role: "zoomin" },
{ role: "zoomout" },
{ type: "separator" },
{ role: "togglefullscreen" },
],
},
{
label: "Window",
submenu: [
{ role: "minimize" },
...(isMac
? [{ type: "separator" }, { role: "front" }, { type: "separator" }, { role: "window" }]
: [{ role: "close" }]),
],
},
settingsMenuEntry,
{
label: "About",
click() {
showSettingsWindow("about");
const menuModule = {};
menuModule.getMenu = function (mainWindow) {
const toggleWindow = {
label: "Toggle Window",
click: function () {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
},
},
];
};
const menuModule = { mainMenu };
const mainMenu = [
...(isMac
? [
{
label: name,
submenu: [
{ role: "about" },
settingsMenuEntry,
{ type: "separator" },
{ role: "services" },
{ type: "separator" },
{ role: "hide" },
{ role: "hideothers" },
{ role: "unhide" },
{ type: "separator" },
quitMenuEntry,
],
},
]
: []),
{
label: "File",
submenu: [settingsMenuEntry, isMac ? { role: "close" } : quitMenuEntry],
},
{
label: "Edit",
submenu: [
{ role: "undo" },
{ role: "redo" },
{ type: "separator" },
{ role: "cut" },
{ role: "copy" },
{ role: "paste" },
...(isMac
? [
{ role: "pasteAndMatchStyle" },
{ role: "delete" },
{ role: "selectAll" },
{ type: "separator" },
{
label: "Speech",
submenu: [{ role: "startspeaking" }, { role: "stopspeaking" }],
},
]
: [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]),
{ type: "separator" },
settingsMenuEntry,
],
},
{
label: "View",
submenu: [
{ role: "reload" },
{ role: "forcereload" },
{ type: "separator" },
{ role: "resetzoom" },
{ role: "zoomin" },
{ role: "zoomout" },
{ type: "separator" },
{ role: "togglefullscreen" },
{ role: "toggledevtools" },
],
},
{
label: "Window",
submenu: [
{ role: "minimize" },
toggleWindow,
...(isMac
? [{ type: "separator" }, { role: "front" }, { type: "separator" }, { role: "window" }]
: [{ role: "close" }]),
],
},
settingsMenuEntry,
{
label: "About",
click() {
showSettingsWindow("about");
},
},
toggleWindow,
];
menuModule.getMenu = function () {
return Menu.buildFromTemplate(mainMenu);
};

View File

@ -1,48 +1,21 @@
const { Tray, app } = require("electron");
const { Menu } = require("electron");
const { getMenu, mainMenu } = require("./menu");
const { store, settings } = require("./settings");
const { Tray } = require("electron");
const { getMenu } = require("./menu");
const trayModule = {};
let tray;
trayModule.addTray = function (options = { icon: "" }) {
trayModule.addTray = function (mainWindow, options = { icon: "" }) {
tray = new Tray(options.icon);
tray.setIgnoreDoubleClickEvents(true);
tray.setToolTip("Tidal-hifi");
const menu = getMenu(mainWindow);
tray.setContextMenu(menu);
};
trayModule.refreshTray = function (mainWindow) {
if (!tray) {
trayModule.addTray();
}
tray.on("click", function (e) {
if (mainWindow) {
mainWindow.show();
}
});
tray.setToolTip("Tidal-hifi");
if (mainWindow && store.get(settings.minimizeOnClose)) {
tray.setContextMenu(
Menu.buildFromTemplate([
{
label: "Toggle Window",
click: function () {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
},
},
{
label: "Quit",
click: function () {
mainWindow.destroy();
app.quit();
},
},
...mainMenu, //we add menu items from the other context
])
);
} else {
tray.setContextMenu(getMenu());
trayModule.addTray(mainWindow);
}
};