Stef.fm is an online music streaming platform which I am using as a permanent home for my late friend's amazing house music mix collection. I explain more in my LinkedIn article.
The UI is based on the Roland Jupiter-8 and Yamaha DX7 keyboards from the 1980s. It's a classic design that I've always loved.
🌈 Stef.fm 🌈
git clone https://github.com/rymix/steffm4
cd steffm
yarn
yarn run dev
yarn run build
docker build -t steffm .
docker run -dp 3001:3001 --rm --name steffm steffm
This project includes several reusable React hooks and contexts that provide core functionality across the application.
Location: contexts/voice/
A comprehensive context for voice command functionality using Porcupine wake word detection and Web Speech API. This context provides singleton voice control that can be safely used across multiple components.
- Wake word detection ("Hey Stef")
- Speech-to-text recognition
- Fuzzy command matching with scoring system
- Automatic command timeout and error handling
- Debug logging and state management
- Singleton pattern prevents conflicts when used in multiple components
- Context Factory Pattern: Uses the same factory pattern as MixcloudContext
- Centralized State: Single voice control instance shared across the app
- Type Safety: Full TypeScript support with comprehensive interfaces
interface VoiceContextState {
// Status and state
status: VoiceStatus;
isLoaded: boolean;
isListening: boolean;
isCommandListening: boolean;
error: any;
lastCommand: string;
debugInfo: string[];
keywordDetections: string[];
// Control functions
start: () => void;
stop: () => void;
testCommandListening: () => Promise<void>;
// Utility functions
getStatusMessage: () => string;
}import { VoiceProvider, useVoice } from "contexts/voice";
// 1. Wrap your app or component tree with VoiceProvider
const App = () => (
<VoiceProvider>
<MyVoiceComponents />
</VoiceProvider>
);
// 2. Use the voice context in any child component
const VoiceStatusComponent = () => {
const { status, start, stop, isListening } = useVoice();
return (
<div>
<p>Voice Status: {status}</p>
<button onClick={start}>Start Voice Control</button>
<button onClick={stop}>Stop Voice Control</button>
</div>
);
};
// 3. Multiple components can safely use the same voice instance
const VoiceControlPanel = () => {
const { lastCommand, debugInfo, testCommandListening } = useVoice();
return (
<div>
<p>Last Command: {lastCommand}</p>
<button onClick={testCommandListening}>Test Voice</button>
<div>Debug: {debugInfo.join(", ")}</div>
</div>
);
};- No Conflicts: Multiple components can use voice control without interference
- Shared State: All components see the same voice control status
- Resource Efficiency: Single Porcupine instance and speech recognition
- Consistent Behavior: Same voice control behavior across the entire app
Location: hooks/usePersistedState.ts
A hook that persists state to localStorage, providing automatic synchronization between React state and browser storage.
- Automatic localStorage persistence
- SSR-safe (handles server-side rendering)
- TypeScript generic support for type safety
- JSON serialization/deserialization
function usePersistedState<T>(
key: string,
defaultValue: T
): [T, React.Dispatch<React.SetStateAction<T>>]import usePersistedState from "hooks/usePersistedState";
const MyComponent = () => {
const [theme, setTheme] = usePersistedState("user-theme", "dark");
const [settings, setSettings] = usePersistedState("app-settings", {
volume: 0.5,
autoplay: true
});
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
Toggle Theme
</button>
</div>
);
};Location: hooks/useMasterTimer.ts
A performance-optimized timer hook that consolidates multiple timers into a single setInterval, reducing resource usage and improving performance.
- Single master timer for all subscriptions
- Automatic cleanup when no subscriptions remain
- Subscription-based architecture
- 100ms precision for accurate timing
interface MasterTimerHook {
subscribe: (
id: string,
callback: () => void,
interval: number
) => () => void; // Returns unsubscribe function
}import { useMasterTimer } from "hooks/useMasterTimer";
const MyComponent = () => {
const { subscribe } = useMasterTimer();
useEffect(() => {
// Subscribe to a timer that runs every 1000ms
const unsubscribe = subscribe("my-timer", () => {
console.log("Timer tick!");
}, 1000);
// Cleanup subscription
return unsubscribe;
}, [subscribe]);
return <div>Check console for timer ticks</div>;
};Location: hooks/useDeviceOrientation.ts
A comprehensive hook for detecting device characteristics, screen size, and orientation changes with multiple detection methods.
- Multi-method mobile device detection
- Screen size and orientation tracking
- Special mode detection (skinny/tall wide modes)
- Real-time updates on resize/orientation change
- SSR-safe implementation
interface DeviceOrientationState {
isSmallScreen: boolean;
isMobile: boolean;
isPortrait: boolean;
isLandscape: boolean;
isSkinnyWideMode: boolean; // height < 450 && landscape
isTallWideMode: boolean; // 450 <= height < 650 && landscape
windowWidth: number;
windowHeight: number;
}- User Agent Client Hints (modern browsers)
- Touch capability + screen size combination
- User Agent string patterns (fallback)
- CSS Media Query matching
import { useDeviceOrientation } from "hooks/useDeviceOrientation";
const MyComponent = () => {
const {
isMobile,
isPortrait,
isLandscape,
isSkinnyWideMode,
windowWidth,
windowHeight
} = useDeviceOrientation();
return (
<div>
<p>Device: {isMobile ? "Mobile" : "Desktop"}</p>
<p>Orientation: {isPortrait ? "Portrait" : "Landscape"}</p>
<p>Screen: {windowWidth} × {windowHeight}</p>
{isSkinnyWideMode && <p>Optimized for skinny wide screens</p>}
</div>
);
};- Context Factory Pattern: Use contexts (like Voice) for complex stateful logic that needs to be shared across components
- Hook Pattern: Use hooks for simple, reusable stateful logic that doesn't need sharing
- All hooks and contexts are TypeScript-enabled with proper type definitions
- SSR-safe implementations where applicable
- Proper cleanup and memory management
- Performance optimizations (like timer consolidation and singleton contexts)
- Comprehensive error handling and fallbacks