Skip to content
This repository has been archived by the owner on Aug 12, 2024. It is now read-only.

Commit

Permalink
Merge pull request #33 from organization/feature/ambient-mode
Browse files Browse the repository at this point in the history
  • Loading branch information
JellyBrick authored Oct 4, 2023
2 parents edd7b80 + 0c948d5 commit 95ac01c
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 0 deletions.
1 change: 1 addition & 0 deletions config/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const defaultConfig = {
disableDefaultLists: false,
},
'album-color-theme': {},
'ambient-mode': {},
'audio-compressor': {},
'blur-nav-bar': {},
'bypass-age-restrictions': {},
Expand Down
2 changes: 2 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions plugins/ambient-mode/back.ts
Original file line number Diff line number Diff line change
@@ -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);
};
138 changes: 138 additions & 0 deletions plugins/ambient-mode/front.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { ConfigType } from '../../config/dynamic';

export default (_: ConfigType<'ambient-mode'>) => {
const interpolationTime = 3000; // interpolation time (ms)
const framerate = 30; // frame
const qualityRatio = 50; // width size (pixel)

let unregister: (() => void) | null = null;

const injectBlurVideo = (): (() => void) | null => {
const songVideo = document.querySelector<HTMLDivElement>('#song-video');
const video = document.querySelector<HTMLVideoElement>('#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', { willReadFrequently: true });

/* effect */
let lastEffectWorkId: number | null = null;
let lastImageData: ImageData | null = null;

const onSync = () => {
if (typeof lastEffectWorkId === 'number') cancelAnimationFrame(lastEffectWorkId);

lastEffectWorkId = requestAnimationFrame(() => {
if (!context) return;

const width = qualityRatio;
let height = Math.max(Math.floor(blurCanvas.height / blurCanvas.width * width), 1);
if (!Number.isFinite(height)) height = width;

context.globalAlpha = 1;
if (lastImageData) {
const frameOffset = (1 / framerate) * (1000 / interpolationTime);
context.globalAlpha = 1 - (frameOffset * 2); // because of alpha value must be < 1
context.putImageData(lastImageData, 0, 0);
context.globalAlpha = frameOffset;
}
context.drawImage(video, 0, 0, width, height);

const nowImageData = context.getImageData(0, 0, width, height);
lastImageData = nowImageData;

lastEffectWorkId = null;
});
};

const applyVideoAttributes = () => {
const rect = video.getBoundingClientRect();

const newWidth = Math.floor(video.width || rect.width);
const newHeight = Math.floor(video.height || rect.height);

if (newWidth === 0 || newHeight === 0) return;

blurCanvas.width = qualityRatio;
blurCanvas.height = Math.floor(newHeight / newWidth * qualityRatio);
blurCanvas.style.width = `${newWidth}px`;
blurCanvas.style.height = `${newHeight}px`;
};

const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes') {
applyVideoAttributes();
}
});
});
const resizeObserver = new ResizeObserver(() => {
applyVideoAttributes();
});

/* hooking */
let canvasInterval: NodeJS.Timeout | null = null;
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / framerate)));
applyVideoAttributes();
observer.observe(songVideo, { attributes: true });
resizeObserver.observe(songVideo);
window.addEventListener('resize', applyVideoAttributes);

const onPause = () => {
if (canvasInterval) clearInterval(canvasInterval);
canvasInterval = null;
};
const onPlay = () => {
if (canvasInterval) clearInterval(canvasInterval);
canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / framerate)));
};
songVideo.addEventListener('pause', onPause);
songVideo.addEventListener('play', onPlay);

/* injecting */
wrapper.prepend(blurCanvas);

/* cleanup */
return () => {
if (canvasInterval) clearInterval(canvasInterval);

songVideo.removeEventListener('pause', onPause);
songVideo.removeEventListener('play', onPlay);

observer.disconnect();
resizeObserver.disconnect();
window.removeEventListener('resize', applyVideoAttributes);

wrapper.removeChild(blurCanvas);
};
};


const playerPage = document.querySelector<HTMLElement>('#player-page');
const ytmusicAppLayout = document.querySelector<HTMLElement>('#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;
} else {
unregister?.();
unregister = null;
}
}
}
});

if (playerPage) {
observer.observe(playerPage, { attributes: true });
}
};
7 changes: 7 additions & 0 deletions plugins/ambient-mode/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#song-video canvas.html5-blur-canvas{
position: absolute;
left: 0;
top: 0;

filter: blur(100px);
}
2 changes: 2 additions & 0 deletions preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -43,6 +44,7 @@ type PluginMapper<Type extends 'renderer' | 'preload' | 'backend'> = {

const rendererPlugins: PluginMapper<'renderer'> = {
'album-color-theme': albumColorThemeRenderer,
'ambient-mode': ambientModeRenderer,
'audio-compressor': audioCompressorRenderer,
'bypass-age-restrictions': bypassAgeRestrictionsRenderer,
'captions-selector': captionsSelectorRenderer,
Expand Down

0 comments on commit 95ac01c

Please sign in to comment.