mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
Tasks 1.1, 5.1, 5.2, 5.4 and the data half of 5.3. - mechanisms.toml: closed vocabulary v1 (20 ids) every expected row cites. - vibe-py: 35 Real-Vuln-Benchmark pairs (Apache-2.0), human provenance only because the LLM-generated repositories carry no license (escalated). - vfc-js: 17 Node fix-commit pairs harvested upstream (MIT/Apache-2.0), SecBench.js itself is not vendored (no license); sinks derived on 13/17. - agent-vfc: 29 agent-trailer fixes as catalog-candidates.toml plus REVIEW.md; the loaded catalog stays empty until a human reviews them. - datasets.toml and README document every slice and its review status. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
127 lines
4.0 KiB
TypeScript
127 lines
4.0 KiB
TypeScript
// Provenance: alexshao3/melodix (fixed).
|
|
// repo: alexshao3/melodix
|
|
// commit: 089931ef71ea1e5b7586520baf1a5498e2f05b13
|
|
// parent: c074fc7413338aefcbea3170ef4d3ba486a8a4b7
|
|
// commit_url: https://github.com/alexshao3/melodix/commit/089931ef71ea1e5b7586520baf1a5498e2f05b13
|
|
// cve:
|
|
// license: MIT
|
|
// function: PlayerProvider
|
|
// relpath: apps/miniapp/src/components/PlayerProvider.tsx
|
|
// provenance: agent
|
|
// mechanism: permissive_default
|
|
|
|
export function PlayerProvider({ children }: { children: React.ReactNode }) {
|
|
const audioRef = useRef<HTMLAudioElement | null>(null);
|
|
const [currentTrack, setCurrentTrack] = useState<Track | null>(null);
|
|
const [queue, setQueue] = useState<Track[]>([]);
|
|
const [history, setHistory] = useState<Track[]>([]);
|
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
const [position, setPosition] = useState(0);
|
|
const [duration, setDuration] = useState(0);
|
|
const handleEndedRef = useRef<() => void>(() => {});
|
|
|
|
useEffect(() => {
|
|
const a = new Audio();
|
|
a.preload = 'metadata';
|
|
audioRef.current = a;
|
|
const t = () => setPosition(a.currentTime);
|
|
const l = () => setDuration(a.duration || 0);
|
|
const p = () => setIsPlaying(true);
|
|
const ps = () => setIsPlaying(false);
|
|
const e = () => handleEndedRef.current?.();
|
|
a.addEventListener('timeupdate', t);
|
|
a.addEventListener('loadedmetadata', l);
|
|
a.addEventListener('play', p);
|
|
a.addEventListener('pause', ps);
|
|
a.addEventListener('ended', e);
|
|
return () => {
|
|
a.pause();
|
|
a.removeEventListener('timeupdate', t);
|
|
a.removeEventListener('loadedmetadata', l);
|
|
a.removeEventListener('play', p);
|
|
a.removeEventListener('pause', ps);
|
|
a.removeEventListener('ended', e);
|
|
};
|
|
}, []);
|
|
|
|
const playTrack = useCallback((t: Track) => {
|
|
const a = audioRef.current;
|
|
if (!a) return;
|
|
a.src = t.streamUrl ?? t.audioUrl;
|
|
setCurrentTrack(t);
|
|
setPosition(0);
|
|
pushRecentlyPlayed(t);
|
|
// Best-effort server-side history mirror; `recordPlay` no-ops for guests.
|
|
void api.recordPlay(t.id);
|
|
a.play().catch(() => undefined);
|
|
}, []);
|
|
|
|
const play = useCallback(
|
|
(track: Track, newQueue?: Track[]) => {
|
|
tgHaptic('light');
|
|
if (newQueue) {
|
|
const idx = newQueue.findIndex((t) => t.id === track.id);
|
|
const upcoming =
|
|
idx >= 0 ? newQueue.slice(idx + 1) : newQueue.filter((t) => t.id !== track.id);
|
|
setQueue(upcoming);
|
|
}
|
|
if (currentTrack && currentTrack.id !== track.id) {
|
|
setHistory((h) => [...h.slice(-19), currentTrack]);
|
|
}
|
|
playTrack(track);
|
|
},
|
|
[currentTrack, playTrack],
|
|
);
|
|
|
|
const toggle = useCallback(() => {
|
|
const a = audioRef.current;
|
|
if (!a || !currentTrack) return;
|
|
tgHaptic('light');
|
|
if (a.paused) a.play();
|
|
else a.pause();
|
|
}, [currentTrack]);
|
|
|
|
const next = useCallback(() => {
|
|
if (queue.length === 0) return;
|
|
const [head, ...rest] = queue;
|
|
if (!head) return;
|
|
if (currentTrack) setHistory((h) => [...h.slice(-19), currentTrack]);
|
|
setQueue(rest);
|
|
playTrack(head);
|
|
}, [queue, currentTrack, playTrack]);
|
|
|
|
const prev = useCallback(() => {
|
|
const a = audioRef.current;
|
|
if (a && a.currentTime > 3) {
|
|
a.currentTime = 0;
|
|
return;
|
|
}
|
|
const last = history[history.length - 1];
|
|
if (!last) {
|
|
if (a) a.currentTime = 0;
|
|
return;
|
|
}
|
|
setHistory((h) => h.slice(0, -1));
|
|
if (currentTrack) setQueue((q) => [currentTrack, ...q]);
|
|
playTrack(last);
|
|
}, [history, currentTrack, playTrack]);
|
|
|
|
useEffect(() => {
|
|
handleEndedRef.current = () => next();
|
|
}, [next]);
|
|
|
|
const seek = useCallback((s: number) => {
|
|
const a = audioRef.current;
|
|
if (!a) return;
|
|
a.currentTime = s;
|
|
setPosition(s);
|
|
}, []);
|
|
|
|
const value = useMemo(
|
|
() => ({ currentTrack, queue, isPlaying, position, duration, play, toggle, next, prev, seek }),
|
|
[currentTrack, queue, isPlaying, position, duration, play, toggle, next, prev, seek],
|
|
);
|
|
|
|
return <PlayerContext.Provider value={value}>{children}</PlayerContext.Provider>;
|
|
}
|