mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-14 06:30:02 +02:00
trigger stop event on queue end
This commit is contained in:
@@ -161,19 +161,29 @@ const createMpv = async (data: {
|
|||||||
await mpv.setMultipleProperties(properties || {});
|
await mpv.setMultipleProperties(properties || {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previousPlaylistPos: number | undefined;
|
||||||
|
|
||||||
mpv.on('status', (status) => {
|
mpv.on('status', (status) => {
|
||||||
if (status.property === 'playlist-pos') {
|
if (status.property === 'playlist-pos') {
|
||||||
|
const currentPos = typeof status.value === 'number' ? status.value : undefined;
|
||||||
|
|
||||||
// mpv uses playlist-pos = -1 when nothing is playing (ended, cleared, load failure, etc).
|
// mpv uses playlist-pos = -1 when nothing is playing (ended, cleared, load failure, etc).
|
||||||
if (status.value === -1) {
|
if (currentPos === -1) {
|
||||||
|
if (previousPlaylistPos === 0) {
|
||||||
|
getMainWindow()?.webContents.send('renderer-player-track-ended');
|
||||||
|
}
|
||||||
mpv?.pause();
|
mpv?.pause();
|
||||||
|
previousPlaylistPos = currentPos;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In our 2-item queue model, playlist-pos should normally be 0.
|
// In our 2-item queue model, playlist-pos should normally be 0.
|
||||||
// When mpv auto-advances to the next track it becomes > 0 (typically 1).
|
// When mpv auto-advances to the next track it becomes > 0 (typically 1).
|
||||||
if (typeof status.value === 'number' && status.value > 0) {
|
if (typeof currentPos === 'number' && currentPos > 0) {
|
||||||
getMainWindow()?.webContents.send('renderer-player-auto-next');
|
getMainWindow()?.webContents.send('renderer-player-auto-next');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previousPlaylistPos = currentPos;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -102,6 +102,10 @@ const getAudioDevices = async () => {
|
|||||||
return ipcRenderer.invoke('player-get-audio-devices');
|
return ipcRenderer.invoke('player-get-audio-devices');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const rendererTrackEnded = (cb: () => void) => {
|
||||||
|
ipcRenderer.on('renderer-player-track-ended', () => cb());
|
||||||
|
};
|
||||||
|
|
||||||
const rendererAutoNext = (cb: (data: PlayerData) => void) => {
|
const rendererAutoNext = (cb: (data: PlayerData) => void) => {
|
||||||
ipcRenderer.on('renderer-player-auto-next', (_, data) => cb(data));
|
ipcRenderer.on('renderer-player-auto-next', (_, data) => cb(data));
|
||||||
};
|
};
|
||||||
@@ -222,6 +226,7 @@ export const mpvPlayerListener = {
|
|||||||
rendererStop,
|
rendererStop,
|
||||||
rendererToggleRepeat,
|
rendererToggleRepeat,
|
||||||
rendererToggleShuffle,
|
rendererToggleShuffle,
|
||||||
|
rendererTrackEnded,
|
||||||
rendererVolumeDown,
|
rendererVolumeDown,
|
||||||
rendererVolumeMute,
|
rendererVolumeMute,
|
||||||
rendererVolumeUp,
|
rendererVolumeUp,
|
||||||
|
|||||||
@@ -276,10 +276,21 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => {
|
|||||||
handleMpvAutoNext(transcode);
|
handleMpvAutoNext(transcode);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTrackEnded = () => {
|
||||||
|
const { player } = usePlayerStore.getState();
|
||||||
|
if (player.status !== PlayerStatus.PLAYING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaAutoNext();
|
||||||
|
};
|
||||||
|
|
||||||
mpvPlayerListener.rendererAutoNext(handleOnAutoNext);
|
mpvPlayerListener.rendererAutoNext(handleOnAutoNext);
|
||||||
|
mpvPlayerListener.rendererTrackEnded(handleTrackEnded);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
ipc?.removeAllListeners('renderer-player-auto-next');
|
ipc?.removeAllListeners('renderer-player-auto-next');
|
||||||
|
ipc?.removeAllListeners('renderer-player-track-ended');
|
||||||
};
|
};
|
||||||
}, [mediaAutoNext, onEnded, transcode]);
|
}, [mediaAutoNext, onEnded, transcode]);
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
usePlayerData,
|
usePlayerData,
|
||||||
usePlayerMuted,
|
usePlayerMuted,
|
||||||
usePlayerProperties,
|
usePlayerProperties,
|
||||||
|
usePlayerStoreBase,
|
||||||
usePlayerVolume,
|
usePlayerVolume,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
import { PlayerStatus, PlayerStyle } from '/@/shared/types/types';
|
import { PlayerStatus, PlayerStyle } from '/@/shared/types/types';
|
||||||
@@ -153,7 +154,13 @@ export function WaveSurferPlayer() {
|
|||||||
|
|
||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
playerRef.current?.player1()?.ref?.pause();
|
playerRef.current?.player1()?.ref?.pause();
|
||||||
|
|
||||||
|
const currentStatus = usePlayerStoreBase.getState().player.status;
|
||||||
|
if (currentStatus !== PlayerStatus.PLAYING) {
|
||||||
|
playerRef.current?.pause();
|
||||||
|
} else {
|
||||||
playerRef.current?.setVolume(volume);
|
playerRef.current?.setVolume(volume);
|
||||||
|
}
|
||||||
setIsTransitioning(false);
|
setIsTransitioning(false);
|
||||||
});
|
});
|
||||||
}, [mediaAutoNext, volume]);
|
}, [mediaAutoNext, volume]);
|
||||||
@@ -166,7 +173,13 @@ export function WaveSurferPlayer() {
|
|||||||
|
|
||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
playerRef.current?.player2()?.ref?.pause();
|
playerRef.current?.player2()?.ref?.pause();
|
||||||
|
|
||||||
|
const currentStatus = usePlayerStoreBase.getState().player.status;
|
||||||
|
if (currentStatus !== PlayerStatus.PLAYING) {
|
||||||
|
playerRef.current?.pause();
|
||||||
|
} else {
|
||||||
playerRef.current?.setVolume(volume);
|
playerRef.current?.setVolume(volume);
|
||||||
|
}
|
||||||
setIsTransitioning(false);
|
setIsTransitioning(false);
|
||||||
});
|
});
|
||||||
}, [mediaAutoNext, volume]);
|
}, [mediaAutoNext, volume]);
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ export function WebPlayer() {
|
|||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
playerRef.current?.player1()?.ref?.getInternalPlayer().pause();
|
playerRef.current?.player1()?.ref?.getInternalPlayer().pause();
|
||||||
|
|
||||||
// If mediaAutoNext resulted in a paused state (e.g. end of queue,
|
// If mediaAutoNext resulted in a stopped/paused state (e.g. end of queue,
|
||||||
// or pauseOnNextSongEnd flag), stop all audio instead of restoring volume.
|
// or pauseOnNextSongEnd flag), stop all audio instead of restoring volume.
|
||||||
const currentStatus = usePlayerStoreBase.getState().player.status;
|
const currentStatus = usePlayerStoreBase.getState().player.status;
|
||||||
if (currentStatus !== PlayerStatus.PLAYING) {
|
if (currentStatus !== PlayerStatus.PLAYING) {
|
||||||
|
|||||||
@@ -207,25 +207,25 @@ function calculateNextIndex(
|
|||||||
currentIndex: number,
|
currentIndex: number,
|
||||||
queueLength: number,
|
queueLength: number,
|
||||||
repeat: PlayerRepeat,
|
repeat: PlayerRepeat,
|
||||||
): { nextIndex: number; shouldPause: boolean } {
|
): { nextIndex: number; shouldStop: boolean } {
|
||||||
const isLastTrack = currentIndex === queueLength - 1;
|
const isLastTrack = currentIndex === queueLength - 1;
|
||||||
|
|
||||||
if (repeat === PlayerRepeat.ONE) {
|
if (repeat === PlayerRepeat.ONE) {
|
||||||
// Repeat one: stay on the same track
|
// Repeat one: stay on the same track
|
||||||
return { nextIndex: currentIndex, shouldPause: false };
|
return { nextIndex: currentIndex, shouldStop: false };
|
||||||
} else if (repeat === PlayerRepeat.ALL) {
|
} else if (repeat === PlayerRepeat.ALL) {
|
||||||
// Repeat all: loop to first track if at the end
|
// Repeat all: loop to first track if at the end
|
||||||
if (isLastTrack) {
|
if (isLastTrack) {
|
||||||
return { nextIndex: 0, shouldPause: false };
|
return { nextIndex: 0, shouldStop: false };
|
||||||
} else {
|
} else {
|
||||||
return { nextIndex: currentIndex + 1, shouldPause: false };
|
return { nextIndex: currentIndex + 1, shouldStop: false };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Repeat none: move to next track, or pause if at the end
|
// Repeat none: move to next track, or stop if at the end
|
||||||
if (isLastTrack) {
|
if (isLastTrack) {
|
||||||
return { nextIndex: currentIndex, shouldPause: true };
|
return { nextIndex: currentIndex, shouldStop: true };
|
||||||
} else {
|
} else {
|
||||||
return { nextIndex: currentIndex + 1, shouldPause: false };
|
return { nextIndex: currentIndex + 1, shouldStop: false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,6 +292,19 @@ function emitPlayerPlayEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function emitPlayerStop(get: () => PlayerState, reset: boolean): void {
|
||||||
|
const currentState = get();
|
||||||
|
const queue = currentState.getQueue();
|
||||||
|
const currentIndex = currentState.player.index;
|
||||||
|
const currentSong = queue.items[currentIndex];
|
||||||
|
|
||||||
|
eventEmitter.emit('PLAYER_STOP', {
|
||||||
|
id: currentSong?._uniqueId,
|
||||||
|
index: currentIndex !== undefined && currentIndex >= 0 ? currentIndex : undefined,
|
||||||
|
reset,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to find shuffled position for a given queue index
|
// Helper function to find shuffled position for a given queue index
|
||||||
function findShuffledPositionForQueueIndex(
|
function findShuffledPositionForQueueIndex(
|
||||||
queueIndex: number,
|
queueIndex: number,
|
||||||
@@ -921,11 +934,12 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
|
|||||||
? stateSnapshot.queue.shuffled.length
|
? stateSnapshot.queue.shuffled.length
|
||||||
: queue.items.length;
|
: queue.items.length;
|
||||||
|
|
||||||
const { nextIndex: nextPlaybackIndex, shouldPause } = calculateNextIndex(
|
const { nextIndex: nextPlaybackIndex, shouldStop } = calculateNextIndex(
|
||||||
currentIndex,
|
currentIndex,
|
||||||
playbackLength,
|
playbackLength,
|
||||||
repeat,
|
repeat,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isRepeatOneSameTrack =
|
const isRepeatOneSameTrack =
|
||||||
repeat === PlayerRepeat.ONE && nextPlaybackIndex === currentIndex;
|
repeat === PlayerRepeat.ONE && nextPlaybackIndex === currentIndex;
|
||||||
// Dual web players alternate for gapless/crossfade between tracks. Repeat-one
|
// Dual web players alternate for gapless/crossfade between tracks. Repeat-one
|
||||||
@@ -937,9 +951,12 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
|
|||||||
? 2
|
? 2
|
||||||
: 1;
|
: 1;
|
||||||
const pauseOnNext = player.pauseOnNextSongEnd;
|
const pauseOnNext = player.pauseOnNextSongEnd;
|
||||||
const newStatus =
|
const newStatus = shouldStop
|
||||||
shouldPause || pauseOnNext ? PlayerStatus.PAUSED : PlayerStatus.PLAYING;
|
? PlayerStatus.STOPPED
|
||||||
const shouldKeepCurrentPlayer = newStatus === PlayerStatus.PAUSED;
|
: pauseOnNext
|
||||||
|
? PlayerStatus.PAUSED
|
||||||
|
: PlayerStatus.PLAYING;
|
||||||
|
const shouldKeepCurrentPlayer = newStatus !== PlayerStatus.PLAYING;
|
||||||
const shouldSwapPlayer = !isRepeatOneSameTrack && !shouldKeepCurrentPlayer;
|
const shouldSwapPlayer = !isRepeatOneSameTrack && !shouldKeepCurrentPlayer;
|
||||||
|
|
||||||
set((state) => {
|
set((state) => {
|
||||||
@@ -948,11 +965,19 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
|
|||||||
setTimestampStore(0);
|
setTimestampStore(0);
|
||||||
state.player.status = newStatus;
|
state.player.status = newStatus;
|
||||||
|
|
||||||
|
if (shouldStop) {
|
||||||
|
state.player.seekToTimestamp = uniqueSeekToTimestamp(0);
|
||||||
|
}
|
||||||
|
|
||||||
if (pauseOnNext) {
|
if (pauseOnNext) {
|
||||||
state.player.pauseOnNextSongEnd = false;
|
state.player.pauseOnNextSongEnd = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (shouldStop) {
|
||||||
|
emitPlayerStop(get, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (repeat === PlayerRepeat.ONE && nextPlaybackIndex === currentIndex) {
|
if (repeat === PlayerRepeat.ONE && nextPlaybackIndex === currentIndex) {
|
||||||
eventEmitter.emit('PLAYER_REPEATED', {
|
eventEmitter.emit('PLAYER_REPEATED', {
|
||||||
index: nextPlaybackIndex,
|
index: nextPlaybackIndex,
|
||||||
@@ -1242,19 +1267,7 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentState = get();
|
emitPlayerStop(get, reset);
|
||||||
const queue = currentState.getQueue();
|
|
||||||
const currentIndex = currentState.player.index;
|
|
||||||
const currentSong = queue.items[currentIndex];
|
|
||||||
|
|
||||||
eventEmitter.emit('PLAYER_STOP', {
|
|
||||||
id: currentSong?._uniqueId,
|
|
||||||
index:
|
|
||||||
currentIndex !== undefined && currentIndex >= 0
|
|
||||||
? currentIndex
|
|
||||||
: undefined,
|
|
||||||
reset,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
mediaToggleMute: () => {
|
mediaToggleMute: () => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user