mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
Merge branch 'main' into exp/orbit (pre-PR sync)
Conflicts resolved in: - src/pages/SearchResults.tsx - src/pages/AdvancedSearch.tsx Both pages were rewritten on main (PR #303) to use the shared <SongRow> component with click-to-enqueueAndPlay semantics. Orbit's playSong helper that branched on orbit-active is no longer needed at the page level — instead, orbit awareness moved INTO SongRow and SongCard themselves: in an active orbit session both buttons collapse into addTrackToOrbit (suggest for guests, host-enqueue for the host) so we don't ship a queue replacement to every guest. Also kept main's IntersectionObserver-based pagination on both pages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import type { SubsonicSong } from '../api/subsonic';
|
||||
|
||||
function fadeOut(setVolume: (v: number) => void, from: number, durationMs: number): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const steps = 16;
|
||||
const stepMs = durationMs / steps;
|
||||
let step = 0;
|
||||
const id = setInterval(() => {
|
||||
step++;
|
||||
setVolume(Math.max(0, from * (1 - step / steps)));
|
||||
if (step >= steps) {
|
||||
clearInterval(id);
|
||||
resolve();
|
||||
}
|
||||
}, stepMs);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a single song. When `queue` is provided, surrounds the chosen song with that queue
|
||||
* so Next/Prev work — pass the rail / pool the click came from. Mirrors playAlbum's fade-out.
|
||||
*/
|
||||
export async function playSongNow(song: SubsonicSong, queue?: SubsonicSong[]): Promise<void> {
|
||||
const track = songToTrack(song);
|
||||
const tracks = queue && queue.length > 0
|
||||
? queue.map(songToTrack)
|
||||
: [track];
|
||||
|
||||
const store = usePlayerStore.getState();
|
||||
const { isPlaying, volume } = store;
|
||||
|
||||
if (isPlaying) {
|
||||
await fadeOut(store.setVolume, volume, 700);
|
||||
usePlayerStore.setState({ volume });
|
||||
}
|
||||
|
||||
usePlayerStore.getState().playTrack(track, tracks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the song to the existing queue (if not already there) and immediately jump to it.
|
||||
* Existing queue stays intact — different from playSongNow which replaces the queue.
|
||||
*/
|
||||
export async function enqueueAndPlay(song: SubsonicSong): Promise<void> {
|
||||
const track = songToTrack(song);
|
||||
const store = usePlayerStore.getState();
|
||||
const { isPlaying, volume, queue } = store;
|
||||
|
||||
if (isPlaying) {
|
||||
await fadeOut(store.setVolume, volume, 700);
|
||||
usePlayerStore.setState({ volume });
|
||||
}
|
||||
|
||||
if (!queue.some(t => t.id === track.id)) {
|
||||
usePlayerStore.getState().enqueue([track]);
|
||||
}
|
||||
// playTrack with no queue arg uses the current state.queue, finds the track by id,
|
||||
// and sets queueIndex accordingly.
|
||||
usePlayerStore.getState().playTrack(track);
|
||||
}
|
||||
Reference in New Issue
Block a user