mirror of
https://github.com/Mastermindzh/tidal-hifi.git
synced 2024-11-23 22:12:57 +01:00
did some more work. Mainly worked on code cleanliness and hotkey support (https://defkey.com/tidal-desktop-shortcuts)
This commit is contained in:
parent
cbf15965e4
commit
9c3ac88d12
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,2 @@
|
||||
node_modules
|
||||
node_modules
|
||||
dist
|
||||
|
23
LICENSE
Normal file
23
LICENSE
Normal file
@ -0,0 +1,23 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Rick van Lieshout (Mastermindzh)
|
||||
|
||||
Online version: https://choosealicense.com/licenses/mit/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
48
README.md
48
README.md
@ -1,23 +1,59 @@
|
||||
# tidal-hifi-electron
|
||||
<img src = "./build/icon.png" height="50" style="float:right; margin-top: 29px;" />
|
||||
# Tidal-hifi
|
||||
|
||||
The web version of [listen.tidal.com](listen.tidal.com) running in electron with hifi support thanks to widevine.
|
||||
|
||||
![tidal-hifi preview](./docs/preview.png)
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Using releases](#using-releases)
|
||||
- [Using source](#using-source)
|
||||
- [Why](#why)
|
||||
- [Requirements](#requirements)
|
||||
- [Integrations](#integrations)
|
||||
- [Why not extend existing projects?](#why-not-extend-existing-projects)
|
||||
- [Special thanks to..](#special-thanks-to)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Installation
|
||||
|
||||
### Using releases
|
||||
|
||||
Various packaged versions of the software are available on the [releases](https://github.com/Mastermindzh/tidal-hifi/releases) tab.
|
||||
|
||||
### Using source
|
||||
|
||||
To install and work with the code on this project follow these steps:
|
||||
|
||||
- git clone https://github.com/Mastermindzh/tidal-hifi.git
|
||||
- cd tidal-hifi
|
||||
- npm install
|
||||
- npm start
|
||||
|
||||
## Why
|
||||
|
||||
I moved from Spotify over to Tidal and found Linux support to be lacking.
|
||||
|
||||
When I started this project there weren't any Linux apps that offered Tidal's "hifi" options nor any scripts to control it.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Internet connection
|
||||
|
||||
## Integrations
|
||||
|
||||
- [i3 blocks config]() - My dotfiles where I use this app to fetch currently playing music
|
||||
|
||||
## Why not extend existing projects?
|
||||
|
||||
Whilst there are a handful of projects attempting to run Tidal on Electron they are all unappealing to me because of various reasons:
|
||||
|
||||
- Lack of a maintainers/developers. (no hotfixes, no issues being handled etc)
|
||||
- Most are simple web wrappers, not my cup of tea.
|
||||
- Some are DE oriented. I want this to work on WM's too.
|
||||
- None have widevine working at the moment and that is really the hardest part..
|
||||
|
||||
Sometimes it's just easier to start over, cover my own needs and then making it available to the public :)
|
||||
|
||||
## Special thanks to..
|
||||
|
||||
- [Castlabs](https://castlabs.com/)
|
||||
For maintaining Electron with Widevine CDM installation, Verified Media Path (VMP), and persistent licenses (StorageID)
|
||||
|
17
build/electron-builder.yml
Normal file
17
build/electron-builder.yml
Normal file
@ -0,0 +1,17 @@
|
||||
appId: com.rickvanlieshout.tidal-hifi
|
||||
snap:
|
||||
plugs:
|
||||
- default
|
||||
- screen-inhibit-control
|
||||
linux:
|
||||
category: Audio
|
||||
target:
|
||||
- pacman
|
||||
- tar.gz
|
||||
- deb
|
||||
- AppImage
|
||||
- snap
|
||||
mac:
|
||||
category: public.app-category.entertainment
|
||||
win:
|
||||
target: msi
|
BIN
build/icon.ico
Normal file
BIN
build/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 103 KiB |
BIN
build/icon.png
Normal file
BIN
build/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
BIN
docs/preview.png
Normal file
BIN
docs/preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 800 KiB |
1417
package-lock.json
generated
1417
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "tidal-hifi-electron",
|
||||
"name": "tidal-hifi",
|
||||
"version": "0.1.0",
|
||||
"description": "Tidal on Electron with widevine(hifi) support",
|
||||
"main": "main.js",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
"start": "electron ."
|
||||
"start": "electron .",
|
||||
"build": "electron-builder -c ./build/electron-builder.yml"
|
||||
},
|
||||
"keywords": [
|
||||
"electron",
|
||||
@ -13,13 +14,14 @@
|
||||
"linux"
|
||||
],
|
||||
"author": "Rick van Lieshout <info@rickvanlieshout.com> (http://rickvanlieshout.com)",
|
||||
"license": "ISC",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"electron": "https://github.com/castlabs/electron-releases#v6.0.9-wvvmp",
|
||||
"hotkeys-js": "^3.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mastermindzh/prettier-config": "^1.0.0",
|
||||
"electron": "https://github.com/castlabs/electron-releases#v6.0.12-wvvmp",
|
||||
"electron-builder": "^21.2.0",
|
||||
"electron-reload": "^1.5.0",
|
||||
"prettier": "^1.18.2"
|
||||
},
|
||||
|
68
preload.js
68
preload.js
@ -1,68 +0,0 @@
|
||||
const hotkeys = require("hotkeys-js");
|
||||
|
||||
function getNode(string) {
|
||||
return window.document.querySelectorAll(string)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Play or pause the current song
|
||||
*/
|
||||
function playPause() {
|
||||
let controls = getNode('*[class^="playbackControls"]');
|
||||
|
||||
let play = controls.querySelectorAll('*[data-test^="play"]')[0];
|
||||
let pause = controls.querySelectorAll('*[data-test^="pause"]')[0];
|
||||
|
||||
if (play) {
|
||||
play.click();
|
||||
} else {
|
||||
pause.click();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Click a button
|
||||
* @param {*} dataTestValue
|
||||
*/
|
||||
function clickButton(dataTestValue) {
|
||||
getNode(`*[data-test^="${dataTestValue}"]`).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the title of the current song
|
||||
*/
|
||||
function getTitle() {
|
||||
return getNode('*[data-test^="footer-track-title"]').textContent;
|
||||
}
|
||||
|
||||
function getArtists() {
|
||||
return getNode('*[class^="mediaArtists"]').textContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hotkeys
|
||||
*/
|
||||
function addHotKeys() {
|
||||
hotkeys("f4", function(event, handler) {
|
||||
// Prevent the default refresh event under WINDOWS system
|
||||
event.preventDefault();
|
||||
playPause();
|
||||
// clickButton("next");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ipc event listeners
|
||||
*/
|
||||
function addIPCEventListeners() {
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const { ipcRenderer } = require("electron");
|
||||
|
||||
ipcRenderer.on("getPlayInfo", (event, col) => {
|
||||
window.document.querySelectorAll('*[data-test^="next"]')[0].click();
|
||||
alert(`${getTitle()} - ${getArtists()}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addHotKeys();
|
||||
addIPCEventListeners();
|
@ -1,4 +1,4 @@
|
||||
const { app, globalShortcut, BrowserWindow, process } = require("electron");
|
||||
const { app, BrowserWindow } = require("electron");
|
||||
const path = require("path");
|
||||
const tidalUrl = "https://listen.tidal.com";
|
||||
let mainWindow;
|
||||
@ -6,9 +6,11 @@ let mainWindow;
|
||||
/**
|
||||
* Enable live reload in development builds
|
||||
*/
|
||||
require("electron-reload")(__dirname, {
|
||||
electron: require(`${__dirname}/node_modules/electron`),
|
||||
});
|
||||
if (!app.isPackaged) {
|
||||
require("electron-reload")(`${__dirname}`, {
|
||||
electron: require(`${__dirname}/../node_modules/electron`),
|
||||
});
|
||||
}
|
||||
|
||||
function createWindow(options = {}) {
|
||||
// Create the browser window.
|
||||
@ -25,6 +27,8 @@ function createWindow(options = {}) {
|
||||
},
|
||||
});
|
||||
|
||||
mainWindow.setMenuBarVisibility(false);
|
||||
|
||||
// load the Tidal website
|
||||
mainWindow.loadURL(tidalUrl);
|
||||
|
||||
@ -38,9 +42,10 @@ function createWindow(options = {}) {
|
||||
}
|
||||
|
||||
function addGlobalShortcuts() {
|
||||
globalShortcut.register("Control+A", () => {
|
||||
mainWindow.webContents.send("getPlayInfo");
|
||||
});
|
||||
// globalShortcut.register("Control+A", () => {
|
||||
// dialog.showErrorBox("test", "test");
|
||||
// // mainWindow.webContents.send("getPlayInfo");
|
||||
// });
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
165
src/preload.js
Normal file
165
src/preload.js
Normal file
@ -0,0 +1,165 @@
|
||||
const { setTitle, getTitle } = require("./scripts/window-functions");
|
||||
const { dialog } = require("electron").remote;
|
||||
const hotkeys = require("./scripts/hotkeys");
|
||||
|
||||
const elements = {
|
||||
play: '*[data-test="play"]',
|
||||
pause: '*[data-test="pause"]',
|
||||
next: '*[data-test="next"]',
|
||||
previous: 'button[data-test="previous"]',
|
||||
title: '*[data-test^="footer-track-title"]',
|
||||
artists: '*[class^="mediaArtists"]',
|
||||
home: '*[data-test="menu--home"]',
|
||||
back: '[class^="backwardButton"]',
|
||||
forward: '[class^="forwardButton"]',
|
||||
search: '[class^="searchField"]',
|
||||
shuffle: '*[data-test="shuffle"]',
|
||||
repeat: '*[data-test="repeat"]',
|
||||
block: '[class="blockButton"]',
|
||||
account: '*[data-test^="profile-image-button"]',
|
||||
settings: '*[data-test^="open-settings"]',
|
||||
|
||||
get: function(key) {
|
||||
return window.document.querySelector(this[key.toLowerCase()]);
|
||||
},
|
||||
|
||||
getText: function(key) {
|
||||
return this.get(key).textContent;
|
||||
},
|
||||
|
||||
click: function(key) {
|
||||
this.get(key).click();
|
||||
return this;
|
||||
},
|
||||
focus: function(key) {
|
||||
return this.get(key).focus();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Play or pause the current song
|
||||
*/
|
||||
function playPause() {
|
||||
const play = elements.get("play");
|
||||
|
||||
if (play) {
|
||||
elements.click("play");
|
||||
} else {
|
||||
elements.click("pause");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hotkeys for when tidal is focused
|
||||
* Reflects the desktop hotkeys found on:
|
||||
* https://defkey.com/tidal-desktop-shortcuts
|
||||
*/
|
||||
function addHotKeys() {
|
||||
hotkeys.add("Control+p", function() {
|
||||
elements.click("account").click("settings");
|
||||
});
|
||||
hotkeys.add("Control+l", function() {
|
||||
handleLogout();
|
||||
});
|
||||
|
||||
hotkeys.add("Control+h", function() {
|
||||
elements.click("home");
|
||||
});
|
||||
|
||||
hotkeys.add("backspace", function() {
|
||||
elements.click("back");
|
||||
});
|
||||
|
||||
hotkeys.add("shift+backspace", function() {
|
||||
elements.click("forward");
|
||||
});
|
||||
|
||||
hotkeys.add("control+f", function() {
|
||||
elements.focus("search");
|
||||
});
|
||||
|
||||
hotkeys.add("control+u", function() {
|
||||
// reloading window without cache should show the update bar if applicable
|
||||
window.location.reload(true);
|
||||
});
|
||||
|
||||
hotkeys.add("control+left", function() {
|
||||
elements.click("previous");
|
||||
});
|
||||
|
||||
hotkeys.add("control+right", function() {
|
||||
elements.click("next");
|
||||
});
|
||||
|
||||
hotkeys.add("control+right", function() {
|
||||
elements.click("next");
|
||||
});
|
||||
|
||||
hotkeys.add("control+s", function() {
|
||||
elements.click("shuffle");
|
||||
});
|
||||
|
||||
hotkeys.add("control+r", function() {
|
||||
elements.click("repeat");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will ask the user whether he/she wants to log out.
|
||||
* It will log the user out if he/she selects "yes"
|
||||
*/
|
||||
function handleLogout() {
|
||||
const logoutOptions = ["Cancel", "Yes, please", "No, thanks"];
|
||||
|
||||
dialog.showMessageBox(
|
||||
null,
|
||||
{
|
||||
type: "question",
|
||||
title: "Logging out",
|
||||
message: "Are you sure you want to log out?",
|
||||
buttons: logoutOptions,
|
||||
defaultId: 2,
|
||||
},
|
||||
function(response) {
|
||||
if (logoutOptions.indexOf("Yes, please") == response) {
|
||||
for (i = 0; i < window.localStorage.length; i++) {
|
||||
key = window.localStorage.key(i);
|
||||
if (key.startsWith("_TIDAL_activeSession")) {
|
||||
window.localStorage.removeItem(key);
|
||||
i = window.localStorage.length + 1;
|
||||
}
|
||||
}
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ipc event listeners.
|
||||
* Some actions triggered outside of the site need info from the site.
|
||||
*/
|
||||
function addIPCEventListeners() {
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const { ipcRenderer } = require("electron");
|
||||
|
||||
ipcRenderer.on("getPlayInfo", (event, col) => {
|
||||
alert(`${elements.getText("title")} - ${elements.getText("artists")}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update window title
|
||||
*/
|
||||
setInterval(function() {
|
||||
const title = elements.getText("title");
|
||||
const artists = elements.getText("artists");
|
||||
const songDashArtistTitle = `${title} - ${artists}`;
|
||||
if (getTitle() !== songDashArtistTitle) {
|
||||
setTitle(songDashArtistTitle);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
addHotKeys();
|
||||
addIPCEventListeners();
|
11
src/scripts/hotkeys.js
Normal file
11
src/scripts/hotkeys.js
Normal file
@ -0,0 +1,11 @@
|
||||
const hotkeyjs = require("hotkeys-js");
|
||||
const hotkeys = {};
|
||||
|
||||
hotkeys.add = function(keys, func) {
|
||||
hotkeyjs(keys, function(event, args) {
|
||||
event.preventDefault();
|
||||
func(event, args);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = hotkeys;
|
11
src/scripts/window-functions.js
Normal file
11
src/scripts/window-functions.js
Normal file
@ -0,0 +1,11 @@
|
||||
const windowFunctions = {};
|
||||
|
||||
windowFunctions.setTitle = function(title) {
|
||||
window.document.title = title;
|
||||
};
|
||||
|
||||
windowFunctions.getTitle = function() {
|
||||
return window.document.title;
|
||||
};
|
||||
|
||||
module.exports = windowFunctions;
|
Loading…
Reference in New Issue
Block a user