Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🎶 Stef.fm 🎶

Music streaming service in a retro audio player in the browser

Stars License

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.

See it in Action

🌈 Stef.fm 🌈

Try it for Yourself

Clone repo

git clone https://github.com/rymix/steffm4
cd steffm

Yarn

yarn
Development
yarn run dev

http://localhost:3001

Production
yarn run build

Docker

docker build -t steffm .
docker run -dp 3001:3001 --rm --name steffm steffm

Custom Hooks & Contexts

This project includes several reusable React hooks and contexts that provide core functionality across the application.

🎙️ Voice Context (useVoice)

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.

Features

  • 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

Architecture

  • 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

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;
}

Usage

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>
  );
};

Benefits of Context Pattern

  • 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

💾 usePersistedState

Location: hooks/usePersistedState.ts

A hook that persists state to localStorage, providing automatic synchronization between React state and browser storage.

Features

  • Automatic localStorage persistence
  • SSR-safe (handles server-side rendering)
  • TypeScript generic support for type safety
  • JSON serialization/deserialization

Interface

function usePersistedState<T>(
  key: string,
  defaultValue: T
): [T, React.Dispatch<React.SetStateAction<T>>]

Usage

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>
  );
};

⏱️ useMasterTimer

Location: hooks/useMasterTimer.ts

A performance-optimized timer hook that consolidates multiple timers into a single setInterval, reducing resource usage and improving performance.

Features

  • Single master timer for all subscriptions
  • Automatic cleanup when no subscriptions remain
  • Subscription-based architecture
  • 100ms precision for accurate timing

Interface

interface MasterTimerHook {
  subscribe: (
    id: string,
    callback: () => void,
    interval: number
  ) => () => void; // Returns unsubscribe function
}

Usage

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>;
};

📱 useDeviceOrientation

Location: hooks/useDeviceOrientation.ts

A comprehensive hook for detecting device characteristics, screen size, and orientation changes with multiple detection methods.

Features

  • 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

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;
}

Detection Methods

  1. User Agent Client Hints (modern browsers)
  2. Touch capability + screen size combination
  3. User Agent string patterns (fallback)
  4. CSS Media Query matching

Usage

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>
  );
};

Best Practices

  • 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

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages