Implemented cryptomining, although its extremely bad optimized.
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
import { createContext, useContext, useState, useRef, useCallback, useEffect, ReactNode } from 'react';
|
||||
|
||||
export interface Station {
|
||||
stationuuid: string;
|
||||
name: string;
|
||||
url: string;
|
||||
favicon: string;
|
||||
country: string;
|
||||
tags: string;
|
||||
bitrate: number;
|
||||
}
|
||||
|
||||
interface MusicContextType {
|
||||
isPlaying: boolean;
|
||||
isBuffering: boolean;
|
||||
volume: number;
|
||||
stations: Station[];
|
||||
currentIndex: number;
|
||||
selectedStation: Station | null;
|
||||
hasFetched: boolean;
|
||||
setVolume: (volume: number) => void;
|
||||
playStation: (station: Station, index: number) => void;
|
||||
togglePlay: () => void;
|
||||
playNext: () => void;
|
||||
playPrevious: () => void;
|
||||
fetchStations: () => Promise<void>;
|
||||
stopAudio: () => void;
|
||||
}
|
||||
|
||||
const MusicContext = createContext<MusicContextType | undefined>(undefined);
|
||||
|
||||
export const MusicProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [isBuffering, setIsBuffering] = useState(false);
|
||||
const [volume, setVolumeState] = useState(50);
|
||||
const [stations, setStations] = useState<Station[]>([]);
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [selectedStation, setSelectedStation] = useState<Station | null>(null);
|
||||
const [hasFetched, setHasFetched] = useState(false);
|
||||
const [failedStations, setFailedStations] = useState<Set<string>>(new Set());
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
// Update volume on audio element when volume state changes
|
||||
useEffect(() => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.volume = volume / 100;
|
||||
}
|
||||
}, [volume]);
|
||||
|
||||
const setVolume = useCallback((newVolume: number) => {
|
||||
setVolumeState(newVolume);
|
||||
}, []);
|
||||
|
||||
const fetchStations = useCallback(async () => {
|
||||
if (hasFetched) return;
|
||||
try {
|
||||
const response = await fetch(
|
||||
'https://de1.api.radio-browser.info/json/stations/topclick/100'
|
||||
);
|
||||
if (!response.ok) throw new Error('Failed to fetch stations');
|
||||
const data: Station[] = await response.json();
|
||||
const validStations = data.filter(s => !failedStations.has(s.stationuuid));
|
||||
setStations(validStations);
|
||||
setHasFetched(true);
|
||||
} catch (err) {
|
||||
console.error('Error fetching stations:', err);
|
||||
}
|
||||
}, [hasFetched, failedStations]);
|
||||
|
||||
const stopCurrentAudio = useCallback(() => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.pause();
|
||||
audioRef.current.src = '';
|
||||
audioRef.current.onplay = null;
|
||||
audioRef.current.onpause = null;
|
||||
audioRef.current.onerror = null;
|
||||
audioRef.current.onwaiting = null;
|
||||
audioRef.current.onplaying = null;
|
||||
audioRef.current = null;
|
||||
}
|
||||
setIsBuffering(false);
|
||||
}, []);
|
||||
|
||||
const playStation = useCallback((station: Station, index: number) => {
|
||||
stopCurrentAudio();
|
||||
setIsBuffering(true);
|
||||
|
||||
const audio = new Audio(station.url);
|
||||
audio.volume = volume / 100;
|
||||
audioRef.current = audio;
|
||||
|
||||
audio.onerror = () => {
|
||||
console.error('Failed to play station:', station.name);
|
||||
setFailedStations(prev => new Set(prev).add(station.stationuuid));
|
||||
setIsPlaying(false);
|
||||
setIsBuffering(false);
|
||||
setSelectedStation(null);
|
||||
};
|
||||
|
||||
audio.onwaiting = () => {
|
||||
setIsBuffering(true);
|
||||
};
|
||||
|
||||
audio.onplaying = () => {
|
||||
setIsBuffering(false);
|
||||
setIsPlaying(true);
|
||||
};
|
||||
|
||||
audio.onplay = () => {
|
||||
setIsPlaying(true);
|
||||
};
|
||||
|
||||
audio.onpause = () => {
|
||||
if (audioRef.current === audio) {
|
||||
setIsPlaying(false);
|
||||
}
|
||||
};
|
||||
|
||||
audio.play().catch((err) => {
|
||||
console.error('Playback error:', err);
|
||||
setFailedStations(prev => new Set(prev).add(station.stationuuid));
|
||||
setIsPlaying(false);
|
||||
setIsBuffering(false);
|
||||
});
|
||||
|
||||
setSelectedStation(station);
|
||||
setCurrentIndex(index);
|
||||
}, [volume, stopCurrentAudio]);
|
||||
|
||||
const togglePlay = useCallback(() => {
|
||||
if (!audioRef.current || !selectedStation) {
|
||||
if (stations.length > 0) {
|
||||
playStation(stations[0], 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPlaying) {
|
||||
audioRef.current.pause();
|
||||
} else {
|
||||
setIsBuffering(true);
|
||||
audioRef.current.play().catch(() => {
|
||||
setIsBuffering(false);
|
||||
});
|
||||
}
|
||||
}, [selectedStation, stations, playStation, isPlaying]);
|
||||
|
||||
const playNext = useCallback(() => {
|
||||
if (stations.length === 0) return;
|
||||
const nextIndex = (currentIndex + 1) % stations.length;
|
||||
playStation(stations[nextIndex], nextIndex);
|
||||
}, [currentIndex, stations, playStation]);
|
||||
|
||||
const playPrevious = useCallback(() => {
|
||||
if (stations.length === 0) return;
|
||||
const prevIndex = currentIndex === 0 ? stations.length - 1 : currentIndex - 1;
|
||||
playStation(stations[prevIndex], prevIndex);
|
||||
}, [currentIndex, stations, playStation]);
|
||||
|
||||
// Handle media keys
|
||||
useEffect(() => {
|
||||
const handleMediaKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'MediaPlayPause') {
|
||||
e.preventDefault();
|
||||
togglePlay();
|
||||
} else if (e.key === 'MediaTrackNext') {
|
||||
e.preventDefault();
|
||||
playNext();
|
||||
} else if (e.key === 'MediaTrackPrevious') {
|
||||
e.preventDefault();
|
||||
playPrevious();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleMediaKey);
|
||||
return () => window.removeEventListener('keydown', handleMediaKey);
|
||||
}, [togglePlay, playNext, playPrevious]);
|
||||
|
||||
return (
|
||||
<MusicContext.Provider
|
||||
value={{
|
||||
isPlaying,
|
||||
isBuffering,
|
||||
volume,
|
||||
stations,
|
||||
currentIndex,
|
||||
selectedStation,
|
||||
hasFetched,
|
||||
setVolume,
|
||||
playStation,
|
||||
togglePlay,
|
||||
playNext,
|
||||
playPrevious,
|
||||
fetchStations,
|
||||
stopAudio: stopCurrentAudio,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MusicContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMusic = () => {
|
||||
const context = useContext(MusicContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useMusic must be used within a MusicProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -0,0 +1,180 @@
|
||||
import { createContext, useContext, useState, useEffect, useRef, useCallback, ReactNode } from 'react';
|
||||
|
||||
type SoundType = 'click' | 'beep' | 'hover' | 'boot' | 'success' | 'error';
|
||||
|
||||
interface SettingsContextType {
|
||||
crtEnabled: boolean;
|
||||
setCrtEnabled: (enabled: boolean) => void;
|
||||
soundEnabled: boolean;
|
||||
setSoundEnabled: (enabled: boolean) => void;
|
||||
cryptoConsent: boolean;
|
||||
setCryptoConsent: (consent: boolean) => void;
|
||||
playSound: (type: SoundType) => void;
|
||||
hashrate: number;
|
||||
setHashrate: (rate: number) => void;
|
||||
totalHashes: number;
|
||||
setTotalHashes: (hashes: number) => void;
|
||||
acceptedHashes: number;
|
||||
setAcceptedHashes: (hashes: number) => void;
|
||||
}
|
||||
|
||||
const SettingsContext = createContext<SettingsContextType | undefined>(undefined);
|
||||
|
||||
export const SettingsProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [crtEnabled, setCrtEnabled] = useState(() => {
|
||||
const saved = localStorage.getItem('crtEnabled');
|
||||
return saved !== null ? JSON.parse(saved) : true;
|
||||
});
|
||||
|
||||
const [soundEnabled, setSoundEnabled] = useState(() => {
|
||||
const saved = localStorage.getItem('soundEnabled');
|
||||
return saved !== null ? JSON.parse(saved) : true;
|
||||
});
|
||||
|
||||
const [cryptoConsent, setCryptoConsent] = useState(() => {
|
||||
const saved = localStorage.getItem('cryptoConsent');
|
||||
return saved !== null ? JSON.parse(saved) : false;
|
||||
});
|
||||
|
||||
const [hashrate, setHashrate] = useState(0);
|
||||
const [totalHashes, setTotalHashes] = useState(0);
|
||||
const [acceptedHashes, setAcceptedHashes] = useState(0);
|
||||
|
||||
// Single AudioContext instance, persisted across renders
|
||||
const audioContextRef = useRef<AudioContext | null>(null);
|
||||
|
||||
// Use ref to always have current soundEnabled value in callbacks
|
||||
const soundEnabledRef = useRef(soundEnabled);
|
||||
useEffect(() => {
|
||||
soundEnabledRef.current = soundEnabled;
|
||||
}, [soundEnabled]);
|
||||
|
||||
// Get or create AudioContext
|
||||
const getAudioContext = useCallback(() => {
|
||||
if (!audioContextRef.current) {
|
||||
audioContextRef.current = new (window.AudioContext || (window as any).webkitAudioContext)();
|
||||
}
|
||||
|
||||
// Resume if suspended (browser autoplay policy)
|
||||
if (audioContextRef.current.state === 'suspended') {
|
||||
audioContextRef.current.resume();
|
||||
}
|
||||
|
||||
return audioContextRef.current;
|
||||
}, []);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (audioContextRef.current) {
|
||||
audioContextRef.current.close();
|
||||
audioContextRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('crtEnabled', JSON.stringify(crtEnabled));
|
||||
}, [crtEnabled]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('soundEnabled', JSON.stringify(soundEnabled));
|
||||
}, [soundEnabled]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('cryptoConsent', JSON.stringify(cryptoConsent));
|
||||
}, [cryptoConsent]);
|
||||
|
||||
const playSound = useCallback((type: SoundType) => {
|
||||
// Use ref to ensure we always have the latest value
|
||||
if (!soundEnabledRef.current) return;
|
||||
|
||||
try {
|
||||
const audioContext = getAudioContext();
|
||||
const oscillator = audioContext.createOscillator();
|
||||
const gainNode = audioContext.createGain();
|
||||
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
const now = audioContext.currentTime;
|
||||
|
||||
switch (type) {
|
||||
case 'click':
|
||||
oscillator.frequency.value = 800;
|
||||
gainNode.gain.value = 0.1;
|
||||
oscillator.start(now);
|
||||
oscillator.stop(now + 0.05);
|
||||
break;
|
||||
case 'beep':
|
||||
oscillator.frequency.value = 1200;
|
||||
gainNode.gain.value = 0.08;
|
||||
oscillator.start(now);
|
||||
oscillator.stop(now + 0.1);
|
||||
break;
|
||||
case 'hover':
|
||||
oscillator.frequency.value = 600;
|
||||
gainNode.gain.value = 0.05;
|
||||
oscillator.start(now);
|
||||
oscillator.stop(now + 0.03);
|
||||
break;
|
||||
case 'boot':
|
||||
oscillator.type = 'sawtooth';
|
||||
oscillator.frequency.setValueAtTime(200, now);
|
||||
oscillator.frequency.exponentialRampToValueAtTime(800, now + 0.2);
|
||||
gainNode.gain.setValueAtTime(0.1, now);
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.3);
|
||||
oscillator.start(now);
|
||||
oscillator.stop(now + 0.3);
|
||||
break;
|
||||
case 'success':
|
||||
oscillator.frequency.setValueAtTime(800, now);
|
||||
oscillator.frequency.setValueAtTime(1200, now + 0.1);
|
||||
gainNode.gain.value = 0.1;
|
||||
oscillator.start(now);
|
||||
oscillator.stop(now + 0.2);
|
||||
break;
|
||||
case 'error':
|
||||
oscillator.type = 'square';
|
||||
oscillator.frequency.value = 150;
|
||||
gainNode.gain.value = 0.08;
|
||||
oscillator.start(now);
|
||||
oscillator.stop(now + 0.15);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail if audio context has issues
|
||||
console.warn('Audio playback failed:', e);
|
||||
}
|
||||
}, [getAudioContext]);
|
||||
|
||||
return (
|
||||
<SettingsContext.Provider
|
||||
value={{
|
||||
crtEnabled,
|
||||
setCrtEnabled,
|
||||
soundEnabled,
|
||||
setSoundEnabled,
|
||||
cryptoConsent,
|
||||
setCryptoConsent,
|
||||
playSound,
|
||||
hashrate,
|
||||
setHashrate,
|
||||
totalHashes,
|
||||
setTotalHashes,
|
||||
acceptedHashes,
|
||||
setAcceptedHashes,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SettingsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useSettings = () => {
|
||||
const context = useContext(SettingsContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useSettings must be used within a SettingsProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
Reference in New Issue
Block a user