mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 18:41:47 +00:00
feat(plugin): add onPlayerApiReady hook
Co-authored-by: JellyBrick <shlee1503@naver.com>
This commit is contained in:
@ -63,15 +63,23 @@ export default builder.createRenderer(() => {
|
||||
}
|
||||
}
|
||||
|
||||
let playerPage: HTMLElement | null = null;
|
||||
let navBarBackground: HTMLElement | null = null;
|
||||
let ytmusicPlayerBar: HTMLElement | null = null;
|
||||
let playerBarBackground: HTMLElement | null = null;
|
||||
let sidebarBig: HTMLElement | null = null;
|
||||
let sidebarSmall: HTMLElement | null = null;
|
||||
let ytmusicAppLayout: HTMLElement | null = null;
|
||||
|
||||
return {
|
||||
onLoad() {
|
||||
const playerPage = document.querySelector<HTMLElement>('#player-page');
|
||||
const navBarBackground = document.querySelector<HTMLElement>('#nav-bar-background');
|
||||
const ytmusicPlayerBar = document.querySelector<HTMLElement>('ytmusic-player-bar');
|
||||
const playerBarBackground = document.querySelector<HTMLElement>('#player-bar-background');
|
||||
const sidebarBig = document.querySelector<HTMLElement>('#guide-wrapper');
|
||||
const sidebarSmall = document.querySelector<HTMLElement>('#mini-guide-background');
|
||||
const ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
||||
playerPage = document.querySelector<HTMLElement>('#player-page');
|
||||
navBarBackground = document.querySelector<HTMLElement>('#nav-bar-background');
|
||||
ytmusicPlayerBar = document.querySelector<HTMLElement>('ytmusic-player-bar');
|
||||
playerBarBackground = document.querySelector<HTMLElement>('#player-bar-background');
|
||||
sidebarBig = document.querySelector<HTMLElement>('#guide-wrapper');
|
||||
sidebarSmall = document.querySelector<HTMLElement>('#mini-guide-background');
|
||||
ytmusicAppLayout = document.querySelector<HTMLElement>('#layout');
|
||||
|
||||
const observer = new MutationObserver((mutationsList) => {
|
||||
for (const mutation of mutationsList) {
|
||||
@ -91,39 +99,38 @@ export default builder.createRenderer(() => {
|
||||
if (playerPage) {
|
||||
observer.observe(playerPage, { attributes: true });
|
||||
}
|
||||
},
|
||||
onPlayerApiReady(playerApi) {
|
||||
const fastAverageColor = new FastAverageColor();
|
||||
|
||||
document.addEventListener('apiLoaded', (apiEvent) => {
|
||||
const fastAverageColor = new FastAverageColor();
|
||||
|
||||
apiEvent.detail.addEventListener('videodatachange', (name: string) => {
|
||||
if (name === 'dataloaded') {
|
||||
const playerResponse = apiEvent.detail.getPlayerResponse();
|
||||
const thumbnail = playerResponse?.videoDetails?.thumbnail?.thumbnails?.at(0);
|
||||
if (thumbnail) {
|
||||
fastAverageColor.getColorAsync(thumbnail.url)
|
||||
.then((albumColor) => {
|
||||
if (albumColor) {
|
||||
[hue, saturation, lightness] = hexToHSL(albumColor.hex);
|
||||
changeElementColor(playerPage, hue, saturation, lightness - 30);
|
||||
changeElementColor(navBarBackground, hue, saturation, lightness - 15);
|
||||
changeElementColor(ytmusicPlayerBar, hue, saturation, lightness - 15);
|
||||
changeElementColor(playerBarBackground, hue, saturation, lightness - 15);
|
||||
changeElementColor(sidebarBig, hue, saturation, lightness - 15);
|
||||
if (ytmusicAppLayout?.hasAttribute('player-page-open')) {
|
||||
changeElementColor(sidebarSmall, hue, saturation, lightness - 30);
|
||||
}
|
||||
const ytRightClickList = document.querySelector<HTMLElement>('tp-yt-paper-listbox');
|
||||
changeElementColor(ytRightClickList, hue, saturation, lightness - 15);
|
||||
} else {
|
||||
if (playerPage) {
|
||||
playerPage.style.backgroundColor = '#000000';
|
||||
}
|
||||
playerApi.addEventListener('videodatachange', (name: string) => {
|
||||
if (name === 'dataloaded') {
|
||||
const playerResponse = playerApi.getPlayerResponse();
|
||||
const thumbnail = playerResponse?.videoDetails?.thumbnail?.thumbnails?.at(0);
|
||||
if (thumbnail) {
|
||||
fastAverageColor.getColorAsync(thumbnail.url)
|
||||
.then((albumColor) => {
|
||||
if (albumColor) {
|
||||
[hue, saturation, lightness] = hexToHSL(albumColor.hex);
|
||||
changeElementColor(playerPage, hue, saturation, lightness - 30);
|
||||
changeElementColor(navBarBackground, hue, saturation, lightness - 15);
|
||||
changeElementColor(ytmusicPlayerBar, hue, saturation, lightness - 15);
|
||||
changeElementColor(playerBarBackground, hue, saturation, lightness - 15);
|
||||
changeElementColor(sidebarBig, hue, saturation, lightness - 15);
|
||||
if (ytmusicAppLayout?.hasAttribute('player-page-open')) {
|
||||
changeElementColor(sidebarSmall, hue, saturation, lightness - 30);
|
||||
}
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
}
|
||||
const ytRightClickList = document.querySelector<HTMLElement>('tp-yt-paper-listbox');
|
||||
changeElementColor(ytRightClickList, hue, saturation, lightness - 15);
|
||||
} else {
|
||||
if (playerPage) {
|
||||
playerPage.style.backgroundColor = '#000000';
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -5,8 +5,6 @@ import builder from './index';
|
||||
import { ElementFromHtml } from '../utils/renderer';
|
||||
import { YoutubePlayer } from '../../types/youtube-player';
|
||||
|
||||
import type { ConfigType } from '../../config/dynamic';
|
||||
|
||||
interface LanguageOptions {
|
||||
displayName: string;
|
||||
id: string | null;
|
||||
@ -82,30 +80,24 @@ export default builder.createRenderer(({ getConfig, setConfig }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const listener = ({ detail }: {
|
||||
detail: YoutubePlayer;
|
||||
}) => {
|
||||
api = detail;
|
||||
$('.right-controls-buttons').append(captionsSettingsButton);
|
||||
|
||||
captionTrackList = api.getOption<LanguageOptions[]>('captions', 'tracklist') ?? [];
|
||||
|
||||
$('video').addEventListener('srcChanged', videoChangeListener);
|
||||
captionsSettingsButton.addEventListener('click', captionsButtonClickListener);
|
||||
};
|
||||
|
||||
const removeListener = () => {
|
||||
$('.right-controls-buttons').removeChild(captionsSettingsButton);
|
||||
$<YoutubePlayer & HTMLElement>('#movie_player').unloadModule('captions');
|
||||
|
||||
document.removeEventListener('apiLoaded', listener);
|
||||
};
|
||||
|
||||
return {
|
||||
async onLoad() {
|
||||
config = await getConfig();
|
||||
},
|
||||
onPlayerApiReady(playerApi) {
|
||||
api = playerApi;
|
||||
|
||||
document.addEventListener('apiLoaded', listener, { once: true, passive: true });
|
||||
$('.right-controls-buttons').append(captionsSettingsButton);
|
||||
|
||||
captionTrackList = api.getOption<LanguageOptions[]>('captions', 'tracklist') ?? [];
|
||||
|
||||
$('video').addEventListener('srcChanged', videoChangeListener);
|
||||
captionsSettingsButton.addEventListener('click', captionsButtonClickListener);
|
||||
},
|
||||
onUnload() {
|
||||
removeListener();
|
||||
|
||||
@ -85,7 +85,7 @@ export default builder.createRenderer(({ getConfig, invoke }) => {
|
||||
});
|
||||
|
||||
// Exit just before the end for the transition
|
||||
const transitionBeforeEnd = async () => {
|
||||
const transitionBeforeEnd = () => {
|
||||
if (
|
||||
video.currentTime >= video.duration - config.secondsBeforeEnd
|
||||
&& isReadyToCrossfade()
|
||||
@ -140,14 +140,11 @@ export default builder.createRenderer(({ getConfig, invoke }) => {
|
||||
};
|
||||
|
||||
return {
|
||||
onLoad() {
|
||||
document.addEventListener('apiLoaded', async () => {
|
||||
config = await getConfig();
|
||||
onApiLoaded();
|
||||
}, {
|
||||
once: true,
|
||||
passive: true,
|
||||
});
|
||||
async onLoad() {
|
||||
config = await getConfig();
|
||||
},
|
||||
onPlayerApiReady() {
|
||||
onApiLoaded();
|
||||
},
|
||||
onConfigChange(newConfig) {
|
||||
config = newConfig;
|
||||
|
||||
@ -5,7 +5,7 @@ import type { YoutubePlayer } from '../../types/youtube-player';
|
||||
export default builder.createRenderer(({ getConfig }) => {
|
||||
let config: Awaited<ReturnType<typeof getConfig>>;
|
||||
|
||||
let apiEvent: CustomEvent<YoutubePlayer>;
|
||||
let apiEvent: YoutubePlayer;
|
||||
|
||||
const timeUpdateListener = (e: Event) => {
|
||||
if (e.target instanceof HTMLVideoElement) {
|
||||
@ -15,27 +15,25 @@ export default builder.createRenderer(({ getConfig }) => {
|
||||
|
||||
const eventListener = async (name: string) => {
|
||||
if (config.applyOnce) {
|
||||
apiEvent.detail.removeEventListener('videodatachange', eventListener);
|
||||
apiEvent.removeEventListener('videodatachange', eventListener);
|
||||
}
|
||||
|
||||
if (name === 'dataloaded') {
|
||||
apiEvent.detail.pauseVideo();
|
||||
apiEvent.pauseVideo();
|
||||
document.querySelector<HTMLVideoElement>('video')?.addEventListener('timeupdate', timeUpdateListener, { once: true });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
async onLoad() {
|
||||
async onPlayerApiReady(api) {
|
||||
config = await getConfig();
|
||||
|
||||
document.addEventListener('apiLoaded', (api) => {
|
||||
apiEvent = api;
|
||||
apiEvent = api;
|
||||
|
||||
apiEvent.detail.addEventListener('videodatachange', eventListener);
|
||||
}, { once: true, passive: true });
|
||||
apiEvent.addEventListener('videodatachange', eventListener);
|
||||
},
|
||||
onUnload() {
|
||||
apiEvent.detail.removeEventListener('videodatachange', eventListener);
|
||||
apiEvent.removeEventListener('videodatachange', eventListener);
|
||||
},
|
||||
onConfigChange(newConfig) {
|
||||
config = newConfig;
|
||||
|
||||
@ -14,35 +14,35 @@ const downloadButton = ElementFromHtml(downloadHTML);
|
||||
let doneFirstLoad = false;
|
||||
|
||||
export default builder.createRenderer(({ invoke, on }) => {
|
||||
const menuObserver = new MutationObserver(() => {
|
||||
if (!menu) {
|
||||
menu = getSongMenu();
|
||||
if (!menu) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (menu.contains(downloadButton)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const menuUrl = document.querySelector<HTMLAnchorElement>('tp-yt-paper-listbox [tabindex="-1"] #navigation-endpoint')?.href;
|
||||
if (!menuUrl?.includes('watch?') && doneFirstLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
menu.prepend(downloadButton);
|
||||
progress = document.querySelector('#ytmcustom-download');
|
||||
|
||||
if (doneFirstLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => doneFirstLoad ||= true, 500);
|
||||
});
|
||||
|
||||
return {
|
||||
onLoad() {
|
||||
const menuObserver = new MutationObserver(() => {
|
||||
if (!menu) {
|
||||
menu = getSongMenu();
|
||||
if (!menu) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (menu.contains(downloadButton)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const menuUrl = document.querySelector<HTMLAnchorElement>('tp-yt-paper-listbox [tabindex="-1"] #navigation-endpoint')?.href;
|
||||
if (!menuUrl?.includes('watch?') && doneFirstLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
menu.prepend(downloadButton);
|
||||
progress = document.querySelector('#ytmcustom-download');
|
||||
|
||||
if (doneFirstLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => doneFirstLoad ||= true, 500);
|
||||
});
|
||||
|
||||
window.download = () => {
|
||||
let videoUrl = getSongMenu()
|
||||
// Selector of first button which is always "Start Radio"
|
||||
@ -64,13 +64,6 @@ export default builder.createRenderer(({ invoke, on }) => {
|
||||
invoke('download-song', videoUrl);
|
||||
};
|
||||
|
||||
document.addEventListener('apiLoaded', () => {
|
||||
menuObserver.observe(document.querySelector('ytmusic-popup-container')!, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}, { once: true, passive: true });
|
||||
|
||||
on('downloader-feedback', (feedback: string) => {
|
||||
if (progress) {
|
||||
progress.innerHTML = feedback || 'Download';
|
||||
@ -78,6 +71,12 @@ export default builder.createRenderer(({ invoke, on }) => {
|
||||
console.warn('Cannot update progress');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onPlayerApiReady() {
|
||||
menuObserver.observe(document.querySelector('ytmusic-popup-container')!, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@ -41,10 +41,7 @@ const exponentialVolume = () => {
|
||||
};
|
||||
|
||||
export default builder.createRenderer(() => ({
|
||||
onLoad() {
|
||||
return document.addEventListener('apiLoaded', exponentialVolume, {
|
||||
once: true,
|
||||
passive: true,
|
||||
});
|
||||
onPlayerApiReady() {
|
||||
exponentialVolume();
|
||||
},
|
||||
}));
|
||||
|
||||
@ -17,7 +17,7 @@ export default builder.createRenderer(({ getConfig, invoke, on }) => {
|
||||
return {
|
||||
async onLoad() {
|
||||
const config = await getConfig();
|
||||
|
||||
|
||||
const hideDOMWindowControls = config.hideDOMWindowControls;
|
||||
|
||||
let hideMenu = window.mainConfig.get('options.hideMenu');
|
||||
@ -26,13 +26,13 @@ export default builder.createRenderer(({ getConfig, invoke, on }) => {
|
||||
let maximizeButton: HTMLButtonElement;
|
||||
let panelClosers: (() => void)[] = [];
|
||||
if (isMacOS) titleBar.style.setProperty('--offset-left', '70px');
|
||||
|
||||
|
||||
const logo = document.createElement('img');
|
||||
const close = document.createElement('img');
|
||||
const minimize = document.createElement('img');
|
||||
const maximize = document.createElement('img');
|
||||
const unmaximize = document.createElement('img');
|
||||
|
||||
|
||||
if (window.ELECTRON_RENDERER_URL) {
|
||||
logo.src = window.ELECTRON_RENDERER_URL + '/' + logoRaw;
|
||||
close.src = window.ELECTRON_RENDERER_URL + '/' + closeRaw;
|
||||
@ -46,7 +46,7 @@ export default builder.createRenderer(({ getConfig, invoke, on }) => {
|
||||
maximize.src = maximizeRaw;
|
||||
unmaximize.src = unmaximizeRaw;
|
||||
}
|
||||
|
||||
|
||||
logo.classList.add('title-bar-icon');
|
||||
const logoClick = () => {
|
||||
hideMenu = !hideMenu;
|
||||
@ -62,22 +62,22 @@ export default builder.createRenderer(({ getConfig, invoke, on }) => {
|
||||
});
|
||||
};
|
||||
logo.onclick = logoClick;
|
||||
|
||||
|
||||
on('toggle-in-app-menu', logoClick);
|
||||
|
||||
|
||||
if (!isMacOS) titleBar.appendChild(logo);
|
||||
document.body.appendChild(titleBar);
|
||||
|
||||
|
||||
titleBar.appendChild(logo);
|
||||
|
||||
|
||||
const addWindowControls = async () => {
|
||||
|
||||
|
||||
// Create window control buttons
|
||||
const minimizeButton = document.createElement('button');
|
||||
minimizeButton.classList.add('window-control');
|
||||
minimizeButton.appendChild(minimize);
|
||||
minimizeButton.onclick = () => invoke('window-minimize');
|
||||
|
||||
|
||||
maximizeButton = document.createElement('button');
|
||||
if (await invoke('window-is-maximized')) {
|
||||
maximizeButton.classList.add('window-control');
|
||||
@ -91,37 +91,37 @@ export default builder.createRenderer(({ getConfig, invoke, on }) => {
|
||||
// change icon to maximize
|
||||
maximizeButton.removeChild(maximizeButton.firstChild!);
|
||||
maximizeButton.appendChild(maximize);
|
||||
|
||||
|
||||
// call unmaximize
|
||||
await invoke('window-unmaximize');
|
||||
} else {
|
||||
// change icon to unmaximize
|
||||
maximizeButton.removeChild(maximizeButton.firstChild!);
|
||||
maximizeButton.appendChild(unmaximize);
|
||||
|
||||
|
||||
// call maximize
|
||||
await invoke('window-maximize');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const closeButton = document.createElement('button');
|
||||
closeButton.classList.add('window-control');
|
||||
closeButton.appendChild(close);
|
||||
closeButton.onclick = () => invoke('window-close');
|
||||
|
||||
|
||||
// Create a container div for the window control buttons
|
||||
const windowControlsContainer = document.createElement('div');
|
||||
windowControlsContainer.classList.add('window-controls-container');
|
||||
windowControlsContainer.appendChild(minimizeButton);
|
||||
windowControlsContainer.appendChild(maximizeButton);
|
||||
windowControlsContainer.appendChild(closeButton);
|
||||
|
||||
|
||||
// Add window control buttons to the title bar
|
||||
titleBar.appendChild(windowControlsContainer);
|
||||
};
|
||||
|
||||
|
||||
if (isNotWindowsOrMacOS && !hideDOMWindowControls) await addWindowControls();
|
||||
|
||||
|
||||
if (navBar) {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach(() => {
|
||||
@ -129,25 +129,25 @@ export default builder.createRenderer(({ getConfig, invoke, on }) => {
|
||||
document.querySelector('html')!.style.setProperty('--titlebar-background-color', navBar.style.backgroundColor);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
observer.observe(navBar, { attributes : true, attributeFilter : ['style'] });
|
||||
}
|
||||
|
||||
|
||||
const updateMenu = async () => {
|
||||
const children = [...titleBar.children];
|
||||
children.forEach((child) => {
|
||||
if (child !== logo) child.remove();
|
||||
});
|
||||
panelClosers = [];
|
||||
|
||||
|
||||
const menu = await invoke<Menu | null>('get-menu');
|
||||
if (!menu) return;
|
||||
|
||||
|
||||
menu.items.forEach((menuItem) => {
|
||||
const menu = document.createElement('menu-button');
|
||||
const [, { close: closer }] = createPanel(titleBar, menu, menuItem.submenu?.items ?? []);
|
||||
panelClosers.push(closer);
|
||||
|
||||
|
||||
menu.append(menuItem.label);
|
||||
titleBar.appendChild(menu);
|
||||
if (hideMenu) {
|
||||
@ -159,7 +159,7 @@ export default builder.createRenderer(({ getConfig, invoke, on }) => {
|
||||
await updateMenu();
|
||||
|
||||
document.title = 'Youtube Music';
|
||||
|
||||
|
||||
on('close-all-in-app-menu-panel', () => {
|
||||
panelClosers.forEach((closer) => closer());
|
||||
});
|
||||
@ -176,21 +176,20 @@ export default builder.createRenderer(({ getConfig, invoke, on }) => {
|
||||
maximizeButton.appendChild(unmaximize);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (window.mainConfig.plugins.isEnabled('picture-in-picture')) {
|
||||
on('pip-toggle', () => {
|
||||
updateMenu();
|
||||
});
|
||||
}
|
||||
|
||||
// Increases the right margin of Navbar background when the scrollbar is visible to avoid blocking it (z-index doesn't affect it)
|
||||
document.addEventListener('apiLoaded', () => {
|
||||
const htmlHeadStyle = document.querySelector('head > div > style');
|
||||
if (htmlHeadStyle) {
|
||||
// HACK: This is a hack to remove the scrollbar width
|
||||
htmlHeadStyle.innerHTML = htmlHeadStyle.innerHTML.replace('html::-webkit-scrollbar {width: var(--ytmusic-scrollbar-width);', 'html::-webkit-scrollbar {');
|
||||
}
|
||||
}, { once: true, passive: true });
|
||||
}
|
||||
},
|
||||
// Increases the right margin of Navbar background when the scrollbar is visible to avoid blocking it (z-index doesn't affect it)
|
||||
onPlayerApiReady() {
|
||||
const htmlHeadStyle = document.querySelector('head > div > style');
|
||||
if (htmlHeadStyle) {
|
||||
// HACK: This is a hack to remove the scrollbar width
|
||||
htmlHeadStyle.innerHTML = htmlHeadStyle.innerHTML.replace('html::-webkit-scrollbar {width: var(--ytmusic-scrollbar-width);', 'html::-webkit-scrollbar {');
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@ -211,7 +211,7 @@ export default (
|
||||
songControls = getSongControls(win);
|
||||
|
||||
let currentSeconds = 0;
|
||||
on('apiLoaded', () => send('setupTimeChangedListener'));
|
||||
on('ytmd:player-api-loaded', () => send('setupTimeChangedListener'));
|
||||
|
||||
on('timeChanged', (t: number) => {
|
||||
currentSeconds = t;
|
||||
|
||||
@ -133,11 +133,27 @@ const listenForToggle = () => {
|
||||
});
|
||||
};
|
||||
|
||||
function observeMenu(options: PictureInPicturePluginConfig) {
|
||||
useNativePiP = options.useNativePiP;
|
||||
document.addEventListener(
|
||||
'apiLoaded',
|
||||
() => {
|
||||
|
||||
export default builder.createRenderer(({ getConfig }) => {
|
||||
return {
|
||||
async onLoad() {
|
||||
const config = await getConfig();
|
||||
|
||||
useNativePiP = config.useNativePiP;
|
||||
|
||||
if (config.hotkey) {
|
||||
const hotkeyEvent = toKeyEvent(config.hotkey);
|
||||
window.addEventListener('keydown', (event) => {
|
||||
if (
|
||||
keyEventAreEqual(event, hotkeyEvent)
|
||||
&& !$<HTMLElement & { opened: boolean }>('ytmusic-search-box')?.opened
|
||||
) {
|
||||
togglePictureInPicture();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onPlayerApiReady() {
|
||||
listenForToggle();
|
||||
|
||||
cloneButton('.player-minimize-button')?.addEventListener('click', async () => {
|
||||
@ -154,28 +170,5 @@ function observeMenu(options: PictureInPicturePluginConfig) {
|
||||
subtree: true,
|
||||
});
|
||||
},
|
||||
{ once: true, passive: true },
|
||||
);
|
||||
}
|
||||
|
||||
export default builder.createRenderer(({ getConfig }) => {
|
||||
return {
|
||||
async onLoad() {
|
||||
const config = await getConfig();
|
||||
|
||||
observeMenu(config);
|
||||
|
||||
if (config.hotkey) {
|
||||
const hotkeyEvent = toKeyEvent(config.hotkey);
|
||||
window.addEventListener('keydown', (event) => {
|
||||
if (
|
||||
keyEventAreEqual(event, hotkeyEvent)
|
||||
&& !$<HTMLElement & { opened: boolean }>('ytmusic-search-box')?.opened
|
||||
) {
|
||||
togglePictureInPicture();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@ -116,12 +116,10 @@ function forcePlaybackRate(e: Event) {
|
||||
|
||||
export default builder.createRenderer(() => {
|
||||
return {
|
||||
onLoad() {
|
||||
document.addEventListener('apiLoaded', () => {
|
||||
observePopupContainer();
|
||||
observeVideo();
|
||||
setupWheelListener();
|
||||
}, { once: true, passive: true });
|
||||
onPlayerApiReady() {
|
||||
observePopupContainer();
|
||||
observeVideo();
|
||||
setupWheelListener();
|
||||
},
|
||||
onUnload() {
|
||||
const video = $<HTMLVideoElement>('video');
|
||||
|
||||
@ -258,13 +258,13 @@ export default builder.createRenderer(async ({ on, getConfig, setConfig }) => {
|
||||
return {
|
||||
onLoad() {
|
||||
overrideListener();
|
||||
},
|
||||
onPlayerApiReady(playerApi) {
|
||||
api = playerApi;
|
||||
|
||||
document.addEventListener('apiLoaded', (e) => {
|
||||
api = e.detail;
|
||||
on('changeVolume', (toIncrease: boolean) => changeVolume(toIncrease));
|
||||
on('setVolume', (value: number) => setVolume(value));
|
||||
firstRun();
|
||||
}, { once: true, passive: true });
|
||||
on('changeVolume', (toIncrease: boolean) => changeVolume(toIncrease));
|
||||
on('setVolume', (value: number) => setVolume(value));
|
||||
firstRun();
|
||||
},
|
||||
onConfigChange(config) {
|
||||
options = config;
|
||||
|
||||
@ -32,19 +32,19 @@ export default builder.createRenderer(({ invoke }) => {
|
||||
api.setPlaybackQualityRange(newQuality);
|
||||
api.setPlaybackQuality(newQuality);
|
||||
});
|
||||
}
|
||||
|
||||
function setup(event: CustomEvent<YoutubePlayer>) {
|
||||
api = event.detail;
|
||||
};
|
||||
|
||||
function setup() {
|
||||
$('.top-row-buttons.ytmusic-player')?.prepend(qualitySettingsButton);
|
||||
|
||||
qualitySettingsButton.addEventListener('click', chooseQuality);
|
||||
}
|
||||
|
||||
return {
|
||||
onLoad() {
|
||||
document.addEventListener('apiLoaded', setup, { once: true, passive: true });
|
||||
onPlayerApiReady(playerApi) {
|
||||
api = playerApi;
|
||||
|
||||
setup();
|
||||
},
|
||||
onUnload() {
|
||||
$('.top-row-buttons.ytmusic-player')?.removeChild(qualitySettingsButton);
|
||||
|
||||
@ -32,7 +32,7 @@ function registerMPRIS(win: BrowserWindow) {
|
||||
|
||||
const player = setupMPRIS();
|
||||
|
||||
ipcMain.handle('apiLoaded', () => {
|
||||
ipcMain.on('ytmd:player-api-loaded', () => {
|
||||
win.webContents.send('setupSeekedListener', 'mpris');
|
||||
win.webContents.send('setupTimeChangedListener', 'mpris');
|
||||
win.webContents.send('setupRepeatChangedListener', 'mpris');
|
||||
|
||||
@ -29,15 +29,14 @@ export default builder.createRenderer(({ on }) => {
|
||||
on('sponsorblock-skip', (_, segments: Segment[]) => {
|
||||
currentSegments = segments;
|
||||
});
|
||||
},
|
||||
onPlayerApiReady() {
|
||||
const video = document.querySelector<HTMLVideoElement>('video');
|
||||
if (!video) return;
|
||||
|
||||
document.addEventListener('apiLoaded', () => {
|
||||
const video = document.querySelector<HTMLVideoElement>('video');
|
||||
if (!video) return;
|
||||
|
||||
video.addEventListener('timeupdate', timeUpdateListener);
|
||||
// Reset segments on song end
|
||||
video.addEventListener('emptied', resetSegments);
|
||||
}, { once: true, passive: true });
|
||||
video.addEventListener('timeupdate', timeUpdateListener);
|
||||
// Reset segments on song end
|
||||
video.addEventListener('emptied', resetSegments);
|
||||
},
|
||||
onUnload() {
|
||||
const video = document.querySelector<HTMLVideoElement>('video');
|
||||
|
||||
@ -54,7 +54,7 @@ const post = (data: Data) => {
|
||||
export default builder.createMain(({ send, handle, on }) => {
|
||||
return {
|
||||
onLoad() {
|
||||
on('apiLoaded', () => send('setupTimeChangedListener'));
|
||||
on('ytmd:player-api-loaded', () => send('setupTimeChangedListener'));
|
||||
on('timeChanged', (t: number) => {
|
||||
if (!data.title) {
|
||||
return;
|
||||
|
||||
@ -2,6 +2,7 @@ import type {
|
||||
BrowserWindow,
|
||||
MenuItemConstructorOptions,
|
||||
} from 'electron';
|
||||
import type { YoutubePlayer } from '../../types/youtube-player';
|
||||
|
||||
export type PluginBaseConfig = {
|
||||
enabled: boolean;
|
||||
@ -11,7 +12,9 @@ export type BasePlugin<Config extends PluginBaseConfig> = {
|
||||
onUnload?: () => void;
|
||||
onConfigChange?: (newConfig: Config) => void;
|
||||
}
|
||||
export type RendererPlugin<Config extends PluginBaseConfig> = BasePlugin<Config>;
|
||||
export type RendererPlugin<Config extends PluginBaseConfig> = BasePlugin<Config> & {
|
||||
onPlayerApiReady?: (api: YoutubePlayer) => void;
|
||||
};
|
||||
export type MainPlugin<Config extends PluginBaseConfig> = Omit<BasePlugin<Config>, 'onLoad' | 'onUnload'> & {
|
||||
onLoad?: (window: BrowserWindow) => void;
|
||||
onUnload?: (window: BrowserWindow) => void;
|
||||
|
||||
@ -22,8 +22,8 @@ export default builder.createRenderer(({ getConfig }) => {
|
||||
|
||||
const switchButtonDiv = ElementFromHtml(buttonTemplate);
|
||||
|
||||
function setup(e: CustomEvent<YoutubePlayer>) {
|
||||
api = e.detail;
|
||||
function setup(playerApi: YoutubePlayer) {
|
||||
api = playerApi;
|
||||
player = document.querySelector<(HTMLElement & { videoMode_: boolean; })>('ytmusic-player');
|
||||
video = document.querySelector<HTMLVideoElement>('video');
|
||||
|
||||
@ -194,13 +194,11 @@ export default builder.createRenderer(({ getConfig }) => {
|
||||
document.querySelector('ytmusic-player')?.removeAttribute('has-av-switcher');
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
case 'custom': {
|
||||
document.addEventListener('apiLoaded', setup, { once: true, passive: true });
|
||||
}
|
||||
}
|
||||
},
|
||||
onPlayerApiReady(playerApi) {
|
||||
if (config.mode !== 'native' && config.mode != 'disabled') setup(playerApi);
|
||||
},
|
||||
onConfigChange(newConfig) {
|
||||
config = newConfig;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user