mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-14 20:01:47 +00:00
Merge branch 'master' into patch-1
This commit is contained in:
107
.github/workflows/build.yml
vendored
107
.github/workflows/build.yml
vendored
@ -23,20 +23,40 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Expose yarn config as "$GITHUB_OUTPUT"
|
||||||
id: yarn-cache-dir-path
|
id: yarn-config
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "CACHE_FOLDER=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
# Yarn rotates the downloaded cache archives, @see https://github.com/actions/setup-node/issues/325
|
||||||
id: yarn-cache
|
# Yarn cache is also reusable between arch and os.
|
||||||
|
- name: Restore yarn cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
id: yarn-download-cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
key: yarn-download-cache-${{ hashFiles('yarn.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-yarn-
|
yarn-download-cache-
|
||||||
|
|
||||||
|
# Invalidated on yarn.lock changes
|
||||||
|
- name: Restore yarn install state
|
||||||
|
id: yarn-install-state-cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: .yarn/ci-cache/
|
||||||
|
key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn --frozen-lockfile
|
shell: bash
|
||||||
|
run: |
|
||||||
|
yarn install --immutable --inline-builds
|
||||||
|
env:
|
||||||
|
# CI optimizations. Overrides yarnrc.yml options (or their defaults) in the CI action.
|
||||||
|
YARN_ENABLE_GLOBAL_CACHE: "false" # Use local cache folder to keep downloaded archives
|
||||||
|
YARN_NM_MODE: "hardlinks-local" # Hardlinks-(local|global) reduces io / node_modules size
|
||||||
|
YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz # Very small speedup when lock does not change
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
uses: GabrielBB/xvfb-action@v1
|
uses: GabrielBB/xvfb-action@v1
|
||||||
@ -45,31 +65,48 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
run: yarn test
|
run: yarn test
|
||||||
|
|
||||||
- name: Build on Mac
|
# Build and release if it's the main repository
|
||||||
if: startsWith(matrix.os, 'macOS')
|
- name: Build and release on Mac
|
||||||
|
if: startsWith(matrix.os, 'macOS') && github.repository == 'th-ch/youtube-music'
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
yarn run release:mac
|
yarn run release:mac
|
||||||
|
|
||||||
- name: Build on Linux
|
- name: Build and release on Linux
|
||||||
if: startsWith(matrix.os, 'ubuntu')
|
if: startsWith(matrix.os, 'ubuntu') && github.repository == 'th-ch/youtube-music'
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
yarn run release:linux
|
yarn run release:linux
|
||||||
|
|
||||||
- name: Build on Windows
|
- name: Build and release on Windows
|
||||||
if: startsWith(matrix.os, 'windows')
|
if: startsWith(matrix.os, 'windows') && github.repository == 'th-ch/youtube-music'
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
yarn run release:win
|
yarn run release:win
|
||||||
|
|
||||||
|
# Only build without release if it is a fork
|
||||||
|
- name: Build on Mac
|
||||||
|
if: startsWith(matrix.os, 'macOS') && github.repository != 'th-ch/youtube-music'
|
||||||
|
run: |
|
||||||
|
yarn run build:mac
|
||||||
|
|
||||||
|
- name: Build on Linux
|
||||||
|
if: startsWith(matrix.os, 'ubuntu') && github.repository != 'th-ch/youtube-music'
|
||||||
|
run: |
|
||||||
|
yarn run build:linux
|
||||||
|
|
||||||
|
- name: Build on Windows
|
||||||
|
if: startsWith(matrix.os, 'windows') && github.repository != 'th-ch/youtube-music'
|
||||||
|
run: |
|
||||||
|
yarn run build:win
|
||||||
|
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Release YouTube Music
|
name: Release YouTube Music
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.repository == 'th-ch/youtube-music' && github.ref == 'refs/heads/master'
|
||||||
needs: build
|
needs: build
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@ -81,20 +118,40 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Expose yarn config as "$GITHUB_OUTPUT"
|
||||||
id: yarn-cache-dir-path
|
id: yarn-config
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "CACHE_FOLDER=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
# Yarn rotates the downloaded cache archives, @see https://github.com/actions/setup-node/issues/325
|
||||||
id: yarn-cache
|
# Yarn cache is also reusable between arch and os.
|
||||||
|
- name: Restore yarn cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
id: yarn-download-cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
key: yarn-download-cache-${{ hashFiles('yarn.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-yarn-
|
yarn-download-cache-
|
||||||
|
|
||||||
|
# Invalidated on yarn.lock changes
|
||||||
|
- name: Restore yarn install state
|
||||||
|
id: yarn-install-state-cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: .yarn/ci-cache/
|
||||||
|
key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn --frozen-lockfile
|
shell: bash
|
||||||
|
run: |
|
||||||
|
yarn install --immutable --inline-builds
|
||||||
|
env:
|
||||||
|
# CI optimizations. Overrides yarnrc.yml options (or their defaults) in the CI action.
|
||||||
|
YARN_ENABLE_GLOBAL_CACHE: "false" # Use local cache folder to keep downloaded archives
|
||||||
|
YARN_NM_MODE: "hardlinks-local" # Hardlinks-(local|global) reduces io / node_modules size
|
||||||
|
YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz # Very small speedup when lock does not change
|
||||||
|
|
||||||
- name: Get version
|
- name: Get version
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
9
.gitignore
vendored
9
.gitignore
vendored
@ -3,3 +3,12 @@ node_modules
|
|||||||
/assets/generated
|
/assets/generated
|
||||||
electron-builder.yml
|
electron-builder.yml
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
|
.idea
|
||||||
|
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
|||||||
9
.yarn/plugins/@yarnpkg/plugin-after-install.cjs
vendored
Normal file
9
.yarn/plugins/@yarnpkg/plugin-after-install.cjs
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
//prettier-ignore
|
||||||
|
module.exports = {
|
||||||
|
name: "@yarnpkg/plugin-after-install",
|
||||||
|
factory: function (require) {
|
||||||
|
var plugin=(()=>{var g=Object.create,r=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var k=Object.getPrototypeOf,y=Object.prototype.hasOwnProperty;var I=t=>r(t,"__esModule",{value:!0});var i=t=>{if(typeof require!="undefined")return require(t);throw new Error('Dynamic require of "'+t+'" is not supported')};var h=(t,o)=>{for(var e in o)r(t,e,{get:o[e],enumerable:!0})},w=(t,o,e)=>{if(o&&typeof o=="object"||typeof o=="function")for(let n of C(o))!y.call(t,n)&&n!=="default"&&r(t,n,{get:()=>o[n],enumerable:!(e=x(o,n))||e.enumerable});return t},a=t=>w(I(r(t!=null?g(k(t)):{},"default",t&&t.__esModule&&"default"in t?{get:()=>t.default,enumerable:!0}:{value:t,enumerable:!0})),t);var j={};h(j,{default:()=>b});var c=a(i("@yarnpkg/core")),m={afterInstall:{description:"Hook that will always run after install",type:c.SettingsType.STRING,default:""}};var u=a(i("clipanion")),d=a(i("@yarnpkg/core"));var p=a(i("@yarnpkg/shell")),l=async(t,o)=>{var f;let e=t.get("afterInstall"),n=!!((f=t.projectCwd)==null?void 0:f.endsWith(`dlx-${process.pid}`));return e&&!n?(o&&console.log("Running `afterInstall` hook..."),(0,p.execute)(e,[],{cwd:t.projectCwd||void 0})):0};var s=class extends u.Command{async execute(){let o=await d.Configuration.find(this.context.cwd,this.context.plugins);return l(o,!1)}};s.paths=[["after-install"]];var P={configuration:m,commands:[s],hooks:{afterAllInstalled:async t=>{if(await l(t.configuration,!0))throw new Error("The `afterInstall` hook failed, see output above.")}}},b=P;return j;})();
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
};
|
||||||
873
.yarn/releases/yarn-3.4.1.cjs
vendored
Executable file
873
.yarn/releases/yarn-3.4.1.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
9
.yarnrc.yml
Normal file
9
.yarnrc.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
afterInstall: yarn postinstall
|
||||||
|
|
||||||
|
nodeLinker: node-modules
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- path: .yarn/plugins/@yarnpkg/plugin-after-install.cjs
|
||||||
|
spec: "https://raw.githubusercontent.com/mhassan1/yarn-plugin-after-install/v0.3.1/bundles/@yarnpkg/plugin-after-install.js"
|
||||||
|
|
||||||
|
yarnPath: .yarn/releases/yarn-3.4.1.cjs
|
||||||
20
package.json
20
package.json
@ -71,10 +71,10 @@
|
|||||||
"test:debug": "DEBUG=pw:browser* playwright test",
|
"test:debug": "DEBUG=pw:browser* playwright test",
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"start:debug": "ELECTRON_ENABLE_LOGGING=1 electron .",
|
"start:debug": "ELECTRON_ENABLE_LOGGING=1 electron .",
|
||||||
"icon": "del-cli -f \"assets/generated\" && electron-icon-maker --input=assets/youtube-music.png --output=assets/generated",
|
"icon": "del-cli assets/generated && electron-icon-builder --input=assets/youtube-music.png --output=assets/generated",
|
||||||
"generate:package": "node utils/generate-package-json.js",
|
"generate:package": "node utils/generate-package-json.js",
|
||||||
"postinstall": "yarn run icon && yarn run plugins",
|
"postinstall": "yarn run icon && yarn run plugins",
|
||||||
"clean": "del-cli -f dist",
|
"clean": "del-cli dist",
|
||||||
"build": "yarn run clean && electron-builder --win --mac --linux",
|
"build": "yarn run clean && electron-builder --win --mac --linux",
|
||||||
"build:linux": "yarn run clean && electron-builder --linux",
|
"build:linux": "yarn run clean && electron-builder --linux",
|
||||||
"build:mac": "yarn run clean && electron-builder --mac dmg:x64",
|
"build:mac": "yarn run clean && electron-builder --mac dmg:x64",
|
||||||
@ -83,8 +83,8 @@
|
|||||||
"lint": "xo",
|
"lint": "xo",
|
||||||
"changelog": "auto-changelog",
|
"changelog": "auto-changelog",
|
||||||
"plugins": "yarn run plugin:adblocker && yarn run plugin:bypass-age-restrictions",
|
"plugins": "yarn run plugin:adblocker && yarn run plugin:bypass-age-restrictions",
|
||||||
"plugin:adblocker": "del-cli -f \"plugins/adblocker/ad-blocker-engine.bin\" && node plugins/adblocker/blocker.js",
|
"plugin:adblocker": "del-cli plugins/adblocker/ad-blocker-engine.bin && node plugins/adblocker/blocker.js",
|
||||||
"plugin:bypass-age-restrictions": "yarn run generate:package Simple-YouTube-Age-Restriction-Bypass",
|
"plugin:bypass-age-restrictions": "del-cli node_modules/simple-youtube-age-restriction-bypass/package.json && yarn run generate:package simple-youtube-age-restriction-bypass",
|
||||||
"release:linux": "yarn run clean && electron-builder --linux -p always -c.snap.publish=github",
|
"release:linux": "yarn run clean && electron-builder --linux -p always -c.snap.publish=github",
|
||||||
"release:mac": "yarn run clean && electron-builder --mac -p always",
|
"release:mac": "yarn run clean && electron-builder --mac -p always",
|
||||||
"release:win": "yarn run clean && electron-builder --win -p always"
|
"release:win": "yarn run clean && electron-builder --win -p always"
|
||||||
@ -94,7 +94,7 @@
|
|||||||
"npm": "Please use yarn instead"
|
"npm": "Please use yarn instead"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cliqz/adblocker-electron": "^1.25.1",
|
"@cliqz/adblocker-electron": "^1.25.2",
|
||||||
"@ffmpeg/core": "^0.11.0",
|
"@ffmpeg/core": "^0.11.0",
|
||||||
"@ffmpeg/ffmpeg": "^0.11.6",
|
"@ffmpeg/ffmpeg": "^0.11.6",
|
||||||
"@foobar404/wave": "^2.0.4",
|
"@foobar404/wave": "^2.0.4",
|
||||||
@ -116,11 +116,13 @@
|
|||||||
"filenamify": "^4.3.0",
|
"filenamify": "^4.3.0",
|
||||||
"howler": "^2.2.3",
|
"howler": "^2.2.3",
|
||||||
"html-to-text": "^9.0.3",
|
"html-to-text": "^9.0.3",
|
||||||
|
"keyboardevent-from-electron-accelerator": "^2.0.0",
|
||||||
|
"keyboardevents-areequal": "^0.2.2",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"mpris-service": "^2.1.2",
|
"mpris-service": "^2.1.2",
|
||||||
"node-fetch": "^2.6.8",
|
"node-fetch": "^2.6.8",
|
||||||
"node-notifier": "^10.0.1",
|
"node-notifier": "^10.0.1",
|
||||||
"simple-youtube-age-restriction-bypass": "zerodytrash/Simple-YouTube-Age-Restriction-Bypass#v2.5.4",
|
"simple-youtube-age-restriction-bypass": "https://gitpkg.now.sh/api/pkg.tgz?url=zerodytrash/Simple-YouTube-Age-Restriction-Bypass&commit=v2.5.4",
|
||||||
"vudio": "^2.1.1",
|
"vudio": "^2.1.1",
|
||||||
"youtubei.js": "^2.9.0",
|
"youtubei.js": "^2.9.0",
|
||||||
"ytdl-core": "^4.11.1",
|
"ytdl-core": "^4.11.1",
|
||||||
@ -133,7 +135,8 @@
|
|||||||
"electron": "^22.0.2",
|
"electron": "^22.0.2",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-builder": "^23.6.0",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-icon-maker": "0.0.5",
|
"electron-icon-builder": "^2.0.1",
|
||||||
|
"node-gyp": "^9.3.1",
|
||||||
"playwright": "^1.29.2",
|
"playwright": "^1.29.2",
|
||||||
"xo": "^0.53.1"
|
"xo": "^0.53.1"
|
||||||
},
|
},
|
||||||
@ -158,5 +161,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@3.4.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
module.exports = () => {
|
module.exports = () => {
|
||||||
// See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass#userscript
|
// See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass#userscript
|
||||||
require("Simple-YouTube-Age-Restriction-Bypass/Simple-YouTube-Age-Restriction-Bypass.user.js");
|
require("simple-youtube-age-restriction-bypass/dist/Simple-YouTube-Age-Restriction-Bypass.user.js");
|
||||||
};
|
};
|
||||||
|
|||||||
@ -43,9 +43,18 @@ module.exports = (options) => {
|
|||||||
setNavbarMargin();
|
setNavbarMargin();
|
||||||
const playPageObserver = new MutationObserver(setNavbarMargin);
|
const playPageObserver = new MutationObserver(setNavbarMargin);
|
||||||
playPageObserver.observe($('ytmusic-app-layout'), { attributeFilter: ['player-page-open_', 'playerPageOpen_'] })
|
playPageObserver.observe($('ytmusic-app-layout'), { attributeFilter: ['player-page-open_', 'playerPageOpen_'] })
|
||||||
|
setupSearchOpenObserver();
|
||||||
}, { once: true, passive: true })
|
}, { once: true, passive: true })
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function setupSearchOpenObserver() {
|
||||||
|
const searchOpenObserver = new MutationObserver(mutations => {
|
||||||
|
$('#nav-bar-background').style.webkitAppRegion =
|
||||||
|
mutations[0].target.opened ? 'no-drag' : 'drag';
|
||||||
|
});
|
||||||
|
searchOpenObserver.observe($('ytmusic-search-box'), { attributeFilter: ["opened"] })
|
||||||
|
}
|
||||||
|
|
||||||
function setNavbarMargin() {
|
function setNavbarMargin() {
|
||||||
$('#nav-bar-background').style.right =
|
$('#nav-bar-background').style.right =
|
||||||
$('ytmusic-app-layout').playerPageOpen_ ?
|
$('ytmusic-app-layout').playerPageOpen_ ?
|
||||||
|
|||||||
@ -8,8 +8,7 @@
|
|||||||
#nav-bar-background {
|
#nav-bar-background {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
position: sticky !important;
|
top: 30px !important;
|
||||||
top: 0 !important;
|
|
||||||
height: 75px !important;
|
height: 75px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,3 +79,15 @@ yt-page-navigation-progress,
|
|||||||
.cet-menubar-menu-container .cet-action-item {
|
.cet-menubar-menu-container .cet-action-item {
|
||||||
background-color: inherit
|
background-color: inherit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nav-bar-background {
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
ytmusic-nav-bar input,
|
||||||
|
ytmusic-nav-bar span,
|
||||||
|
ytmusic-nav-bar [role="button"],
|
||||||
|
ytmusic-nav-bar yt-icon,
|
||||||
|
tp-yt-iron-dropdown {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,37 +0,0 @@
|
|||||||
const { Menu, app } = require("electron");
|
|
||||||
const { setApplicationMenu } = require("../../../menu");
|
|
||||||
|
|
||||||
module.exports = (win, options, setOptions, togglePip, isInPip) => {
|
|
||||||
if (isInPip) {
|
|
||||||
Menu.setApplicationMenu(Menu.buildFromTemplate([
|
|
||||||
{
|
|
||||||
label: "App",
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: "Exit Picture in Picture",
|
|
||||||
click: togglePip,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Always on top",
|
|
||||||
type: "checkbox",
|
|
||||||
checked: options.alwaysOnTop,
|
|
||||||
click: (item) => {
|
|
||||||
setOptions({ alwaysOnTop: item.checked });
|
|
||||||
win.setAlwaysOnTop(item.checked);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Restart",
|
|
||||||
click: () => {
|
|
||||||
app.relaunch();
|
|
||||||
app.quit();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ role: "quit" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]));
|
|
||||||
} else {
|
|
||||||
setApplicationMenu(win);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -3,7 +3,7 @@ const path = require("path");
|
|||||||
const { app, ipcMain } = require("electron");
|
const { app, ipcMain } = require("electron");
|
||||||
const electronLocalshortcut = require("electron-localshortcut");
|
const electronLocalshortcut = require("electron-localshortcut");
|
||||||
|
|
||||||
const { setOptions, isEnabled } = require("../../config/plugins");
|
const { setOptions } = require("../../config/plugins");
|
||||||
const { injectCSS } = require("../utils");
|
const { injectCSS } = require("../utils");
|
||||||
|
|
||||||
let isInPiP = false;
|
let isInPiP = false;
|
||||||
@ -23,15 +23,6 @@ const setLocalOptions = (_options) => {
|
|||||||
setOptions("picture-in-picture", _options);
|
setOptions("picture-in-picture", _options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const adaptors = [];
|
|
||||||
const runAdaptors = () => adaptors.forEach(a => a());
|
|
||||||
|
|
||||||
if (isEnabled("in-app-menu")) {
|
|
||||||
let adaptor = require("./adaptors/in-app-menu");
|
|
||||||
adaptors.push(() => adaptor(win, options, setLocalOptions, togglePiP, isInPiP));
|
|
||||||
}
|
|
||||||
|
|
||||||
const togglePiP = async () => {
|
const togglePiP = async () => {
|
||||||
isInPiP = !isInPiP;
|
isInPiP = !isInPiP;
|
||||||
setLocalOptions({ isInPiP });
|
setLocalOptions({ isInPiP });
|
||||||
@ -50,7 +41,6 @@ const togglePiP = async () => {
|
|||||||
win.setMaximizable(false);
|
win.setMaximizable(false);
|
||||||
win.setFullScreenable(false);
|
win.setFullScreenable(false);
|
||||||
|
|
||||||
runAdaptors();
|
|
||||||
win.webContents.send("pip-toggle", true);
|
win.webContents.send("pip-toggle", true);
|
||||||
|
|
||||||
app.dock?.hide();
|
app.dock?.hide();
|
||||||
@ -66,7 +56,6 @@ const togglePiP = async () => {
|
|||||||
win.setMaximizable(true);
|
win.setMaximizable(true);
|
||||||
win.setFullScreenable(true);
|
win.setFullScreenable(true);
|
||||||
|
|
||||||
runAdaptors();
|
|
||||||
win.webContents.send("pip-toggle", false);
|
win.webContents.send("pip-toggle", false);
|
||||||
|
|
||||||
win.setVisibleOnAllWorkspaces(false);
|
win.setVisibleOnAllWorkspaces(false);
|
||||||
@ -103,9 +92,6 @@ module.exports = (_win, _options) => {
|
|||||||
ipcMain.on("picture-in-picture", async () => {
|
ipcMain.on("picture-in-picture", async () => {
|
||||||
await togglePiP();
|
await togglePiP();
|
||||||
});
|
});
|
||||||
if (options.hotkey) {
|
|
||||||
electronLocalshortcut.register(win, options.hotkey, togglePiP);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.setOptions = setLocalOptions;
|
module.exports.setOptions = setLocalOptions;
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
const { ipcRenderer } = require("electron");
|
const { ipcRenderer } = require("electron");
|
||||||
|
|
||||||
|
const { toKeyEvent } = require("keyboardevent-from-electron-accelerator");
|
||||||
|
const keyEventAreEqual = require("keyboardevents-areequal");
|
||||||
|
|
||||||
const { getSongMenu } = require("../../providers/dom-elements");
|
const { getSongMenu } = require("../../providers/dom-elements");
|
||||||
const { ElementFromFile, templatePath } = require("../utils");
|
const { ElementFromFile, templatePath } = require("../utils");
|
||||||
|
|
||||||
function $(selector) { return document.querySelector(selector); }
|
function $(selector) { return document.querySelector(selector); }
|
||||||
|
|
||||||
|
let useNativePiP = false;
|
||||||
let menu = null;
|
let menu = null;
|
||||||
const pipButton = ElementFromFile(
|
const pipButton = ElementFromFile(
|
||||||
templatePath(__dirname, "picture-in-picture.html")
|
templatePath(__dirname, "picture-in-picture.html")
|
||||||
@ -39,8 +43,24 @@ const observer = new MutationObserver(() => {
|
|||||||
menu.prepend(pipButton);
|
menu.prepend(pipButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
global.togglePictureInPicture = () => {
|
global.togglePictureInPicture = async () => {
|
||||||
|
if (useNativePiP) {
|
||||||
|
const isInPiP = document.pictureInPictureElement !== null;
|
||||||
|
const video = $("video");
|
||||||
|
const togglePiP = () =>
|
||||||
|
isInPiP
|
||||||
|
? document.exitPictureInPicture.call(document)
|
||||||
|
: video.requestPictureInPicture.call(video);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await togglePiP();
|
||||||
|
$("#icon").click(); // Close the menu
|
||||||
|
return true;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
ipcRenderer.send("picture-in-picture");
|
ipcRenderer.send("picture-in-picture");
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const listenForToggle = () => {
|
const listenForToggle = () => {
|
||||||
@ -54,9 +74,12 @@ const listenForToggle = () => {
|
|||||||
const player = $('#player');
|
const player = $('#player');
|
||||||
const onPlayerDblClick = player.onDoubleClick_;
|
const onPlayerDblClick = player.onDoubleClick_;
|
||||||
|
|
||||||
ipcRenderer.on('pip-toggle', (_, isPip) => {
|
const titlebar = $(".cet-titlebar");
|
||||||
|
|
||||||
|
ipcRenderer.on("pip-toggle", (_, isPip) => {
|
||||||
if (isPip) {
|
if (isPip) {
|
||||||
replaceButton(".exit-fullscreen-button", originalExitButton).onclick = () => togglePictureInPicture();
|
replaceButton(".exit-fullscreen-button", originalExitButton).onclick =
|
||||||
|
() => togglePictureInPicture();
|
||||||
player.onDoubleClick_ = () => {};
|
player.onDoubleClick_ = () => {};
|
||||||
expandMenu.onmouseleave = () => middleControls.click();
|
expandMenu.onmouseleave = () => middleControls.click();
|
||||||
if (!playerPage.playerPageOpen_) {
|
if (!playerPage.playerPageOpen_) {
|
||||||
@ -64,25 +87,28 @@ const listenForToggle = () => {
|
|||||||
}
|
}
|
||||||
fullScreenButton.click();
|
fullScreenButton.click();
|
||||||
appLayout.classList.add("pip");
|
appLayout.classList.add("pip");
|
||||||
|
if (titlebar) titlebar.style.display = "none";
|
||||||
} else {
|
} else {
|
||||||
$(".exit-fullscreen-button").replaceWith(originalExitButton);
|
$(".exit-fullscreen-button").replaceWith(originalExitButton);
|
||||||
player.onDoubleClick_ = onPlayerDblClick;
|
player.onDoubleClick_ = onPlayerDblClick;
|
||||||
expandMenu.onmouseleave = undefined;
|
expandMenu.onmouseleave = undefined;
|
||||||
originalExitButton.click();
|
originalExitButton.click();
|
||||||
appLayout.classList.remove("pip");
|
appLayout.classList.remove("pip");
|
||||||
|
if (titlebar) titlebar.style.display = "flex";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function observeMenu(options) {
|
function observeMenu(options) {
|
||||||
|
useNativePiP = options.useNativePiP;
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"apiLoaded",
|
"apiLoaded",
|
||||||
() => {
|
() => {
|
||||||
listenForToggle();
|
listenForToggle();
|
||||||
// remove native listeners
|
|
||||||
cloneButton(".player-minimize-button").onclick = () => {
|
cloneButton(".player-minimize-button").onclick = async () => {
|
||||||
global.togglePictureInPicture();
|
await global.togglePictureInPicture();
|
||||||
setTimeout(() => $('#player').click());
|
setTimeout(() => $("#player").click());
|
||||||
};
|
};
|
||||||
|
|
||||||
// allows easily closing the menu by programmatically clicking outside of it
|
// allows easily closing the menu by programmatically clicking outside of it
|
||||||
@ -97,4 +123,18 @@ function observeMenu(options) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = observeMenu;
|
module.exports = (options) => {
|
||||||
|
observeMenu(options);
|
||||||
|
|
||||||
|
if (options.hotkey) {
|
||||||
|
const hotkeyEvent = toKeyEvent(options.hotkey);
|
||||||
|
window.addEventListener("keydown", (event) => {
|
||||||
|
if (
|
||||||
|
keyEventAreEqual(event, hotkeyEvent) &&
|
||||||
|
!$("ytmusic-search-box").opened
|
||||||
|
) {
|
||||||
|
togglePictureInPicture();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -56,5 +56,13 @@ module.exports = (win, options) => [
|
|||||||
item.checked = !item.checked;
|
item.checked = !item.checked;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Use native PiP",
|
||||||
|
type: "checkbox",
|
||||||
|
checked: options.useNativePiP,
|
||||||
|
click: (item) => {
|
||||||
|
setOptions({ useNativePiP: item.checked });
|
||||||
|
},
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@ -3,9 +3,9 @@ ytmusic-app-layout.pip ytmusic-player-bar svg,
|
|||||||
ytmusic-app-layout.pip ytmusic-player-bar .time-info,
|
ytmusic-app-layout.pip ytmusic-player-bar .time-info,
|
||||||
ytmusic-app-layout.pip ytmusic-player-bar yt-formatted-string,
|
ytmusic-app-layout.pip ytmusic-player-bar yt-formatted-string,
|
||||||
ytmusic-app-layout.pip ytmusic-player-bar .yt-formatted-string {
|
ytmusic-app-layout.pip ytmusic-player-bar .yt-formatted-string {
|
||||||
filter: drop-shadow(2px 4px 6px black);
|
filter: drop-shadow(2px 4px 6px black);
|
||||||
color: white !important;
|
color: white !important;
|
||||||
fill: white !important;
|
fill: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* improve the style of the player bar expanding menu */
|
/* improve the style of the player bar expanding menu */
|
||||||
@ -20,6 +20,23 @@ ytmusic-app-layout.pip ytmusic-player-expanding-menu {
|
|||||||
top: 22px !important;
|
top: 22px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* make player-bar not draggable if in-app-menu is enabled */
|
||||||
|
.cet-container ytmusic-app-layout.pip ytmusic-player-bar {
|
||||||
|
-webkit-app-region: no-drag !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* make player draggable if in-app-menu is enabled */
|
||||||
|
.cet-container ytmusic-app-layout.pip #player {
|
||||||
|
-webkit-app-region: drag !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove info, thumbnail and menu from player-bar */
|
||||||
|
ytmusic-app-layout.pip ytmusic-player-bar .content-info-wrapper,
|
||||||
|
ytmusic-app-layout.pip ytmusic-player-bar .thumbnail-image-wrapper,
|
||||||
|
ytmusic-app-layout.pip ytmusic-player-bar ytmusic-menu-renderer {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* disable the video-toggle button when in PiP mode */
|
/* disable the video-toggle button when in PiP mode */
|
||||||
ytmusic-app-layout.pip .video-switch-button {
|
ytmusic-app-layout.pip .video-switch-button {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const mpris = require("mpris-service");
|
const mpris = require("mpris-service");
|
||||||
const { ipcMain } = require("electron");
|
const {ipcMain} = require("electron");
|
||||||
const registerCallback = require("../../providers/song-info");
|
const registerCallback = require("../../providers/song-info");
|
||||||
const getSongControls = require("../../providers/song-controls");
|
const getSongControls = require("../../providers/song-controls");
|
||||||
const config = require("../../config");
|
const config = require("../../config");
|
||||||
@ -20,7 +20,7 @@ function setupMPRIS() {
|
|||||||
|
|
||||||
function registerMPRIS(win) {
|
function registerMPRIS(win) {
|
||||||
const songControls = getSongControls(win);
|
const songControls = getSongControls(win);
|
||||||
const { playPause, next, previous, volumeMinus10, volumePlus10 } = songControls;
|
const {playPause, next, previous, volumeMinus10, volumePlus10, shuffle} = songControls;
|
||||||
try {
|
try {
|
||||||
const secToMicro = n => Math.round(Number(n) * 1e6);
|
const secToMicro = n => Math.round(Number(n) * 1e6);
|
||||||
const microToSec = n => Math.round(Number(n) / 1e6);
|
const microToSec = n => Math.round(Number(n) / 1e6);
|
||||||
@ -35,33 +35,23 @@ function registerMPRIS(win) {
|
|||||||
let currentSeconds = 0;
|
let currentSeconds = 0;
|
||||||
ipcMain.on('timeChanged', (_, t) => currentSeconds = t);
|
ipcMain.on('timeChanged', (_, t) => currentSeconds = t);
|
||||||
|
|
||||||
let currentLoopStatus = undefined;
|
|
||||||
let manuallySwitchingStatus = false;
|
|
||||||
ipcMain.on("repeatChanged", (_, mode) => {
|
ipcMain.on("repeatChanged", (_, mode) => {
|
||||||
if (manuallySwitchingStatus)
|
if (mode === "NONE")
|
||||||
return;
|
player.loopStatus = mpris.LOOP_STATUS_NONE;
|
||||||
|
else if (mode === "ONE") //MPRIS Playlist and Track Codes are switched to look the same as yt-music icons
|
||||||
if (mode === "Repeat off")
|
player.loopStatus = mpris.LOOP_STATUS_PLAYLIST;
|
||||||
currentLoopStatus = "None";
|
else if (mode === "ALL")
|
||||||
else if (mode === "Repeat one")
|
player.loopStatus = mpris.LOOP_STATUS_TRACK;
|
||||||
currentLoopStatus = "Track";
|
|
||||||
else if (mode === "Repeat all")
|
|
||||||
currentLoopStatus = "Playlist";
|
|
||||||
|
|
||||||
player.loopStatus = currentLoopStatus;
|
|
||||||
});
|
});
|
||||||
player.on("loopStatus", (status) => {
|
player.on("loopStatus", (status) => {
|
||||||
// switchRepeat cycles between states in that order
|
// switchRepeat cycles between states in that order
|
||||||
const switches = ["None", "Playlist", "Track"];
|
const switches = [mpris.LOOP_STATUS_NONE, mpris.LOOP_STATUS_PLAYLIST, mpris.LOOP_STATUS_TRACK];
|
||||||
const currentIndex = switches.indexOf(currentLoopStatus);
|
const currentIndex = switches.indexOf(player.loopStatus);
|
||||||
const targetIndex = switches.indexOf(status);
|
const targetIndex = switches.indexOf(status);
|
||||||
|
|
||||||
// Get a delta in the range [0,2]
|
// Get a delta in the range [0,2]
|
||||||
const delta = (targetIndex - currentIndex + 3) % 3;
|
const delta = (targetIndex - currentIndex + 3) % 3;
|
||||||
|
|
||||||
manuallySwitchingStatus = true;
|
|
||||||
songControls.switchRepeat(delta);
|
songControls.switchRepeat(delta);
|
||||||
manuallySwitchingStatus = false;
|
|
||||||
})
|
})
|
||||||
|
|
||||||
player.getPosition = () => secToMicro(currentSeconds)
|
player.getPosition = () => secToMicro(currentSeconds)
|
||||||
@ -72,19 +62,19 @@ function registerMPRIS(win) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
player.on("play", () => {
|
player.on("play", () => {
|
||||||
if (player.playbackStatus !== 'Playing') {
|
if (player.playbackStatus !== mpris.PLAYBACK_STATUS_PLAYING) {
|
||||||
player.playbackStatus = 'Playing';
|
player.playbackStatus = mpris.PLAYBACK_STATUS_PLAYING;
|
||||||
playPause()
|
playPause()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
player.on("pause", () => {
|
player.on("pause", () => {
|
||||||
if (player.playbackStatus !== 'Paused') {
|
if (player.playbackStatus !== mpris.PLAYBACK_STATUS_PAUSED) {
|
||||||
player.playbackStatus = 'Paused';
|
player.playbackStatus = mpris.PLAYBACK_STATUS_PAUSED;
|
||||||
playPause()
|
playPause()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
player.on("playpause", () => {
|
player.on("playpause", () => {
|
||||||
player.playbackStatus = player.playbackStatus === 'Playing' ? "Paused" : "Playing";
|
player.playbackStatus = player.playbackStatus === mpris.PLAYBACK_STATUS_PLAYING ? mpris.PLAYBACK_STATUS_PAUSED : mpris.PLAYBACK_STATUS_PLAYING;
|
||||||
playPause();
|
playPause();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -94,40 +84,66 @@ function registerMPRIS(win) {
|
|||||||
player.on('seek', seekBy);
|
player.on('seek', seekBy);
|
||||||
player.on('position', seekTo);
|
player.on('position', seekTo);
|
||||||
|
|
||||||
ipcMain.on('volumeChanged', (_, value) => {
|
player.on('shuffle', (enableShuffle) => {
|
||||||
player.volume = value;
|
shuffle();
|
||||||
});
|
});
|
||||||
player.on('volume', (newVolume) => {
|
|
||||||
if (config.plugins.isEnabled('precise-volume')) {
|
|
||||||
// With precise volume we can set the volume to the exact value.
|
|
||||||
win.webContents.send('setVolume', newVolume)
|
|
||||||
} else {
|
|
||||||
// With keyboard shortcuts we can only change the volume in increments of 10, so round it.
|
|
||||||
const deltaVolume = Math.round((newVolume - player.volume) / 10);
|
|
||||||
|
|
||||||
if (deltaVolume > 0) {
|
let mprisVolNewer = false;
|
||||||
for (let i = 0; i < deltaVolume; i++)
|
let autoUpdate = false;
|
||||||
volumePlus10();
|
ipcMain.on('volumeChanged', (_, newVol) => {
|
||||||
|
if (parseInt(player.volume * 100) !== newVol) {
|
||||||
|
if (mprisVolNewer) {
|
||||||
|
mprisVolNewer = false;
|
||||||
|
autoUpdate = false;
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < -deltaVolume; i++)
|
autoUpdate = true;
|
||||||
volumeMinus10();
|
player.volume = parseFloat((newVol / 100).toFixed(2));
|
||||||
|
mprisVolNewer = false;
|
||||||
|
autoUpdate = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerCallback(songInfo => {
|
player.on('volume', (newVolume) => {
|
||||||
if (player) {
|
if (config.plugins.isEnabled('precise-volume')) {
|
||||||
const data = {
|
// With precise volume we can set the volume to the exact value.
|
||||||
'mpris:length': secToMicro(songInfo.songDuration),
|
let newVol = parseInt(newVolume * 100);
|
||||||
'mpris:artUrl': songInfo.imageSrc,
|
if (parseInt(player.volume * 100) !== newVol) {
|
||||||
'xesam:title': songInfo.title,
|
if (!autoUpdate){
|
||||||
'xesam:artist': [songInfo.artist],
|
mprisVolNewer = true;
|
||||||
|
autoUpdate = false;
|
||||||
|
win.webContents.send('setVolume', newVol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// With keyboard shortcuts we can only change the volume in increments of 10, so round it.
|
||||||
|
let deltaVolume = Math.round((newVolume - player.volume) * 10);
|
||||||
|
while (deltaVolume !== 0 && deltaVolume > 0) {
|
||||||
|
volumePlus10();
|
||||||
|
player.volume = player.volume + 0.1;
|
||||||
|
deltaVolume--;
|
||||||
|
}
|
||||||
|
while (deltaVolume !== 0 && deltaVolume < 0) {
|
||||||
|
volumeMinus10();
|
||||||
|
player.volume = player.volume - 0.1;
|
||||||
|
deltaVolume++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerCallback(songInfo => {
|
||||||
|
if (player) {
|
||||||
|
const data = {
|
||||||
|
'mpris:length': secToMicro(songInfo.songDuration),
|
||||||
|
'mpris:artUrl': songInfo.imageSrc,
|
||||||
|
'xesam:title': songInfo.title,
|
||||||
|
'xesam:artist': [songInfo.artist],
|
||||||
'mpris:trackid': '/'
|
'mpris:trackid': '/'
|
||||||
};
|
};
|
||||||
if (songInfo.album) data['xesam:album'] = songInfo.album;
|
if (songInfo.album) data['xesam:album'] = songInfo.album;
|
||||||
player.metadata = data;
|
player.metadata = data;
|
||||||
player.seeked(secToMicro(songInfo.elapsedSeconds))
|
player.seeked(secToMicro(songInfo.elapsedSeconds));
|
||||||
player.playbackStatus = songInfo.isPaused ? "Paused" : "Playing"
|
player.playbackStatus = songInfo.isPaused ? mpris.PLAYBACK_STATUS_PAUSED : mpris.PLAYBACK_STATUS_PLAYING;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const { ipcRenderer } = require("electron");
|
const {ipcRenderer} = require("electron");
|
||||||
const is = require('electron-is');
|
const is = require('electron-is');
|
||||||
const { getImage } = require("./song-info");
|
const {getImage} = require("./song-info");
|
||||||
|
|
||||||
const config = require("../config");
|
const config = require("../config");
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ module.exports = () => {
|
|||||||
data.videoDetails.isPaused = false;
|
data.videoDetails.isPaused = false;
|
||||||
ipcRenderer.send("video-src-changed", JSON.stringify(data));
|
ipcRenderer.send("video-src-changed", JSON.stringify(data));
|
||||||
}
|
}
|
||||||
}, { once: true, passive: true });
|
}, {once: true, passive: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
function setupTimeChangeListener() {
|
function setupTimeChangeListener() {
|
||||||
@ -63,17 +63,17 @@ function setupTimeChangeListener() {
|
|||||||
ipcRenderer.send('timeChanged', mutations[0].target.value);
|
ipcRenderer.send('timeChanged', mutations[0].target.value);
|
||||||
global.songInfo.elapsedSeconds = mutations[0].target.value;
|
global.songInfo.elapsedSeconds = mutations[0].target.value;
|
||||||
});
|
});
|
||||||
progressObserver.observe($('#progress-bar'), { attributeFilter: ["value"] })
|
progressObserver.observe($('#progress-bar'), {attributeFilter: ["value"]})
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupRepeatChangeListener() {
|
function setupRepeatChangeListener() {
|
||||||
const repeatObserver = new MutationObserver(mutations => {
|
const repeatObserver = new MutationObserver(mutations => {
|
||||||
ipcRenderer.send('repeatChanged', mutations[0].target.title);
|
ipcRenderer.send('repeatChanged', mutations[0].target.__dataHost.getState().queue.repeatMode)
|
||||||
});
|
});
|
||||||
repeatObserver.observe($('#right-controls .repeat'), { attributeFilter: ["title"] });
|
repeatObserver.observe($('#right-controls .repeat'), {attributeFilter: ["title"]});
|
||||||
|
|
||||||
// Emit the initial value as well; as it's persistent between launches.
|
// Emit the initial value as well; as it's persistent between launches.
|
||||||
ipcRenderer.send('repeatChanged', $('#right-controls .repeat').title);
|
ipcRenderer.send('repeatChanged', $('ytmusic-player-bar').getState().queue.repeatMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupVolumeChangeListener(api) {
|
function setupVolumeChangeListener(api) {
|
||||||
|
|||||||
@ -39,3 +39,8 @@ img {
|
|||||||
ytmusic-cast-button {
|
ytmusic-cast-button {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Remove useless inaccessible button on top-right corner of the video player */
|
||||||
|
.ytp-chrome-top-buttons {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user