11import { FastAverageColor } from 'fast-average-color' ;
2+ import Color from 'color' ;
23
34import style from './style.css?inline' ;
45
56import { createPlugin } from '@/utils' ;
67import { t } from '@/i18n' ;
78
8- import type { VideoDataChanged } from '@/types/video-data-changed' ;
9+ const COLOR_KEY = '--ytmusic-album-color' ;
10+ const DARK_COLOR_KEY = '--ytmusic-album-color-dark' ;
911
1012export default createPlugin ( {
1113 name : ( ) => t ( 'plugins.album-color-theme.name' ) ,
@@ -16,69 +18,8 @@ export default createPlugin({
1618 } ,
1719 stylesheets : [ style ] ,
1820 renderer : {
19- hexToHSL : ( H : string ) => {
20- // Convert hex to RGB first
21- let r = 0 ;
22- let g = 0 ;
23- let b = 0 ;
24- if ( H . length == 4 ) {
25- r = Number ( '0x' + H [ 1 ] + H [ 1 ] ) ;
26- g = Number ( '0x' + H [ 2 ] + H [ 2 ] ) ;
27- b = Number ( '0x' + H [ 3 ] + H [ 3 ] ) ;
28- } else if ( H . length == 7 ) {
29- r = Number ( '0x' + H [ 1 ] + H [ 2 ] ) ;
30- g = Number ( '0x' + H [ 3 ] + H [ 4 ] ) ;
31- b = Number ( '0x' + H [ 5 ] + H [ 6 ] ) ;
32- }
33- // Then to HSL
34- r /= 255 ;
35- g /= 255 ;
36- b /= 255 ;
37- const cmin = Math . min ( r , g , b ) ;
38- const cmax = Math . max ( r , g , b ) ;
39- const delta = cmax - cmin ;
40- let h : number ;
41- let s : number ;
42- let l : number ;
43-
44- if ( delta == 0 ) {
45- h = 0 ;
46- } else if ( cmax == r ) {
47- h = ( ( g - b ) / delta ) % 6 ;
48- } else if ( cmax == g ) {
49- h = ( ( b - r ) / delta ) + 2 ;
50- } else {
51- h = ( ( r - g ) / delta ) + 4 ;
52- }
53-
54- h = Math . round ( h * 60 ) ;
55-
56- if ( h < 0 ) {
57- h += 360 ;
58- }
59-
60- l = ( cmax + cmin ) / 2 ;
61- s = delta == 0 ? 0 : delta / ( 1 - Math . abs ( ( 2 * l ) - 1 ) ) ;
62- s = + ( s * 100 ) . toFixed ( 1 ) ;
63- l = + ( l * 100 ) . toFixed ( 1 ) ;
64-
65- //return "hsl(" + h + "," + s + "%," + l + "%)";
66- return [ h , s , l ] ;
67- } ,
68- hue : 0 ,
69- saturation : 0 ,
70- lightness : 0 ,
71-
72- changeElementColor : (
73- element : HTMLElement | null ,
74- hue : number ,
75- saturation : number ,
76- lightness : number ,
77- ) => {
78- if ( element ) {
79- element . style . backgroundColor = `hsl(${ hue } , ${ saturation } %, ${ lightness } %)` ;
80- }
81- } ,
21+ color : null as Color | null ,
22+ darkColor : null as Color | null ,
8223
8324 playerPage : null as HTMLElement | null ,
8425 navBarBackground : null as HTMLElement | null ,
@@ -103,113 +44,66 @@ export default createPlugin({
10344 '#mini-guide-background' ,
10445 ) ;
10546 this . ytmusicAppLayout = document . querySelector < HTMLElement > ( '#layout' ) ;
47+ } ,
48+ onPlayerApiReady ( playerApi ) {
49+ const fastAverageColor = new FastAverageColor ( ) ;
50+
51+ document . addEventListener ( 'videodatachange' , async ( event ) => {
52+ if ( event . detail . name !== 'dataloaded' ) return ;
53+
54+ const playerResponse = playerApi . getPlayerResponse ( ) ;
55+ const thumbnail = playerResponse ?. videoDetails ?. thumbnail ?. thumbnails ?. at ( 0 ) ;
56+ if ( ! thumbnail ) return ;
10657
107- const observer = new MutationObserver ( ( mutationsList ) => {
108- for ( const mutation of mutationsList ) {
109- if ( mutation . type === 'attributes' ) {
110- const isPageOpen =
111- this . ytmusicAppLayout ?. hasAttribute ( 'player-page-open' ) ;
112- if ( isPageOpen ) {
113- this . changeElementColor (
114- this . sidebarSmall ,
115- this . hue ,
116- this . saturation ,
117- this . lightness - 30 ,
118- ) ;
119- } else {
120- if ( this . sidebarSmall ) {
121- this . sidebarSmall . style . backgroundColor = 'black' ;
122- }
123- }
58+ const albumColor = await fastAverageColor . getColorAsync ( thumbnail . url )
59+ . catch ( ( err ) => {
60+ console . error ( err ) ;
61+ return null ;
62+ } ) ;
63+
64+ if ( albumColor ) {
65+ const target = Color ( albumColor . hex ) ;
66+
67+ this . darkColor = target . darken ( 0.3 ) . rgb ( ) ;
68+ this . color = target . darken ( 0.15 ) . rgb ( ) ;
69+
70+ while ( this . color . luminosity ( ) > 0.5 ) {
71+ this . color = this . color ?. darken ( 0.05 ) ;
72+ this . darkColor = this . darkColor ?. darken ( 0.05 ) ;
12473 }
74+
75+ document . documentElement . style . setProperty ( COLOR_KEY , `${ ~ ~ this . color . red ( ) } , ${ ~ ~ this . color . green ( ) } , ${ ~ ~ this . color . blue ( ) } ` ) ;
76+ document . documentElement . style . setProperty ( DARK_COLOR_KEY , `${ ~ ~ this . darkColor . red ( ) } , ${ ~ ~ this . darkColor . green ( ) } , ${ ~ ~ this . darkColor . blue ( ) } ` ) ;
77+ } else {
78+ document . documentElement . style . setProperty ( COLOR_KEY , '0, 0, 0' ) ;
79+ document . documentElement . style . setProperty ( DARK_COLOR_KEY , '0, 0, 0' ) ;
12580 }
81+
82+ this . updateColor ( ) ;
12683 } ) ;
84+ } ,
85+ getColor ( key : string , alpha = 1 ) {
86+ return `rgba(var(${ key } ), ${ alpha } )` ;
87+ } ,
88+ updateColor ( ) {
89+ const change = ( element : HTMLElement | null , color : string ) => {
90+ if ( element ) {
91+ element . style . backgroundColor = color ;
92+ }
93+ } ;
94+
95+ change ( this . playerPage , this . getColor ( DARK_COLOR_KEY ) ) ;
96+ change ( this . navBarBackground , this . getColor ( COLOR_KEY ) ) ;
97+ change ( this . ytmusicPlayerBar , this . getColor ( COLOR_KEY ) ) ;
98+ change ( this . playerBarBackground , this . getColor ( COLOR_KEY ) ) ;
99+ change ( this . sidebarBig , this . getColor ( COLOR_KEY ) ) ;
127100
128- if ( this . playerPage ) {
129- observer . observe ( this . playerPage , { attributes : true } ) ;
101+ if ( this . ytmusicAppLayout ?. hasAttribute ( 'player-page-open' ) ) {
102+ change ( this . sidebarSmall , this . getColor ( DARK_COLOR_KEY ) ) ;
130103 }
131- } ,
132- onPlayerApiReady ( playerApi ) {
133- const fastAverageColor = new FastAverageColor ( ) ;
134104
135- document . addEventListener (
136- 'videodatachange' ,
137- ( event : CustomEvent < VideoDataChanged > ) => {
138- if ( event . detail . name === 'dataloaded' ) {
139- const playerResponse = playerApi . getPlayerResponse ( ) ;
140- const thumbnail =
141- playerResponse ?. videoDetails ?. thumbnail ?. thumbnails ?. at ( 0 ) ;
142- if ( thumbnail ) {
143- fastAverageColor
144- . getColorAsync ( thumbnail . url )
145- . then ( ( albumColor ) => {
146- if ( albumColor ) {
147- const [ hue , saturation , lightness ] = ( [
148- this . hue ,
149- this . saturation ,
150- this . lightness ,
151- ] = this . hexToHSL ( albumColor . hex ) ) ;
152- this . changeElementColor (
153- this . playerPage ,
154- hue ,
155- saturation ,
156- lightness - 30 ,
157- ) ;
158- this . changeElementColor (
159- this . navBarBackground ,
160- hue ,
161- saturation ,
162- lightness - 15 ,
163- ) ;
164- this . changeElementColor (
165- this . ytmusicPlayerBar ,
166- hue ,
167- saturation ,
168- lightness - 15 ,
169- ) ;
170- this . changeElementColor (
171- this . playerBarBackground ,
172- hue ,
173- saturation ,
174- lightness - 15 ,
175- ) ;
176- this . changeElementColor (
177- this . sidebarBig ,
178- hue ,
179- saturation ,
180- lightness - 15 ,
181- ) ;
182- if (
183- this . ytmusicAppLayout ?. hasAttribute ( 'player-page-open' )
184- ) {
185- this . changeElementColor (
186- this . sidebarSmall ,
187- hue ,
188- saturation ,
189- lightness - 30 ,
190- ) ;
191- }
192- const ytRightClickList =
193- document . querySelector < HTMLElement > (
194- 'tp-yt-paper-listbox' ,
195- ) ;
196- this . changeElementColor (
197- ytRightClickList ,
198- hue ,
199- saturation ,
200- lightness - 15 ,
201- ) ;
202- } else {
203- if ( this . playerPage ) {
204- this . playerPage . style . backgroundColor = '#000000' ;
205- }
206- }
207- } )
208- . catch ( ( e ) => console . error ( e ) ) ;
209- }
210- }
211- } ,
212- ) ;
105+ const ytRightClickList = document . querySelector < HTMLElement > ( 'tp-yt-paper-listbox' ) ;
106+ change ( ytRightClickList , this . getColor ( COLOR_KEY ) ) ;
213107 } ,
214108 } ,
215109} ) ;
0 commit comments