From 29a5fa3f74cf94d472f05a8a7a6585f65ef47b8e Mon Sep 17 00:00:00 2001 From: jeffvli Date: Fri, 12 Dec 2025 18:25:06 -0800 Subject: [PATCH] fix mpv autoNext and next song replacement behavior --- .../audio-player/engine/mpv-player-engine.tsx | 66 ++++++++++--------- .../audio-player/hooks/use-player-events.ts | 8 +++ src/renderer/store/player.store.ts | 53 +++++++++++++++ 3 files changed, 97 insertions(+), 30 deletions(-) diff --git a/src/renderer/features/player/audio-player/engine/mpv-player-engine.tsx b/src/renderer/features/player/audio-player/engine/mpv-player-engine.tsx index 746105d3a..6477d1baa 100644 --- a/src/renderer/features/player/audio-player/engine/mpv-player-engine.tsx +++ b/src/renderer/features/player/audio-player/engine/mpv-player-engine.tsx @@ -224,11 +224,7 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => { const handleOnAutoNext = () => { mediaAutoNext(); - const playerData = usePlayerStore.getState().getPlayerData(); - const nextSongUrl = playerData.nextSong - ? getSongUrl(playerData.nextSong, transcode) - : undefined; - mpvPlayer?.setQueueNext(nextSongUrl); + handleMpvAutoNext(transcode); }; mpvPlayerListener.rendererAutoNext(handleOnAutoNext); @@ -241,37 +237,20 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => { usePlayerEvents( { onMediaNext: () => { - const playerData = usePlayerStore.getState().getPlayerData(); - const currentSongUrl = playerData.currentSong - ? getSongUrl(playerData.currentSong, transcode) - : undefined; - const nextSongUrl = playerData.nextSong - ? getSongUrl(playerData.nextSong, transcode) - : undefined; - mpvPlayer?.setQueue(currentSongUrl, nextSongUrl, false); + replaceMpvQueue(transcode); }, onMediaPrev: () => { - const playerData = usePlayerStore.getState().getPlayerData(); - const currentSongUrl = playerData.currentSong - ? getSongUrl(playerData.currentSong, transcode) - : undefined; - const nextSongUrl = playerData.nextSong - ? getSongUrl(playerData.nextSong, transcode) - : undefined; - mpvPlayer?.setQueue(currentSongUrl, nextSongUrl, false); + replaceMpvQueue(transcode); + }, + onNextSongInsertion: (song) => { + const nextSongUrl = song ? getSongUrl(song, transcode) : undefined; + mpvPlayer?.setQueueNext(nextSongUrl); }, onPlayerPlay: () => { - const playerData = usePlayerStore.getState().getPlayerData(); - const currentSongUrl = playerData.currentSong - ? getSongUrl(playerData.currentSong, transcode) - : undefined; - const nextSongUrl = playerData.nextSong - ? getSongUrl(playerData.nextSong, transcode) - : undefined; - mpvPlayer?.setQueue(currentSongUrl, nextSongUrl, false); + replaceMpvQueue(transcode); }, }, - [mpvPlayer, transcode], + [transcode], ); useImperativeHandle(playerRef, () => ({ @@ -317,3 +296,30 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => { }; MpvPlayerEngine.displayName = 'MpvPlayerEngine'; + +function handleMpvAutoNext(transcode: { + bitrate?: number | undefined; + enabled: boolean; + format?: string | undefined; +}) { + const playerData = usePlayerStore.getState().getPlayerData(); + const nextSongUrl = playerData.nextSong + ? getSongUrl(playerData.nextSong, transcode) + : undefined; + mpvPlayer?.autoNext(nextSongUrl); +} + +function replaceMpvQueue(transcode: { + bitrate?: number | undefined; + enabled: boolean; + format?: string | undefined; +}) { + const playerData = usePlayerStore.getState().getPlayerData(); + const currentSongUrl = playerData.currentSong + ? getSongUrl(playerData.currentSong, transcode) + : undefined; + const nextSongUrl = playerData.nextSong + ? getSongUrl(playerData.nextSong, transcode) + : undefined; + mpvPlayer?.setQueue(currentSongUrl, nextSongUrl, false); +} diff --git a/src/renderer/features/player/audio-player/hooks/use-player-events.ts b/src/renderer/features/player/audio-player/hooks/use-player-events.ts index 18bf520db..aaa326362 100644 --- a/src/renderer/features/player/audio-player/hooks/use-player-events.ts +++ b/src/renderer/features/player/audio-player/hooks/use-player-events.ts @@ -3,6 +3,7 @@ import { useEffect } from 'react'; import { eventEmitter } from '/@/renderer/events/event-emitter'; import { subscribeCurrentTrack, + subscribeNextSongInsertion, subscribePlayerMute, subscribePlayerProgress, subscribePlayerQueue, @@ -27,6 +28,7 @@ interface PlayerEventsCallbacks { ) => void; onMediaNext?: (properties: { currentIndex: number; nextIndex: number }) => void; onMediaPrev?: (properties: { currentIndex: number; prevIndex: number }) => void; + onNextSongInsertion?: (song: QueueSong | undefined) => void; onPlayerMute?: (properties: { muted: boolean }, prev: { muted: boolean }) => void; onPlayerPlay?: (properties: { id: string; index: number }) => void; onPlayerProgress?: (properties: { timestamp: number }, prev: { timestamp: number }) => void; @@ -78,6 +80,12 @@ function createPlayerEvents(callbacks: PlayerEventsCallbacks): PlayerEvents { unsubscribers.push(unsubscribe); } + // Subscribe to next song insertions (when a song is added at next position) + if (callbacks.onNextSongInsertion) { + const unsubscribe = subscribeNextSongInsertion(callbacks.onNextSongInsertion); + unsubscribers.push(unsubscribe); + } + // Subscribe to player progress if (callbacks.onPlayerProgress) { const unsubscribe = subscribePlayerProgress(callbacks.onPlayerProgress); diff --git a/src/renderer/store/player.store.ts b/src/renderer/store/player.store.ts index 30ce880f4..3e3af190e 100644 --- a/src/renderer/store/player.store.ts +++ b/src/renderer/store/player.store.ts @@ -2383,6 +2383,59 @@ export const subscribeCurrentTrack = ( ); }; +export const subscribeNextSong = ( + onChange: ( + properties: { index: number; song: QueueSong | undefined }, + prev: { index: number; song: QueueSong | undefined }, + ) => void, +) => { + return usePlayerStoreBase.subscribe( + (state) => { + const queue = state.getQueue(); + let queueIndex = state.player.index; + const repeat = state.player.repeat; + + // If shuffle is enabled and not in priority mode, map shuffled position to actual queue position + if (isShuffleEnabled(state)) { + queueIndex = mapShuffledToQueueIndex(queueIndex, state.queue.shuffled); + } + + // Calculate next song based on shuffle and repeat settings + let nextSong: QueueSong | undefined; + if (isShuffleEnabled(state)) { + // Calculate next in shuffled order + const nextShuffledIndex = state.player.index + 1; + if (nextShuffledIndex < state.queue.shuffled.length) { + const nextQueueIndex = state.queue.shuffled[nextShuffledIndex]; + nextSong = queue.items[nextQueueIndex]; + } else if (repeat === PlayerRepeat.ALL) { + // Wrap to first in shuffled order + const firstQueueIndex = state.queue.shuffled[0]; + nextSong = queue.items[firstQueueIndex]; + } + } else { + nextSong = calculateNextSong(queueIndex, queue.items, repeat); + } + + // Calculate the index for the next song + let nextIndex: number | undefined; + if (nextSong) { + nextIndex = queue.items.findIndex((item) => item._uniqueId === nextSong?._uniqueId); + } + + return { index: nextIndex ?? -1, song: nextSong }; + }, + (nextSong, prevNextSong) => { + onChange(nextSong, prevNextSong); + }, + { + equalityFn: (a, b) => { + return a.song?._uniqueId === b.song?._uniqueId; + }, + }, + ); +}; + export const subscribePlayerVolume = ( onChange: (properties: { volume: number }, prev: { volume: number }) => void, ) => {