diff --git a/src/plugins/album-color-theme/index.ts b/src/plugins/album-color-theme/index.ts index 5607d7cb..be8d061c 100644 --- a/src/plugins/album-color-theme/index.ts +++ b/src/plugins/album-color-theme/index.ts @@ -4,6 +4,8 @@ import style from './style.css?inline'; import { createPlugin } from '@/utils'; +import type { VideoDataChanged } from '@/types/video-data-changed'; + export default createPlugin({ name: 'Album Color Theme', restartNeeded: true, @@ -110,8 +112,8 @@ export default createPlugin({ onPlayerApiReady(playerApi) { const fastAverageColor = new FastAverageColor(); - playerApi.addEventListener('videodatachange', (name: string) => { - if (name === 'dataloaded') { + document.addEventListener('videodatachange', (event: CustomEvent) => { + if (event.detail.name === 'dataloaded') { const playerResponse = playerApi.getPlayerResponse(); const thumbnail = playerResponse?.videoDetails?.thumbnail?.thumbnails?.at(0); if (thumbnail) { diff --git a/src/plugins/disable-autoplay/index.ts b/src/plugins/disable-autoplay/index.ts index 6ee430df..101adbb0 100644 --- a/src/plugins/disable-autoplay/index.ts +++ b/src/plugins/disable-autoplay/index.ts @@ -1,5 +1,6 @@ import { createPlugin } from '@/utils'; +import type { VideoDataChanged } from '@/types/video-data-changed'; import type { YoutubePlayer } from '@/types/youtube-player'; export type DisableAutoPlayPluginConfig = { @@ -13,7 +14,7 @@ export default createPlugin< { config: DisableAutoPlayPluginConfig | null; api: YoutubePlayer | null; - eventListener: (name: string) => void; + eventListener: (event: CustomEvent) => void; timeUpdateListener: (e: Event) => void; }, DisableAutoPlayPluginConfig @@ -44,12 +45,12 @@ export default createPlugin< renderer: { config: null, api: null, - eventListener(name: string) { + eventListener(event: CustomEvent) { if (this.config?.applyOnce) { - this.api?.removeEventListener('videodatachange', this.eventListener); + document.removeEventListener('videodatachange', this.eventListener); } - if (name === 'dataloaded') { + if (event.detail.name === 'dataloaded') { this.api?.pauseVideo(); document.querySelector('video')?.addEventListener('timeupdate', this.timeUpdateListener, { once: true }); } @@ -65,10 +66,10 @@ export default createPlugin< onPlayerApiReady(api) { this.api = api; - api.addEventListener('videodatachange', this.eventListener); + document.addEventListener('videodatachange', this.eventListener); }, stop() { - this.api?.removeEventListener('videodatachange', this.eventListener); + document.removeEventListener('videodatachange', this.eventListener); }, onConfigChange(newConfig) { this.config = newConfig; diff --git a/src/renderer.ts b/src/renderer.ts index 3e953c9e..6d29118a 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -13,28 +13,48 @@ import type { PluginConfig } from '@/types/plugins'; import type { YoutubePlayer } from '@/types/youtube-player'; let api: (Element & YoutubePlayer) | null = null; +let isPluginLoaded = false; +let isApiLoaded = false; +let firstDataLoaded = false; -async function listenForApiLoad() { - api = document.querySelector('#movie_player'); - if (api) { - await onApiLoaded(); +const observer = new MutationObserver(() => { + const playerApi = document.querySelector('#movie_player'); + if (playerApi) { + observer.disconnect(); - return; - } + // Inject song-info provider + setupSongInfo(playerApi); + const dataLoadedListener = (name: string) => { + if (!firstDataLoaded && name === 'dataloaded') { + firstDataLoaded = true; + playerApi.removeEventListener('videodatachange', dataLoadedListener); + } + }; + playerApi.addEventListener('videodatachange', dataLoadedListener); - const observer = new MutationObserver(() => { - api = document.querySelector('#movie_player'); - if (api) { - observer.disconnect(); + if (isPluginLoaded && !isApiLoaded) { + api = playerApi; + isApiLoaded = true; onApiLoaded(); } - }); + } +}); - observer.observe(document.documentElement, { - childList: true, - subtree: true, - }); +observer.observe(document.documentElement, { + childList: true, + subtree: true, +}); + +async function listenForApiLoad() { + if (!isApiLoaded) { + api = document.querySelector('#movie_player'); + if (api) { + await onApiLoaded(); + + return; + } + } } interface YouTubeMusicAppElement extends HTMLElement { @@ -45,9 +65,6 @@ async function onApiLoaded() { window.ipcRenderer.on('seekTo', (_, t: number) => api!.seekTo(t)); window.ipcRenderer.on('seekBy', (_, t: number) => api!.seekBy(t)); - // Inject song-info provider - setupSongInfo(api!); - const video = document.querySelector('video')!; const audioContext = new AudioContext(); const audioSource = audioContext.createMediaElementSource(video); @@ -59,6 +76,10 @@ async function onApiLoaded() { } } + if (firstDataLoaded) { + document.dispatchEvent(new CustomEvent('videodatachange', { detail: { name: 'dataloaded' } })); + } + const audioCanPlayEventDispatcher = () => { document.dispatchEvent( new CustomEvent('audioCanPlay', { @@ -128,6 +149,7 @@ async function onApiLoaded() { (async () => { await loadAllRendererPlugins(); + isPluginLoaded = true; window.ipcRenderer.on( 'plugin:unload', diff --git a/src/reset.d.ts b/src/reset.d.ts index 83de5773..b0e369b3 100644 --- a/src/reset.d.ts +++ b/src/reset.d.ts @@ -4,6 +4,7 @@ import type { ipcRenderer as electronIpcRenderer } from 'electron'; import type is from 'electron-is'; import type config from './config'; +import type { VideoDataChanged } from '@/types/video-data-changed'; declare global { interface Compressor { @@ -13,6 +14,7 @@ declare global { interface DocumentEventMap { 'audioCanPlay': CustomEvent; + 'videodatachange': CustomEvent; } interface Window { diff --git a/src/types/video-data-changed.ts b/src/types/video-data-changed.ts new file mode 100644 index 00000000..dcdc1c3e --- /dev/null +++ b/src/types/video-data-changed.ts @@ -0,0 +1,6 @@ +import type { VideoDataChangeValue } from '@/types/player-api-events'; + +export interface VideoDataChanged { + name: string; + videoData?: VideoDataChangeValue; +}