mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-12 02:51:46 +00:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d775e3d588 | |||
| e7ec15e90f | |||
| 403470be69 | |||
| 6dc0ba74c4 | |||
| 6dcfb336c2 | |||
| 84516b2ac1 | |||
| 57cf2a8cdd | |||
| e3ae97fec4 | |||
| ee76e2cb45 | |||
| de01bb6e75 | |||
| 42668c3e99 | |||
| 05f3c56e47 | |||
| d54977b9ee | |||
| b89fb4dc2f | |||
| a0cf77edfb | |||
| 069f9855d1 | |||
| e3e0775401 | |||
| d255e5ffe1 | |||
| fea460a374 | |||
| 302d3f693f | |||
| 9cc320d74b | |||
| e255777283 | |||
| fe0f213919 | |||
| e888b5c896 | |||
| f27ff52689 | |||
| acbe0ac25d | |||
| c66ff2bf05 | |||
| d089487aa8 | |||
| 6bc1d1606f | |||
| 9df5d921c7 | |||
| 4b1dfa1173 | |||
| f98318e737 | |||
| 7fa1278b31 | |||
| 878ec1f6c1 | |||
| 086048780a | |||
| aff0415816 | |||
| 6040fe1cbd | |||
| 52f4e9d796 | |||
| 09fe80cae7 | |||
| 817b48dc9d | |||
| c6f8c42c45 | |||
| 0535686129 | |||
| 53a77255ca | |||
| c01506dc44 | |||
| 6f5f9386ff | |||
| fddd0607e6 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "12.x"
|
||||
node-version: "14.x"
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
|
||||
14
index.js
14
index.js
@ -207,8 +207,18 @@ app.once("browser-window-created", (event, win) => {
|
||||
// Force user-agent "Firefox Windows" for Google OAuth to work
|
||||
// From https://github.com/firebase/firebase-js-sdk/issues/2478#issuecomment-571356751
|
||||
// Only set on accounts.google.com, otherwise querySelectors in preload scripts fail (?)
|
||||
const userAgent =
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0";
|
||||
// Uses custom user agent to Google alert with a correct device type (https://github.com/th-ch/youtube-music/issues/327)
|
||||
// User agents are from https://developers.whatismybrowser.com/useragents/explore/
|
||||
const userAgents = {
|
||||
mac: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0",
|
||||
windows: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0",
|
||||
linux: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:90.0) Gecko/20100101 Firefox/90.0",
|
||||
}
|
||||
|
||||
const userAgent =
|
||||
is.macOS() ? userAgents.mac :
|
||||
is.windows() ? userAgents.windows :
|
||||
userAgents.linux;
|
||||
|
||||
win.webContents.session.webRequest.onBeforeSendHeaders((details, cb) => {
|
||||
details.requestHeaders["User-Agent"] = userAgent;
|
||||
|
||||
15
package.json
15
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "youtube-music",
|
||||
"productName": "YouTube Music",
|
||||
"version": "1.12.2",
|
||||
"version": "1.13.0",
|
||||
"description": "YouTube Music Desktop App - including custom plugins",
|
||||
"license": "MIT",
|
||||
"repository": "th-ch/youtube-music",
|
||||
@ -60,17 +60,17 @@
|
||||
"release:win": "yarn run clean && electron-builder --win -p always"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20",
|
||||
"node": ">=14.0.0",
|
||||
"npm": "Please use yarn and not npm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cliqz/adblocker-electron": "^1.22.1",
|
||||
"@cliqz/adblocker-electron": "^1.22.5",
|
||||
"@ffmpeg/core": "^0.10.0",
|
||||
"@ffmpeg/ffmpeg": "^0.10.0",
|
||||
"YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.9.0",
|
||||
"async-mutex": "^0.3.1",
|
||||
"browser-id3-writer": "^4.4.0",
|
||||
"chokidar": "^3.5.1",
|
||||
"chokidar": "^3.5.2",
|
||||
"custom-electron-titlebar": "^3.2.7",
|
||||
"discord-rpc": "^3.2.0",
|
||||
"electron-better-web-request": "^1.0.1",
|
||||
@ -79,13 +79,14 @@
|
||||
"electron-localshortcut": "^3.2.1",
|
||||
"electron-store": "^7.0.3",
|
||||
"electron-unhandled": "^3.0.2",
|
||||
"electron-updater": "^4.3.9",
|
||||
"electron-updater": "^4.4.6",
|
||||
"filenamify": "^4.3.0",
|
||||
"md5": "^2.3.0",
|
||||
"mpris-service": "^2.1.2",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-notifier": "^9.0.1",
|
||||
"ytdl-core": "^4.8.3",
|
||||
"ytpl": "^2.2.1"
|
||||
"ytdl-core": "^4.9.1",
|
||||
"ytpl": "^2.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^12.0.8",
|
||||
|
||||
@ -29,6 +29,9 @@ module.exports = (win, {activityTimoutEnabled, activityTimoutTime}) => {
|
||||
songInfo.uploadDate,
|
||||
songInfo.views.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " views"
|
||||
].join(' || '),
|
||||
buttons: [
|
||||
{ label: "Listen Along", url: songInfo.url },
|
||||
]
|
||||
};
|
||||
|
||||
if (songInfo.isPaused) {
|
||||
|
||||
@ -6,8 +6,16 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-item > .yt-simple-endpoint:hover {
|
||||
background-color: var(--ytmusic-menu-item-hover-background-color);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
flex: var(--ytmusic-menu-item-icon_-_flex);
|
||||
margin: var(--ytmusic-menu-item-icon_-_margin);
|
||||
fill: var(--ytmusic-menu-item-icon_-_fill);
|
||||
stroke: var(--iron-icon-stroke-color, none);
|
||||
width: var(--iron-icon-width, 24px);
|
||||
height: var(--iron-icon-height, 24px);
|
||||
animation: var(--iron-icon_-_animation);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<div
|
||||
class="menu-item ytmusic-menu-popup-renderer"
|
||||
class="style-scope menu-item ytmusic-menu-popup-renderer"
|
||||
role="option"
|
||||
tabindex="-1"
|
||||
aria-disabled="false"
|
||||
@ -7,31 +7,39 @@
|
||||
onclick="download()"
|
||||
>
|
||||
<div
|
||||
class="menu-icon yt-icon-container yt-icon ytmusic-toggle-menu-service-item-renderer"
|
||||
id="navigation-endpoint"
|
||||
class="yt-simple-endpoint style-scope ytmusic-menu-navigation-item-renderer"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="pointer-events: none; display: block; width: 100%; height: 100%;"
|
||||
<div
|
||||
class="icon menu-icon style-scope ytmusic-menu-navigation-item-renderer"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
d="M25.462,19.105v6.848H4.515v-6.848H0.489v8.861c0,1.111,0.9,2.012,2.016,2.012h24.967c1.115,0,2.016-0.9,2.016-2.012v-8.861H25.462z"
|
||||
class="style-scope yt-icon" fill="#aaaaaa"
|
||||
/>
|
||||
<path
|
||||
d="M14.62,18.426l-5.764-6.965c0,0-0.877-0.828,0.074-0.828s3.248,0,3.248,0s0-0.557,0-1.416c0-2.449,0-6.906,0-8.723c0,0-0.129-0.494,0.615-0.494c0.75,0,4.035,0,4.572,0c0.536,0,0.524,0.416,0.524,0.416c0,1.762,0,6.373,0,8.742c0,0.768,0,1.266,0,1.266s1.842,0,2.998,0c1.154,0,0.285,0.867,0.285,0.867s-4.904,6.51-5.588,7.193C15.092,18.979,14.62,18.426,14.62,18.426z"
|
||||
class="style-scope yt-icon" fill="#aaaaaa"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="text style-scope ytmusic-toggle-menu-service-item-renderer"
|
||||
id="ytmcustom-download"
|
||||
>
|
||||
Download
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false"
|
||||
class="style-scope yt-icon"
|
||||
style="pointer-events: none; display: block; width: 100%; height: 100%"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path
|
||||
d="M25.462,19.105v6.848H4.515v-6.848H0.489v8.861c0,1.111,0.9,2.012,2.016,2.012h24.967c1.115,0,2.016-0.9,2.016-2.012v-8.861H25.462z"
|
||||
class="style-scope yt-icon"
|
||||
fill="#aaaaaa"
|
||||
/>
|
||||
<path
|
||||
d="M14.62,18.426l-5.764-6.965c0,0-0.877-0.828,0.074-0.828s3.248,0,3.248,0s0-0.557,0-1.416c0-2.449,0-6.906,0-8.723c0,0-0.129-0.494,0.615-0.494c0.75,0,4.035,0,4.572,0c0.536,0,0.524,0.416,0.524,0.416c0,1.762,0,6.373,0,8.742c0,0.768,0,1.266,0,1.266s1.842,0,2.998,0c1.154,0,0.285,0.867,0.285,0.867s-4.904,6.51-5.588,7.193C15.092,18.979,14.62,18.426,14.62,18.426z"
|
||||
class="style-scope yt-icon"
|
||||
fill="#aaaaaa"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="text style-scope ytmusic-menu-navigation-item-renderer"
|
||||
id="ytmcustom-download"
|
||||
>
|
||||
Download
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -15,7 +15,7 @@ const ytdl = require("ytdl-core");
|
||||
const { triggerAction, triggerActionSync } = require("../utils");
|
||||
const { ACTIONS, CHANNEL } = require("./actions.js");
|
||||
const { getFolder, urlToJPG } = require("./utils");
|
||||
const { cleanupArtistName } = require("../../providers/song-info");
|
||||
const { cleanupName } = require("../../providers/song-info");
|
||||
|
||||
const { createFFmpeg } = FFmpeg;
|
||||
const ffmpeg = createFFmpeg({
|
||||
@ -40,7 +40,10 @@ const downloadVideoToMP3 = async (
|
||||
const { videoDetails } = await ytdl.getInfo(videoUrl);
|
||||
const thumbnails = videoDetails?.thumbnails;
|
||||
metadata = {
|
||||
artist: videoDetails?.media?.artist || cleanupArtistName(videoDetails?.author?.name) || "",
|
||||
artist:
|
||||
videoDetails?.media?.artist ||
|
||||
cleanupName(videoDetails?.author?.name) ||
|
||||
"",
|
||||
title: videoDetails?.media?.song || videoDetails?.title || "",
|
||||
imageSrcYTPL: thumbnails ?
|
||||
urlToJPG(thumbnails[thumbnails.length - 1].url, videoDetails?.videoId)
|
||||
|
||||
52
plugins/lyrics-genius/back.js
Normal file
52
plugins/lyrics-genius/back.js
Normal file
@ -0,0 +1,52 @@
|
||||
const { join } = require("path");
|
||||
|
||||
const { ipcMain } = require("electron");
|
||||
const is = require("electron-is");
|
||||
const fetch = require("node-fetch");
|
||||
|
||||
const { cleanupName } = require("../../providers/song-info");
|
||||
const { injectCSS } = require("../utils");
|
||||
|
||||
module.exports = async (win) => {
|
||||
injectCSS(win.webContents, join(__dirname, "style.css"));
|
||||
|
||||
ipcMain.on("search-genius-lyrics", async (event, extractedSongInfo) => {
|
||||
const metadata = JSON.parse(extractedSongInfo);
|
||||
const queryString = `${cleanupName(metadata.artist)} ${cleanupName(
|
||||
metadata.title
|
||||
)}`;
|
||||
|
||||
let response = await fetch(
|
||||
`https://genius.com/api/search/multi?per_page=5&q=${encodeURI(
|
||||
queryString
|
||||
)}`
|
||||
);
|
||||
if (!response.ok) {
|
||||
event.returnValue = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await response.json();
|
||||
let url = "";
|
||||
try {
|
||||
url = info.response.sections.filter(
|
||||
(section) => section.type === "song"
|
||||
)[0].hits[0].result.url;
|
||||
} catch {
|
||||
event.returnValue = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (is.dev()) {
|
||||
console.log("Fetching lyrics from Genius:", url);
|
||||
}
|
||||
|
||||
response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
event.returnValue = null;
|
||||
return;
|
||||
}
|
||||
|
||||
event.returnValue = await response.text();
|
||||
});
|
||||
};
|
||||
65
plugins/lyrics-genius/front.js
Normal file
65
plugins/lyrics-genius/front.js
Normal file
@ -0,0 +1,65 @@
|
||||
const { ipcRenderer } = require("electron");
|
||||
|
||||
module.exports = () => {
|
||||
ipcRenderer.on("update-song-info", (_, extractedSongInfo) => {
|
||||
const lyricsTab = document.querySelector('tp-yt-paper-tab[tabindex="-1"]');
|
||||
|
||||
// Check if disabled
|
||||
if (!lyricsTab || !lyricsTab.hasAttribute("disabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const html = ipcRenderer.sendSync(
|
||||
"search-genius-lyrics",
|
||||
extractedSongInfo
|
||||
);
|
||||
if (!html) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.innerHTML = html;
|
||||
const lyricsSelector1 = wrapper.querySelector(".lyrics");
|
||||
const lyricsSelector2 = wrapper.querySelector(
|
||||
'[class^="Lyrics__Container"]'
|
||||
);
|
||||
const lyrics = lyricsSelector1
|
||||
? lyricsSelector1.innerHTML
|
||||
: lyricsSelector2
|
||||
? lyricsSelector2.innerHTML
|
||||
: null;
|
||||
if (!lyrics) {
|
||||
return;
|
||||
}
|
||||
|
||||
lyricsTab.removeAttribute("disabled");
|
||||
lyricsTab.removeAttribute("aria-disabled");
|
||||
document.querySelector("tp-yt-paper-tab").onclick = () => {
|
||||
lyricsTab.removeAttribute("disabled");
|
||||
lyricsTab.removeAttribute("aria-disabled");
|
||||
};
|
||||
|
||||
lyricsTab.onclick = () => {
|
||||
const tabContainer = document.querySelector("ytmusic-tab-renderer");
|
||||
console.log("tabContainer", tabContainer);
|
||||
const observer = new MutationObserver((_, observer) => {
|
||||
const lyricsContainer = document.querySelector(
|
||||
'[page-type="MUSIC_PAGE_TYPE_TRACK_LYRICS"] > ytmusic-message-renderer'
|
||||
);
|
||||
if (lyricsContainer) {
|
||||
lyricsContainer.innerHTML = `<div id="contents" class="style-scope ytmusic-section-list-renderer genius-lyrics">
|
||||
${lyrics}
|
||||
|
||||
<yt-formatted-string class="footer style-scope ytmusic-description-shelf-renderer">Source : Genius</yt-formatted-string>
|
||||
</div>`;
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
observer.observe(tabContainer, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
};
|
||||
});
|
||||
};
|
||||
7
plugins/lyrics-genius/style.css
Normal file
7
plugins/lyrics-genius/style.css
Normal file
@ -0,0 +1,7 @@
|
||||
/* Disable links in Genius lyrics */
|
||||
.genius-lyrics a {
|
||||
color: var(--ytmusic-text-primary);
|
||||
display: inline-block;
|
||||
pointer-events: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
@ -1,79 +1,87 @@
|
||||
<div
|
||||
class="menu-item ytmusic-menu-popup-renderer"
|
||||
class="style-scope menu-item ytmusic-menu-popup-renderer"
|
||||
role="option"
|
||||
tabindex="-1"
|
||||
aria-disabled="false"
|
||||
aria-selected="false"
|
||||
>
|
||||
<tp-yt-paper-slider
|
||||
id="playback-speed-slider"
|
||||
class="volume-slider style-scope ytmusic-player-bar on-hover"
|
||||
max="100"
|
||||
min="0"
|
||||
step="5"
|
||||
dir="ltr"
|
||||
title="Playback speed"
|
||||
aria-label="Playback speed"
|
||||
role="slider"
|
||||
tabindex="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="50"
|
||||
aria-disabled="false"
|
||||
value="50"
|
||||
><!--css-build:shady-->
|
||||
<div id="sliderContainer" class="style-scope tp-yt-paper-slider">
|
||||
<div class="bar-container style-scope tp-yt-paper-slider">
|
||||
<tp-yt-paper-progress
|
||||
id="sliderBar"
|
||||
aria-hidden="true"
|
||||
class="style-scope tp-yt-paper-slider"
|
||||
role="progressbar"
|
||||
value="50"
|
||||
aria-valuenow="50"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-disabled="false"
|
||||
style="touch-action: none"
|
||||
><!--css-build:shady-->
|
||||
<div
|
||||
id="navigation-endpoint"
|
||||
class="yt-simple-endpoint style-scope ytmusic-menu-navigation-item-renderer"
|
||||
tabindex="-1"
|
||||
>
|
||||
<tp-yt-paper-slider
|
||||
id="playback-speed-slider"
|
||||
class="volume-slider style-scope ytmusic-player-bar on-hover"
|
||||
max="100"
|
||||
min="0"
|
||||
step="5"
|
||||
dir="ltr"
|
||||
title="Playback speed"
|
||||
aria-label="Playback speed"
|
||||
role="slider"
|
||||
tabindex="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="50"
|
||||
aria-disabled="false"
|
||||
value="50"
|
||||
><!--css-build:shady-->
|
||||
<div id="sliderContainer" class="style-scope tp-yt-paper-slider">
|
||||
<div class="bar-container style-scope tp-yt-paper-slider">
|
||||
<tp-yt-paper-progress
|
||||
id="sliderBar"
|
||||
aria-hidden="true"
|
||||
class="style-scope tp-yt-paper-slider"
|
||||
role="progressbar"
|
||||
value="50"
|
||||
aria-valuenow="50"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-disabled="false"
|
||||
style="touch-action: none"
|
||||
><!--css-build:shady-->
|
||||
|
||||
<div id="progressContainer" class="style-scope tp-yt-paper-progress">
|
||||
<div
|
||||
id="secondaryProgress"
|
||||
id="progressContainer"
|
||||
class="style-scope tp-yt-paper-progress"
|
||||
hidden="true"
|
||||
style="transform: scaleX(0)"
|
||||
></div>
|
||||
<div
|
||||
id="primaryProgress"
|
||||
class="style-scope tp-yt-paper-progress"
|
||||
style="transform: scaleX(0.5)"
|
||||
></div>
|
||||
</div>
|
||||
</tp-yt-paper-progress>
|
||||
>
|
||||
<div
|
||||
id="secondaryProgress"
|
||||
class="style-scope tp-yt-paper-progress"
|
||||
hidden="true"
|
||||
style="transform: scaleX(0)"
|
||||
></div>
|
||||
<div
|
||||
id="primaryProgress"
|
||||
class="style-scope tp-yt-paper-progress"
|
||||
style="transform: scaleX(0.5)"
|
||||
></div>
|
||||
</div>
|
||||
</tp-yt-paper-progress>
|
||||
</div>
|
||||
<dom-if class="style-scope tp-yt-paper-slider"
|
||||
><template is="dom-if"></template
|
||||
></dom-if>
|
||||
<div
|
||||
id="sliderKnob"
|
||||
class="slider-knob style-scope tp-yt-paper-slider"
|
||||
style="left: 50%; touch-action: none"
|
||||
>
|
||||
<div
|
||||
class="slider-knob-inner style-scope tp-yt-paper-slider"
|
||||
value="50"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<dom-if class="style-scope tp-yt-paper-slider"
|
||||
><template is="dom-if"></template
|
||||
></dom-if>
|
||||
<div
|
||||
id="sliderKnob"
|
||||
class="slider-knob style-scope tp-yt-paper-slider"
|
||||
style="left: 50%; touch-action: none"
|
||||
>
|
||||
<div
|
||||
class="slider-knob-inner style-scope tp-yt-paper-slider"
|
||||
value="50"
|
||||
></div>
|
||||
</div>
|
||||
><template is="dom-if"></template></dom-if
|
||||
></tp-yt-paper-slider>
|
||||
<div
|
||||
class="text style-scope ytmusic-menu-navigation-item-renderer"
|
||||
id="ytmcustom-playback-speed"
|
||||
>
|
||||
Speed (<span id="playback-speed-value">1</span>)
|
||||
</div>
|
||||
<dom-if class="style-scope tp-yt-paper-slider"
|
||||
><template is="dom-if"></template></dom-if
|
||||
></tp-yt-paper-slider>
|
||||
|
||||
<div
|
||||
class="text style-scope ytmusic-toggle-menu-service-item-renderer"
|
||||
id="ytmcustom-playback-speed"
|
||||
>
|
||||
Speed (<span id="playback-speed-value">1</span>)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
const { globalShortcut } = require("electron");
|
||||
const is = require("electron-is");
|
||||
const electronLocalshortcut = require("electron-localshortcut");
|
||||
|
||||
const getSongControls = require("../../providers/song-controls");
|
||||
const { setupMPRIS } = require("./mpris");
|
||||
|
||||
function _registerGlobalShortcut(webContents, shortcut, action) {
|
||||
globalShortcut.register(shortcut, () => {
|
||||
@ -25,6 +27,22 @@ function registerShortcuts(win, options) {
|
||||
_registerLocalShortcut(win, "CommandOrControl+F", search);
|
||||
_registerLocalShortcut(win, "CommandOrControl+L", search);
|
||||
|
||||
if (is.linux()) {
|
||||
try {
|
||||
const player = setupMPRIS();
|
||||
|
||||
player.on("raise", () => {
|
||||
win.setSkipTaskbar(false);
|
||||
win.show();
|
||||
});
|
||||
player.on("playpause", playPause);
|
||||
player.on("next", next);
|
||||
player.on("previous", previous);
|
||||
} catch (e) {
|
||||
console.warn("Error in MPRIS", e);
|
||||
}
|
||||
}
|
||||
|
||||
const { global, local } = options;
|
||||
(global || []).forEach(({ shortcut, action }) => {
|
||||
console.debug("Registering global shortcut", shortcut, ":", action);
|
||||
|
||||
19
plugins/shortcuts/mpris.js
Normal file
19
plugins/shortcuts/mpris.js
Normal file
@ -0,0 +1,19 @@
|
||||
const mpris = require("mpris-service");
|
||||
|
||||
function setupMPRIS() {
|
||||
const player = mpris({
|
||||
name: "youtube-music",
|
||||
identity: "YouTube Music",
|
||||
canRaise: true,
|
||||
supportedUriSchemes: ["https"],
|
||||
supportedMimeTypes: ["audio/mpeg"],
|
||||
supportedInterfaces: ["player"],
|
||||
desktopEntry: "youtube-music",
|
||||
});
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupMPRIS,
|
||||
};
|
||||
33
plugins/tuna-obs/back.js
Normal file
33
plugins/tuna-obs/back.js
Normal file
@ -0,0 +1,33 @@
|
||||
const { ipcRenderer } = require("electron");
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const registerCallback = require("../../providers/song-info");
|
||||
|
||||
const post = (data) => {
|
||||
const port = 1608;
|
||||
headers = {'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
'Access-Control-Allow-Origin': '*'}
|
||||
const url = `http://localhost:${port}/`;
|
||||
fetch(url, {method: 'POST', headers, body:JSON.stringify({data})});
|
||||
}
|
||||
|
||||
module.exports = async (win) => {
|
||||
registerCallback((songInfo) => {
|
||||
|
||||
// Register the callback
|
||||
if (songInfo.title.length === 0 && songInfo.artist.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const duration = Number(songInfo.songDuration)*1000
|
||||
const progress = Number(songInfo.elapsedSeconds)*1000
|
||||
const cover_url = songInfo.imageSrc
|
||||
const album_url = songInfo.imageSrc
|
||||
const title = songInfo.title
|
||||
const artists = [songInfo.artist]
|
||||
const status = !songInfo.isPaused ? 'Playing': 'Paused'
|
||||
post({ cover_url, title, artists, status, progress, duration, album_url});
|
||||
})
|
||||
}
|
||||
@ -55,8 +55,9 @@ const songInfo = {
|
||||
|
||||
const handleData = async (responseText, win) => {
|
||||
let data = JSON.parse(responseText);
|
||||
songInfo.title = data?.videoDetails?.title;
|
||||
songInfo.artist = await getArtist(win) || cleanupArtistName(data?.videoDetails?.author);
|
||||
songInfo.title = cleanupName(data?.videoDetails?.title);
|
||||
songInfo.artist =
|
||||
(await getArtist(win)) || cleanupName(data?.videoDetails?.author);
|
||||
songInfo.views = data?.videoDetails?.viewCount;
|
||||
songInfo.imageSrc = data?.videoDetails?.thumbnail?.thumbnails?.pop()?.url;
|
||||
songInfo.songDuration = data?.videoDetails?.lengthSeconds;
|
||||
@ -98,8 +99,15 @@ const registerProvider = (win) => {
|
||||
});
|
||||
};
|
||||
|
||||
const suffixesToRemove = [' - Topic', 'VEVO'];
|
||||
function cleanupArtistName(artist) {
|
||||
const suffixesToRemove = [
|
||||
" - Topic",
|
||||
"VEVO",
|
||||
" (Performance Video)",
|
||||
" (Official Music Video)",
|
||||
" (Official Video)",
|
||||
" (Clip officiel)",
|
||||
];
|
||||
function cleanupName(artist) {
|
||||
if (!artist) {
|
||||
return artist;
|
||||
}
|
||||
@ -114,4 +122,4 @@ function cleanupArtistName(artist) {
|
||||
module.exports = registerCallback;
|
||||
module.exports.setupSongInfo = registerProvider;
|
||||
module.exports.getImage = getImage;
|
||||
module.exports.cleanupArtistName = cleanupArtistName;
|
||||
module.exports.cleanupName = cleanupName;
|
||||
|
||||
21
readme.md
21
readme.md
@ -34,15 +34,22 @@ You can check out the [latest release](https://github.com/th-ch/youtube-music/re
|
||||
Install the `youtube-music-bin` package from the AUR. For AUR installation instructions, take a look at this [wiki page](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages).
|
||||
|
||||
## Available plugins:
|
||||
|
||||
- **Ad Blocker**: block all ads and tracking out of the box
|
||||
- **Downloader**: download to MP3 directly from the interface (youtube-dl)
|
||||
- **No Google Login**: remove Google login buttons and links from the interface
|
||||
- **Shortcuts**: use your usual shortcuts (media keys, Ctrl/CMD + F…) to control YouTube Music
|
||||
- **Navigation**: next/back navigation arrows directly integrated in the interface, like in your favorite browser
|
||||
- **Auto confirm when paused**: when the "Continue Watching?" modal appears, automatically click "Yes"
|
||||
- **Auto confirm when paused**: when the ["Continue Watching?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png) modal appears, automatically click "Yes"
|
||||
- **Disable autoplay**: makes every song start in "paused" mode
|
||||
- [**Discord**](https://discord.com/): show your friends what you listen to with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
|
||||
- **Downloader**: downloads MP3 [directly from the interface](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
|
||||
- **Hide video player**: no video in the interface when playing music
|
||||
- **Notifications**: display a notification when a song starts playing
|
||||
- **In-app menu**: [gives bars a fancy, dark look](https://user-images.githubusercontent.com/78568641/112215894-923dbf00-8c29-11eb-95c3-3ce15db27eca.png)
|
||||
- [**Last.fm**](https://www.last.fm/): scrobbles support
|
||||
- **Navigation**: next/back navigation arrows directly integrated in the interface, like in your favorite browser
|
||||
- **No Google Login**: remove Google login buttons and links from the interface
|
||||
- **Notifications**: display a [notification](https://user-images.githubusercontent.com/78568641/114102651-63ce0e00-98d0-11eb-9dfe-c5a02bb54f9c.png) when a song starts playing
|
||||
- **Playback speed**: listen fast, listen slow! [Adds a slider that controls song speed](https://user-images.githubusercontent.com/61631665/129976003-e55db5ba-bf42-448c-a059-26a009775e68.png)
|
||||
- **Precise volume**: customizable volume steps for more comfort, allows controlling the volume precisely using mousewheel
|
||||
- **Shortcuts**: use your usual shortcuts (media keys, Ctrl/CMD + F…) to control YouTube Music, you may setup custom global hotkeys for play/pause/next/previous song
|
||||
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): skips non-music parts
|
||||
- **Taskbar media control**: control app from your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
|
||||
- **Touchbar**: custom TouchBar layout for macOS
|
||||
|
||||
## Dev
|
||||
|
||||
Reference in New Issue
Block a user