ShadowSpeak
A full-stack ESL learning platform where students practice pronunciation by recording themselves mimicking YouTube video segments, and teachers review submissions and provide feedback.
Live VersionRepo
Project Goal
Build a platform that replaces the scattered workflow ESL teachers use today: Google Drive for storage, email for submissions, and screen recording software for practice. Students can loop specific YouTube segments, record themselves in-browser, and submit directly. Teachers create lessons, assign them to students, and review submissions with feedback.
Tech Stack
- Next.js 15 with App Router, React 19, TypeScript
- Express.js backend with PostgreSQL database
- Azure Blob Storage for audio recordings and images
- JWT authentication with role-based access control
- SWR for data fetching with optimistic updates
- Material UI components, CSS Modules with container queries
Solo Project
This is my most comprehensive project. I built everything from scratch:
- Browser audio recording using MediaRecorder API with start, pause, resume, and stop controls
- YouTube segment looping via custom 100ms polling (YouTube API does not support native looping)
- Cloud storage pipeline: recordings converted to base64, uploaded to Azure Blob Storage, URLs persisted to database
- JWT auth with RBAC: teachers and students have different permissions enforced on both frontend and backend
- PostgreSQL schema with users, lessons, and assignments tables, proper foreign keys and cascade deletes
- State management using Context API + useReducer for complex recorder state machine
Solo Project
Technical Challenges Solved
YouTube Segment Looping
The YouTube embedded player does not support native segment looping. I built a custom solution using a useLoopButtons hook that polls the video position every 100ms. When playback reaches the end timestamp, it automatically seeks back to the start. The state machine manages transitions: idle, start_set, ready, looping.
Browser Audio Recording Pipeline
Recording audio in the browser and uploading to cloud storage required multiple integration steps: capture audio stream with MediaRecorder API, convert Blob to base64, send to Express backend, convert to Buffer, stream to Azure Blob Storage, return URL, and persist to PostgreSQL. All managed through a reducer pattern in RecorderPanelContext.
Role-Based Access Control
Implemented two-tier access: JWT tokens contain role claims ("teacher" or "student"), middleware validates every API request, and server-side route protection checks req.user.role !== "teacher" before allowing destructive operations. Students get 403 Forbidden if they try to access teacher endpoints.
What I Learned
- How to build a complete full-stack application from database schema to deployed frontend
- How to work with browser APIs (MediaRecorder) and cloud services (Azure Blob Storage)
- How to implement proper authentication and authorization with JWT and role-based access
- How to manage complex UI state using reducer patterns and state machines
- How to design relational database schemas with proper foreign keys and constraints
- How to handle file uploads with proper validation, size limits, and unique naming