fix preserve pitch on player start, add to player config (#1349)

This commit is contained in:
jeffvli
2025-12-09 17:57:56 -08:00
parent 753f35f81b
commit a02fc28785
3 changed files with 68 additions and 20 deletions
@@ -1,5 +1,4 @@
import { UseSuspenseQueryOptions } from '@tanstack/react-query'; import { UseSuspenseQueryOptions } from '@tanstack/react-query';
import { ref } from 'process';
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader'; import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader';
@@ -77,7 +76,6 @@ export const AlbumArtistListPaginatedGrid = ({
itemsPerRow={itemsPerRow} itemsPerRow={itemsPerRow}
itemType={LibraryItem.ALBUM_ARTIST} itemType={LibraryItem.ALBUM_ARTIST}
onScrollEnd={handleOnScrollEnd} onScrollEnd={handleOnScrollEnd}
ref={ref}
rows={rows} rows={rows}
/> />
</ItemListWithPagination> </ItemListWithPagination>
@@ -1,6 +1,6 @@
import type { RefObject } from 'react'; import type { RefObject } from 'react';
import { useEffect, useImperativeHandle, useRef, useState } from 'react'; import { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
import ReactPlayer from 'react-player'; import ReactPlayer from 'react-player';
import { AudioPlayer, PlayerOnProgressProps } from '/@/renderer/features/player/audio-player/types'; import { AudioPlayer, PlayerOnProgressProps } from '/@/renderer/features/player/audio-player/types';
@@ -153,15 +153,37 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
useEffect(() => { useEffect(() => {
const player1 = player1Ref.current?.getInternalPlayer(); const player1 = player1Ref.current?.getInternalPlayer();
if (player1) { if (player1 && player1 instanceof HTMLAudioElement) {
player1.preservesPitch = preservesPitch; player1.preservesPitch = preservesPitch;
} }
const player2 = player2Ref.current?.getInternalPlayer(); const player2 = player2Ref.current?.getInternalPlayer();
if (player2) { if (player2 && player2 instanceof HTMLAudioElement) {
player2.preservesPitch = preservesPitch; player2.preservesPitch = preservesPitch;
} }
}, [preservesPitch]); }, [preservesPitch]);
const handleOnReadyPlayer1 = useCallback(
(player: ReactPlayer) => {
const internal = player.getInternalPlayer();
if (internal && internal instanceof HTMLAudioElement) {
internal.preservesPitch = preservesPitch;
}
onStartedPlayer1(player);
},
[onStartedPlayer1, preservesPitch],
);
const handleOnReadyPlayer2 = useCallback(
(player: ReactPlayer) => {
const internal = player.getInternalPlayer();
if (internal && internal instanceof HTMLAudioElement) {
internal.preservesPitch = preservesPitch;
}
onStartedPlayer2(player);
},
[onStartedPlayer2, preservesPitch],
);
return ( return (
<div id="web-player-engine" style={{ display: 'none' }}> <div id="web-player-engine" style={{ display: 'none' }}>
<ReactPlayer <ReactPlayer
@@ -175,7 +197,7 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
onEnded={src1 ? () => onEndedPlayer1() : undefined} onEnded={src1 ? () => onEndedPlayer1() : undefined}
onError={handleOnError(player1Ref, () => onEndedPlayer1())} onError={handleOnError(player1Ref, () => onEndedPlayer1())}
onProgress={onProgressPlayer1} onProgress={onProgressPlayer1}
onReady={onStartedPlayer1} onReady={handleOnReadyPlayer1}
playbackRate={speed || 1} playbackRate={speed || 1}
playing={playerNum === 1 && playerStatus === PlayerStatus.PLAYING} playing={playerNum === 1 && playerStatus === PlayerStatus.PLAYING}
progressInterval={isTransitioning ? 10 : 250} progressInterval={isTransitioning ? 10 : 250}
@@ -195,7 +217,7 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
onEnded={src2 ? () => onEndedPlayer2() : undefined} onEnded={src2 ? () => onEndedPlayer2() : undefined}
onError={handleOnError(player2Ref, () => onEndedPlayer2())} onError={handleOnError(player2Ref, () => onEndedPlayer2())}
onProgress={onProgressPlayer2} onProgress={onProgressPlayer2}
onReady={onStartedPlayer2} onReady={handleOnReadyPlayer2}
playbackRate={speed || 1} playbackRate={speed || 1}
playing={playerNum === 2 && playerStatus === PlayerStatus.PLAYING} playing={playerNum === 2 && playerStatus === PlayerStatus.PLAYING}
progressInterval={isTransitioning ? 10 : 250} progressInterval={isTransitioning ? 10 : 250}
@@ -1,5 +1,5 @@
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ListConfigTable } from '/@/renderer/features/shared/components/list-config-menu'; import { ListConfigTable } from '/@/renderer/features/shared/components/list-config-menu';
@@ -11,12 +11,17 @@ import {
usePlayerSpeed, usePlayerSpeed,
usePlayerStatus, usePlayerStatus,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store'; import {
usePlaybackSettings,
useSettingsStore,
useSettingsStoreActions,
} from '/@/renderer/store/settings.store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Popover } from '/@/shared/components/popover/popover'; import { Popover } from '/@/shared/components/popover/popover';
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control'; import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
import { Select } from '/@/shared/components/select/select'; import { Select } from '/@/shared/components/select/select';
import { Slider } from '/@/shared/components/slider/slider'; import { Slider } from '/@/shared/components/slider/slider';
import { Switch } from '/@/shared/components/switch/switch';
import { toast } from '/@/shared/components/toast/toast'; import { toast } from '/@/shared/components/toast/toast';
import { import {
CrossfadeStyle, CrossfadeStyle,
@@ -42,9 +47,20 @@ export const PlayerConfig = () => {
const { crossfadeDuration, crossfadeStyle, transitionType } = usePlayerProperties(); const { crossfadeDuration, crossfadeStyle, transitionType } = usePlayerProperties();
const { setCrossfadeDuration, setCrossfadeStyle, setQueueType, setSpeed, setTransitionType } = const { setCrossfadeDuration, setCrossfadeStyle, setQueueType, setSpeed, setTransitionType } =
usePlayerActions(); usePlayerActions();
const preservePitch = useSettingsStore((state) => state.playback.preservePitch);
const playbackSettings = usePlaybackSettings(); const playbackSettings = usePlaybackSettings();
const { setSettings } = useSettingsStoreActions(); const { setSettings } = useSettingsStoreActions();
const setPreservePitch = useCallback(
(value: boolean) => {
setSettings({
playback: { ...playbackSettings, preservePitch: value },
});
},
[playbackSettings, setSettings],
);
const [audioDevices, setAudioDevices] = useState<{ label: string; value: string }[]>([]); const [audioDevices, setAudioDevices] = useState<{ label: string; value: string }[]>([]);
useEffect(() => { useEffect(() => {
@@ -288,26 +304,38 @@ export const PlayerConfig = () => {
id: 'playbackSpeed', id: 'playbackSpeed',
label: t('player.playbackSpeed', { postProcess: 'titleCase' }), label: t('player.playbackSpeed', { postProcess: 'titleCase' }),
}, },
{
component: (
<Switch
defaultChecked={preservePitch}
onChange={(e) => setPreservePitch(e.currentTarget.checked)}
/>
),
id: 'preservePitch',
label: t('setting.preservePitch', { postProcess: 'titleCase' }),
},
]; ];
return allOptions; return allOptions;
}, [ }, [
playbackSettings, t,
audioDevices,
status,
setSettings,
currentSong,
speed,
setSpeed,
queueType, queueType,
setQueueType, playbackSettings,
status,
audioDevices,
transitionType, transitionType,
setTransitionType, crossfadeStyle,
crossfadeDuration, crossfadeDuration,
setCrossfadeDuration, setCrossfadeDuration,
crossfadeStyle, speed,
setSpeed,
preservePitch,
currentSong?.bpm,
setQueueType,
setSettings,
setTransitionType,
setCrossfadeStyle, setCrossfadeStyle,
t, setPreservePitch,
]); ]);
return ( return (