feat(autodj): smooth skip and interrupt blend transitions (#1128)

This commit is contained in:
cucadmuh
2026-06-19 04:52:15 +03:00
committed by GitHub
parent d50c9c444d
commit 4225146a16
50 changed files with 1118 additions and 208 deletions
@@ -1,8 +1,9 @@
import React from 'react';
import { Moon, Pause, Play, Repeat, Repeat1, SkipBack, SkipForward, Square, Sunrise } from 'lucide-react';
import { Blend, Moon, Pause, Play, Repeat, Repeat1, SkipBack, SkipForward, Square, Sunrise } from 'lucide-react';
import { invoke } from '@tauri-apps/api/core';
import type { TFunction } from 'i18next';
import type { PlayerState } from '../../store/playerStoreTypes';
import { useAutodjTransitionUi } from '../../store/autodjTransitionUi';
import { usePreviewStore } from '../../store/previewStore';
import PlaybackScheduleBadge from '../PlaybackScheduleBadge';
import { usePlaybackDelayPress } from '../../hooks/usePlaybackDelayPress';
@@ -32,6 +33,10 @@ export function PlayerTransportControls({
isPlaying, isRadio, isPreviewing, stop, previous, next, toggleRepeat, repeatMode,
playPauseBind, scheduleRemaining, transportAnchorRef, playSlotRef, t,
}: Props) {
const autodjPhase = useAutodjTransitionUi(s => s.phase);
const showAutodjTransition =
isPlaying && !isPreviewing && scheduleRemaining == null && autodjPhase === 'mixing';
return (
<div className="player-buttons" ref={transportAnchorRef}>
<button
@@ -68,7 +73,11 @@ export function PlayerTransportControls({
</svg>
)}
<button
className={`player-btn player-btn-primary${isPreviewing ? ' is-previewing' : ''}`}
className={[
'player-btn player-btn-primary',
isPreviewing ? 'is-previewing' : '',
showAutodjTransition ? 'is-autodj-transition' : '',
].filter(Boolean).join(' ')}
type="button"
{...playPauseBind}
onClick={isPreviewing
@@ -80,8 +89,16 @@ export function PlayerTransportControls({
invoke('audio_preview_stop').catch(() => {});
})
: playPauseBind.onClick}
aria-label={isPreviewing ? t('playlists.previewStop') : isPlaying ? t('player.pause') : t('player.play')}
data-tooltip={isPreviewing ? t('playlists.previewStop') : isPlaying ? t('player.pause') : t('player.play')}
aria-label={isPreviewing
? t('playlists.previewStop')
: showAutodjTransition
? t('player.autoDjMixing')
: isPlaying ? t('player.pause') : t('player.play')}
data-tooltip={isPreviewing
? t('playlists.previewStop')
: showAutodjTransition
? t('player.autoDjMixing')
: isPlaying ? t('player.pause') : t('player.play')}
>
{scheduleRemaining != null ? (
<span className={`player-btn-schedule-stack player-btn-schedule-stack--${scheduleRemaining.mode}`}>
@@ -92,6 +109,8 @@ export function PlayerTransportControls({
</span>
) : isPreviewing ? (
<Square size={16} fill="currentColor" strokeWidth={0} />
) : showAutodjTransition ? (
<Blend size={22} className="player-btn-autodj-icon" strokeWidth={2.25} />
) : isPlaying ? <Pause size={22} fill="currentColor" /> : <Play size={22} fill="currentColor" />}
</button>
</span>
@@ -69,9 +69,19 @@ export function PlaybackBehaviorBlock({ t }: Props) {
</div>
)}
{mode === 'autodj' && (
<div style={{ paddingLeft: '1rem', fontSize: 12, color: 'var(--text-muted)', marginTop: '0.7rem' }}>
{t('settings.autoDjDesc')}
</div>
<>
<div style={{ paddingLeft: '1rem', fontSize: 12, color: 'var(--text-muted)', marginTop: '0.7rem' }}>
{t('settings.autoDjDesc')}
</div>
<div style={{ paddingLeft: '1rem', marginTop: '0.7rem' }}>
<SettingsToggle
label={t('settings.autodjSmoothSkip')}
desc={t('settings.autodjSmoothSkipDesc')}
checked={auth.autodjSmoothSkip}
onChange={auth.setAutodjSmoothSkip}
/>
</div>
</>
)}
</SettingsGroup>