Skip to content

harshdeepsingh13/music-player

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Unconventional Music Player

A gesture-driven, accessibility-first music player — playback is controlled with simple pointer gestures on a large full-surface target instead of small, precise transport buttons.

Live demo: https://unconventional-player.theharshdeepsingh.com

Overview

Unconventional Music Player is a full-stack web app (React client + Express/MongoDB API) for uploading, organizing, and playing back personal audio. Its distinguishing idea is a gesture-driven interaction model: rather than aiming a cursor at tiny play/pause/seek controls, the user performs simple gestures anywhere on the player surface — a tap to toggle playback, a press-and-hold to rewind. Media is stored and delivered through Cloudinary so audio is never served from the application server.

Problem statement

Conventional media transport controls are small, densely packed, and demand precise pointer aiming — play, pause, and seek are typically separate buttons only a few pixels wide. That is awkward for anyone who cannot rely on fine pointer accuracy. This project explores a gesture-first alternative: collapse the most common actions (play/pause and rewind) onto one large interaction surface, where the gesture the user performs — a quick tap versus a sustained press-and-hold — determines the action, rather than where they aim. The result is an accessibility-minded control scheme that trades pixel precision for simple, forgiving gestures.

Goals

  • Make the primary playback actions reachable through simple gestures on a single large target instead of multiple small buttons.
  • Distinguish intent through the gesture itself (tap vs. press-and-hold) rather than spatial precision.
  • Let the user tune the rewind step to their own preference and persist it.
  • Provide a self-hostable, account-based library backed by scalable media storage.

Key features

  • Tap-anywhere play/pause — a single tap gesture anywhere on the player surface toggles between play and pause.
  • Press-and-hold rewind gesture — holding the surface for ~500ms pauses playback and then repeatedly seeks backward by the user's configured interval until release, at which point playback resumes. Implemented via a reusable useLongClick hook that separates a tap, a hold start, the in-progress hold loop, and release.
  • Configurable rewind interval — users pick a rewind step (5 / 10 / 15 / 20 seconds); the choice is stored on the user record and mirrored to localStorage.
  • Right-click context menu — a custom context menu positioned at the cursor offers Rename and Delete for a media item.
  • Account system — register and log in with JWT-based auth; passwords are hashed with bcryptjs and all /music API routes are gated by an authentication middleware.
  • Media library CRUD — upload, list, rename, and delete tracks tied to the signed-in user.
  • Cloudinary-backed storage — uploaded audio is pushed to Cloudinary and only its URL/metadata are persisted in MongoDB.

Architecture & key decisions

Client/server split. The frontend is a Create React App (React 18 + React Router + React-Bootstrap, styled with SCSS). The backend is an Express API over MongoDB via Mongoose. In production (MODE=production) the same Express server also serves the compiled React build and provides an SPA fallback route; in development the client runs on its own dev server and proxies API calls to the backend.

Layered backend. The server follows a route → controller → model structure (api/v1/<resource>/{index,controller,model}.js) with cross-cutting concerns isolated in a services/ layer — cloudinary, jwt, password, logger (winston), and mongooseConnection. Controllers stay thin and delegate persistence and business logic, which keeps the HTTP boundary separate from data access.

Gesture layer as a reusable primitive. The gesture recognition is not hard-wired into the player. The useLongClick hook encapsulates the timing logic that turns raw pointer events into discrete gestures — a threshold to tell a tap from a press-and-hold, a repeating interval while held, and distinct callbacks for gesture start / while-held / finish / tap. The MediaPlayer composes that hook (tap → play/pause, hold → rewind loop) so the same gesture primitive could drive other surfaces. A native <audio controls> element remains available as a conventional fallback.

Cloudinary as the media CDN. Uploads are received with multer to a temporary disk location, streamed to Cloudinary with resource_type: "auto", and the local temp file is removed immediately after. MongoDB stores only fullUrl, providerName, and originalFileName. This keeps large binaries off the application server and lets delivery scale independently of the API.

Scope note: the gestures here are pointer gestures — tap and press-and-hold — distinguished by duration rather than motion or shape. Pointer position is used only to place the right-click context menu. This is the foundation of the gesture-driven control scheme; richer directional/motion gestures and non-pointer input modalities are noted under Future scope.

Tech stack

Frontend: React 18, React Router v6, React-Bootstrap + Bootstrap 5, Font Awesome, SCSS (node-sass), axios, web-vitals, prop-types.

Backend: Node.js, Express 4, Mongoose 6 (MongoDB), Cloudinary SDK, multer (uploads), jsonwebtoken + bcryptjs (auth), cors, morgan + winston (logging), dotenv.

Tooling: Create React App / react-scripts, Testing Library (jest-dom, react, user-event).

Getting started

Prerequisites

  • Node.js and npm
  • A MongoDB connection (e.g. MongoDB Atlas)
  • A Cloudinary account
  • The dev, server, and production scripts rely on concurrently, nodemon, and forever, which are not declared in package.json. Install them globally (or add them as dev dependencies) before using those scripts:
    npm install -g concurrently nodemon forever

Install

npm install

package.json defines a postinstall hook that runs npm run build, so installing also produces a production build.

Environment variables

Create a .env file in the project root:

# Database
MONGODB_ATLAS_CONNECTION_URL=

# Cloudinary
REACT_APP_CLOUDINARY_CLOUD_NAME=
REACT_APP_CLOUDINARY_API_KEY=
REACT_APP_CLOUDINARY_API_SECRET=
REACT_APP_CLOUDINARY_UPLOAD_PRESET=
REACT_APP_CLOUDINARY_FOLDER_NAME=

# Server (optional)
PORT=
MODE=

Run

Available scripts (from package.json):

npm run dev:start      # run API (nodemon) and client (react-scripts) together via concurrently
npm run server:start   # run only the API with nodemon
npm run client:start   # run only the React client
npm run build          # build the React client into ./build
npm start              # build, then start the server in production mode with forever
npm test               # run the Testing Library / Jest test runner

In development the React client proxies API requests to the backend server. In production, set MODE=production so the Express server serves the built client and the SPA fallback.

Future scope

  • Broaden the gesture vocabulary with directional/motion gestures and non-pointer modalities (keyboard, touch, swipe).
  • Add fast-forward and a configurable forward step to complement the rewind hold.
  • Surface upload progress and richer library views (search, sorting, playlists).
  • Replace console.log debugging with structured logging end-to-end and add automated tests for the API and the useLongClick hook.
  • Strengthen delete into a clear soft/hard delete contract using the existing isActive flag.

Built by Harshdeep Singh — https://theharshdeepsingh.com

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors