diff --git a/config/defaults.ts b/config/defaults.ts index ee2e0c3c..08369660 100644 --- a/config/defaults.ts +++ b/config/defaults.ts @@ -77,6 +77,7 @@ const defaultConfig = { disableDefaultLists: [], }, 'album-color-theme': {}, + 'ambient-mode': {}, 'audio-compressor': {}, 'blur-nav-bar': {}, 'bypass-age-restrictions': {}, diff --git a/index.ts b/index.ts index 3af1f805..c81229ee 100644 --- a/index.ts +++ b/index.ts @@ -20,6 +20,7 @@ import { APP_PROTOCOL, handleProtocol, setupProtocolHandler } from './providers/ import adblocker from './plugins/adblocker/back'; import albumColorTheme from './plugins/album-color-theme/back'; +import ambientMode from './plugins/ambient-mode/back'; import blurNavigationBar from './plugins/blur-nav-bar/back'; import captionsSelector from './plugins/captions-selector/back'; import crossfade from './plugins/crossfade/back'; @@ -103,6 +104,7 @@ function onClosed() { const mainPlugins = { 'adblocker': adblocker, 'album-color-theme': albumColorTheme, + 'ambient-mode': ambientMode, 'blur-nav-bar': blurNavigationBar, 'captions-selector': captionsSelector, 'crossfade': crossfade, diff --git a/plugins/ambient-mode/back.ts b/plugins/ambient-mode/back.ts new file mode 100644 index 00000000..0f501515 --- /dev/null +++ b/plugins/ambient-mode/back.ts @@ -0,0 +1,10 @@ +import { BrowserWindow } from 'electron'; + +import style from './style.css'; + +import { injectCSS } from '../utils'; + + +export default (win: BrowserWindow) => { + injectCSS(win.webContents, style); +}; diff --git a/plugins/ambient-mode/front.ts b/plugins/ambient-mode/front.ts new file mode 100644 index 00000000..b7b84e55 --- /dev/null +++ b/plugins/ambient-mode/front.ts @@ -0,0 +1,83 @@ +import { ConfigType } from '../../config/dynamic'; + +export default (_: ConfigType<'ambient-mode'>) => { + let unregister: (() => void) | null = null; + + const injectBlurVideo = (): (() => void) | null => { + const songVideo = document.querySelector('#song-video'); + const video = document.querySelector('#song-video .html5-video-container > video'); + const wrapper = document.querySelector('#song-video > .player-wrapper'); + + if (!songVideo) return null; + if (!video) return null; + if (!wrapper) return null; + + const blurCanvas = document.createElement('canvas'); + blurCanvas.classList.add('html5-blur-canvas'); + + const context = blurCanvas.getContext('2d'); + + const applyVideoAttributes = () => { + const rect = video.getBoundingClientRect(); + + blurCanvas.width = video.width || rect.width; + blurCanvas.height = video.height || rect.height; + }; + + const onSync = () => { + requestAnimationFrame(() => { + context?.drawImage(video, 0, 0, blurCanvas.width, blurCanvas.height); + }); + }; + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes') { + applyVideoAttributes(); + } + }); + }); + const resizeObserver = new ResizeObserver(() => { + applyVideoAttributes(); + }); + + /* hooking */ + video.addEventListener('timeupdate', onSync); + + applyVideoAttributes(); + observer.observe(songVideo, { attributes: true }); + resizeObserver.observe(songVideo); + + /* injecting */ + wrapper.prepend(blurCanvas); + + /* cleanup */ + return () => { + video.removeEventListener('timeupdate', onSync); + observer.disconnect(); + resizeObserver.disconnect(); + + wrapper.removeChild(blurCanvas); + }; + }; + + + const playerPage = document.querySelector('#player-page'); + const ytmusicAppLayout = document.querySelector('#layout'); + + const observer = new MutationObserver((mutationsList) => { + for (const mutation of mutationsList) { + if (mutation.type === 'attributes') { + const isPageOpen = ytmusicAppLayout?.hasAttribute('player-page-open'); + if (isPageOpen) { + unregister?.(); + unregister = injectBlurVideo() ?? null; + } + } + } + }); + + if (playerPage) { + observer.observe(playerPage, { attributes: true }); + } +}; \ No newline at end of file diff --git a/plugins/ambient-mode/style.css b/plugins/ambient-mode/style.css new file mode 100644 index 00000000..44f90164 --- /dev/null +++ b/plugins/ambient-mode/style.css @@ -0,0 +1,7 @@ +#song-video canvas.html5-blur-canvas{ + position: absolute; + left: 0; + top: 0; + + filter: blur(100px); +} diff --git a/preload.ts b/preload.ts index c2e0f74c..1886958b 100644 --- a/preload.ts +++ b/preload.ts @@ -7,6 +7,7 @@ import { setupSongControls } from './providers/song-controls-front'; import { startingPages } from './providers/extracted-data'; import albumColorThemeRenderer from './plugins/album-color-theme/front'; +import ambientModeRenderer from './plugins/ambient-mode/front'; import audioCompressorRenderer from './plugins/audio-compressor/front'; import bypassAgeRestrictionsRenderer from './plugins/bypass-age-restrictions/front'; import captionsSelectorRenderer from './plugins/captions-selector/front'; @@ -43,6 +44,7 @@ type PluginMapper = { const rendererPlugins: PluginMapper<'renderer'> = { 'album-color-theme': albumColorThemeRenderer, + 'ambient-mode': ambientModeRenderer, 'audio-compressor': audioCompressorRenderer, 'bypass-age-restrictions': bypassAgeRestrictionsRenderer, 'captions-selector': captionsSelectorRenderer,