Hey there! This is my take on making online learning actually meaningful by tracking real video-watching progress. No more cheating by skipping ahead or rewatching the same bit just to inflate your "complete" badge. 😜
Most e-learning platforms mark a lesson "complete" as soon as you hit the end of the video. But does that mean you actually watched everything? Nope! Users can skip around or rewatch the same parts, and the system still thinks they covered all the content. My solution:
- Track unique seconds: Only count time you haven’t seen before.
- Prevent skipping hype: If you jump to the end, you don’t get credit for the skipped bits.
- Save & Resume: Come back later, and the player picks up where you left off – with progress showing only real new content.
- Frontend: React + TypeScript, React Router, TailwindCSS + shadcn/ui, react-hook-form + Zod, Sonner toasts.
- Backend: Node.js + Express, MongoDB + Mongoose, Passport (Local, Google, GitHub), JWT auth, Celebrate for request validation, Winston logger.
- Video Hosting: Cloudinary (video uploads & thumbnails).
- Infrastructure:
dotenv+ Envalid for env vars, Helmet, CORS, rate limiting, structure following industry best practices.
- User picks a video from the list (fetched from Cloudinary).
- The video element fires
timeupdateevents (throttled to every 500ms). - Every new second is checked against a Set of already-seen seconds.
- If it’s a brand-new second, we start/extend a current interval
{ start, end }. - When there’s a gap (re-jump or pause), we push the old interval into
newIntervalsand start fresh.
- On the backend, we merge all overlapping/adjacent intervals and compute total unique watched seconds.
- Progress% =
(uniqueSeconds / videoDuration) * 100, rounded to 2 decimals.
-
On
pause,ended, or before unload, the frontend POSTs:{ videoId, watchedIntervals, lastPosition, videoDuration } -
Backend upserts the user’s progress in MongoDB, keyed by
{ userId, videoId }. -
When the user returns, we load saved intervals, repopulate the Set, merge them, set the video’s
currentTime, and display the true % watched.
-
Clone it
git clone https://github.com/arcc-hitt/ProofPlay.git cd ProofPlay -
Backend
cd server-
Create a new file named
.envin theserverfolder. -
Open
.env.example, copy all its contents, and paste them into the newly created.envfile. -
Fill in the required environment variables in
server/.env:MONGO_URI=your_mongo_connection_string PORT=5000 JWT_SECRET=your_jwt_secret GOOGLE_CLIENT_ID=your_google_client_id GOOGLE_CLIENT_SECRET=your_google_client_secret GITHUB_CLIENT_ID=your_github_client_id GITHUB_CLIENT_SECRET=your_github_client_secret BACKEND_URL=http://localhost:5000 FRONTEND_URL=http://localhost:5173 CLOUDINARY_CLOUD_NAME=your_cloudinary_cloud_name CLOUDINARY_API_KEY=your_cloudinary_api_key CLOUDINARY_API_SECRET=your_cloudinary_api_secret -
Install dependencies and start the backend server:
npm install npm run dev
-
The backend will run on http://localhost:5000 by default.
-
Frontend
cd client-
Create a new file named
.envin theclientfolder. -
Open
client/.env.example, copy all its contents, and paste them into the newly createdclient/.envfile. -
Fill in the required environment variable:
VITE_BACKEND_URL=http://localhost:5000 VITE_FRONTEND_URL=http://localhost:5173 VITE_GOOGLE_AUTH_URL=http://localhost:5000/auth/google VITE_GITHUB_AUTH_URL=http://localhost:5000/auth/github -
Install dependencies and start the frontend server:
npm install npm run dev
-
The frontend will run on http://localhost:5173 by default.
- Visit
http://localhost:5173, sign up/login, pick a video, and watch real progress happen!
- Modular & Typed: TypeScript + Zod + Celebrate keep things predictable.
- Social Login: Google & GitHub OAuth alongside email/password.
- Security: Helmet, CORS, rate-limiter, JWT, bcrypt hashing.
- Scalable: Mongoose schemas with indexes, cloud-based video storage.
- UX Smoothness: Throttled events, toasts, loading states, responsive design.
- Merging Overlaps: Handling adjacent/overlapping intervals was tricky. I wrote a merge loop that groups and coalesces them before saving.
- Fast Forwards: Jumping ahead shouldn’t inflate progress—my logic checks only newly seen seconds.
- Resilience: Network hiccups? Global axios interceptors log out on 401/403, display friendly errors, and retry pipelines.
- Cool analytics dashboard: show which parts of lectures get skipped most.
- WebSockets for real-time multi-user sessions.
- Mobile app version with React Native.
