diff --git a/src/plugins/lyrics-genius/renderer.ts b/src/plugins/lyrics-genius/renderer.ts index e679d3d4..4f54b42e 100644 --- a/src/plugins/lyrics-genius/renderer.ts +++ b/src/plugins/lyrics-genius/renderer.ts @@ -32,7 +32,7 @@ export const onRendererLoad = ({ let unregister: (() => void) | null = null; - on('update-song-info', (extractedSongInfo: SongInfo) => { + on('ytmd:update-song-info', (extractedSongInfo: SongInfo) => { unregister?.(); setTimeout(async () => { diff --git a/src/providers/song-info-front.ts b/src/providers/song-info-front.ts index 60bf0d64..b75c249b 100644 --- a/src/providers/song-info-front.ts +++ b/src/providers/song-info-front.ts @@ -10,10 +10,7 @@ import type { VideoDataChanged } from '@/types/video-data-changed'; let songInfo: SongInfo = {} as SongInfo; export const getSongInfo = () => songInfo; -const $ = (s: string): E | null => - document.querySelector(s); - -window.ipcRenderer.on('update-song-info', (_, extractedSongInfo: SongInfo) => { +window.ipcRenderer.on('ytmd:update-song-info', (_, extractedSongInfo: SongInfo) => { songInfo = extractedSongInfo; }); @@ -21,7 +18,7 @@ window.ipcRenderer.on('update-song-info', (_, extractedSongInfo: SongInfo) => { const srcChangedEvent = new CustomEvent('ytmd:src-changed'); export const setupSeekedListener = singleton(() => { - $('video')?.addEventListener('seeked', (v) => { + document.querySelector('video')?.addEventListener('seeked', (v) => { if (v.target instanceof HTMLVideoElement) { window.ipcRenderer.send('ytmd:seeked', v.target.currentTime); } @@ -36,7 +33,7 @@ export const setupTimeChangedListener = singleton(() => { songInfo.elapsedSeconds = Number(target.value); } }); - const progressBar = $('#progress-bar'); + const progressBar = document.querySelector('#progress-bar'); if (progressBar) { progressObserver.observe(progressBar, { attributeFilter: ['value'] }); } @@ -56,7 +53,7 @@ export const setupRepeatChangedListener = singleton(() => { ).__dataHost.getState().queue.repeatMode, ); }); - repeatObserver.observe($('#right-controls .repeat')!, { + repeatObserver.observe(document.querySelector('#right-controls .repeat')!, { attributeFilter: ['title'], }); @@ -64,7 +61,7 @@ export const setupRepeatChangedListener = singleton(() => { // provided by YouTube Music window.ipcRenderer.send( 'ytmd:repeat-changed', - $< + document.querySelector< HTMLElement & { getState: () => GetState; } @@ -73,7 +70,7 @@ export const setupRepeatChangedListener = singleton(() => { }); export const setupVolumeChangedListener = singleton((api: YoutubePlayer) => { - $('video')?.addEventListener('volumechange', () => { + document.querySelector('video')?.addEventListener('volumechange', () => { window.ipcRenderer.send('ytmd:volume-changed', api.getVolume()); }); // Emit the initial value as well; as it's persistent between launches. @@ -134,7 +131,7 @@ export default (api: YoutubePlayer) => { waitingEvent.delete(videoData.videoId); sendSongInfo(videoData); } else if (name === 'dataloaded') { - const video = $('video'); + const video = document.querySelector('video'); video?.dispatchEvent(srcChangedEvent); for (const status of ['playing', 'pause'] as const) { @@ -146,9 +143,12 @@ export default (api: YoutubePlayer) => { } }); - const video = $('video')!; - for (const status of ['playing', 'pause'] as const) { - video.addEventListener(status, playPausedHandlers[status]); + const video = document.querySelector('video'); + + if (video) { + for (const status of ['playing', 'pause'] as const) { + video.addEventListener(status, playPausedHandlers[status]); + } } function sendSongInfo(videoData: VideoDataChangeValue) { diff --git a/src/providers/song-info.ts b/src/providers/song-info.ts index f17e0f0e..761ef3e0 100644 --- a/src/providers/song-info.ts +++ b/src/providers/song-info.ts @@ -1,7 +1,8 @@ import { BrowserWindow, ipcMain, nativeImage, net } from 'electron'; -import { cache } from './decorators'; +import { Mutex } from 'async-mutex'; +import { cache } from './decorators'; import config from '@/config'; import type { GetPlayerResponse } from '@/types/get-player-response'; @@ -22,23 +23,6 @@ export interface SongInfo { playlistId?: string; } -// Fill songInfo with empty values -export const songInfo: SongInfo = { - title: '', - artist: '', - views: 0, - uploadDate: '', - imageSrc: '', - image: null, - isPaused: undefined, - songDuration: 0, - elapsedSeconds: 0, - url: '', - album: undefined, - videoId: '', - playlistId: '', -}; - // Grab the native image using the src export const getImage = cache( async (src: string): Promise => { @@ -57,11 +41,28 @@ export const getImage = cache( const handleData = async ( data: GetPlayerResponse, win: Electron.BrowserWindow, -) => { +): Promise => { if (!data) { - return; + return null; } + // Fill songInfo with empty values + const songInfo: SongInfo = { + title: '', + artist: '', + views: 0, + uploadDate: '', + imageSrc: '', + image: null, + isPaused: undefined, + songDuration: 0, + elapsedSeconds: 0, + url: '', + album: undefined, + videoId: '', + playlistId: '', + } satisfies SongInfo; + const microformat = data.microformat?.microformatDataRenderer; if (microformat) { songInfo.uploadDate = microformat.uploadDate; @@ -87,8 +88,10 @@ const handleData = async ( songInfo.imageSrc = thumbnails.at(-1)?.url.split('?')[0]; if (songInfo.imageSrc) songInfo.image = await getImage(songInfo.imageSrc); - win.webContents.send('update-song-info', songInfo); + win.webContents.send('ytmd:update-song-info', songInfo); } + + return songInfo; }; // This variable will be filled with the callbacks once they register @@ -100,35 +103,47 @@ const registerCallback = (callback: SongInfoCallback) => { callbacks.add(callback); }; -let handlingData = false; - const registerProvider = (win: BrowserWindow) => { + const dataMutex = new Mutex(); + let songInfo: SongInfo | null = null; + // This will be called when the song-info-front finds a new request with song data ipcMain.on('ytmd:video-src-changed', async (_, data: GetPlayerResponse) => { - handlingData = true; - await handleData(data, win); - handlingData = false; - for (const c of callbacks) { - c(songInfo, 'ytmd:video-src-changed'); + const tempSongInfo = await dataMutex.runExclusive(async () => { + songInfo = await handleData(data, win); + return songInfo; + }); + + if (tempSongInfo) { + for (const c of callbacks) { + c(tempSongInfo, 'ytmd:video-src-changed'); + } } }); ipcMain.on( 'ytmd:play-or-paused', - ( + async ( _, { isPaused, elapsedSeconds, }: { isPaused: boolean; elapsedSeconds: number }, ) => { - songInfo.isPaused = isPaused; - songInfo.elapsedSeconds = elapsedSeconds; - if (handlingData) { - return; - } + const tempSongInfo = await dataMutex.runExclusive(() => { + if (!songInfo) { + return null; + } - for (const c of callbacks) { - c(songInfo, 'ytmd:play-or-paused'); + songInfo.isPaused = isPaused; + songInfo.elapsedSeconds = elapsedSeconds; + + return songInfo; + }); + + if (tempSongInfo) { + for (const c of callbacks) { + c(tempSongInfo, 'ytmd:play-or-paused'); + } } }, );