Building a Music Player in React
The “Music Player” project is a web application built using React that offers users an interface to play, pause, and manage their music collection. It has a responsive design making it effortless for users to enjoy their songs. It has a separate data file using which the users can add their own songs to the list and can listen to their personalized songs playlist.
Preview of Final Output: Let us have a look at how the final output will look like.
Prerequisites and Technologies Used in Media Player Application
Approach and Functionalities of Music Player
The Music Player project incorporates the following functionalities and approaches:
- User-friendly Interface: The project showcases an interface, with controls, for playing, pausing, adjusting volume, and tracking progress.
- Music Library Management: Users can effortlessly manage their music library by adding or removing songs and selecting tracks to play.
- Audio Controls: The application provides options to play, pause, and control volume levels.
- Track Progress Display: Users can easily track the progress of the playing song.
The design of the project is responsive. Functions effectively, on both desktop computers and mobile devices.
Steps to Create Music Player in React
Step 1: Create a new React JS project using the following command
npx create-react-app <<Project_Name>>
Step 2: Change to the project directory
cd word-letter-counter <<Project_Name>>
Step 3: Install some npm packages required for this project using the following command:
npm install --save @fortawesome/react-fontawesome
npm install --save @fortawesome/free-solid-svg-icons
npm install sass
Project Structure:
The updated dependencies in package.json will look like this:
"dependencies": {
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"sass": "^1.68.0",
"web-vitals": "^2.1.4"
}
Example: Write the following code in respective files
These two files App.js and data.js will be in the src folder
Javascript
// FileName: App.js import { useRef, useState } from "react" ; import Player from "./components/PlayerSong" ; import Song from "./components/Song" ; import "./styles/app.scss" ; // Importing DATA import data from "./data" ; import Library from "./components/Library" ; import Nav from "./components/Navb" ; function App() { const [songs, setSongs] = useState(data()); const [currentSong, setCurrentSong] = useState(songs[0]); const [isPlaying, setIsPlaying] = useState( false ); const [libraryStatus, setLibraryStatus] = useState( false ); const audioRef = useRef( null ); const [songInfo, setSongInfo] = useState({ currentTime: 0, duration: 0, animationPercentage: 0, }); const timeUpdateHandler = (e) => { const current = e.target.currentTime; const duration = e.target.duration; //calculating percentage const roundedCurrent = Math.round(current); const roundedDuration = Math.round(duration); const animation = Math.round((roundedCurrent / roundedDuration) * 100); console.log(); setSongInfo({ currentTime: current, duration, animationPercentage: animation, }); }; const songEndHandler = async () => { let currentIndex = songs.findIndex((song) => song.id === currentSong.id); await setCurrentSong(songs[(currentIndex + 1) % songs.length]); if (isPlaying) audioRef.current.play(); }; return ( <div> <Nav libraryStatus={libraryStatus} setLibraryStatus={setLibraryStatus} /> <Song currentSong={currentSong} /> <Player id={songs.id} songs={songs} songInfo={songInfo} setSongInfo={setSongInfo} audioRef={audioRef} isPlaying={isPlaying} setIsPlaying={setIsPlaying} currentSong={currentSong} setCurrentSong={setCurrentSong} setSongs={setSongs} /> <Library libraryStatus={libraryStatus} setLibraryStatus={setLibraryStatus} setSongs={setSongs} isPlaying={isPlaying} audioRef={audioRef} songs={songs} setCurrentSong={setCurrentSong} /> <audio onLoadedMetadata={timeUpdateHandler} onTimeUpdate={timeUpdateHandler} src={currentSong.audio} ref={audioRef} onEnded={songEndHandler} ></audio> </div> ); } export default App; |
Javascript
// FileName: data.js import { v4 as uuidv4 } from "uuid" ; function chillHop() { return [ { name: "Sunrise Serenade" , cover: "https://media.w3wiki.net/wp-content/uploads/20210224040124/JSBinCollaborativeJavaScriptDebugging6.png" , artist: " Harmony Harp" , audio: "https://media.w3wiki.net/wp-content/uploads/20231004185212/Jawan-Prevue-Theme.mp3" , color: [ "#205950" , "#2ab3bf" ], id: uuidv4(), active: true , }, { name: "Urban Groove" , cover: "https://media.w3wiki.net/wp-content/uploads/20231004210806/DemotivationalPosterfull936506.jpg" , artist: "Beatmaster B" , audio: "https://media.w3wiki.net/wp-content/uploads/20231004184006/SoundHelix-Song-10.mp3" , color: [ "#EF8EA9" , "#ab417f" ], id: uuidv4(), active: false , }, { name: "Mystic Echo" , cover: "https://media.w3wiki.net/wp-content/uploads/20231004210619/3408428b23c516b1687c748cb7de7be7.webp" , artist: " Harmony Harp" , audio: "https://media.w3wiki.net/wp-content/uploads/20231004185212/Jawan-Prevue-Theme.mp3" , color: [ "#CD607D" , "#c94043" ], id: uuidv4(), active: false , }, { name: "Electro Vibes" , cover: "https://media.w3wiki.net/wp-content/uploads/20231004184219/gfglogo0.jpg" , artist: "Synthwave Sensation" , audio: "https://media.w3wiki.net/wp-content/uploads/20231004191840/Zinda-Banda---Jawan-(1).mp3" , color: [ "#EF8EA9" , "#ab417f" ], id: uuidv4(), active: false , }, { name: "Jazzy Whispers" , cover: "https://media.w3wiki.net/wp-content/uploads/20231004210806/DemotivationalPosterfull936506.jpg" , artist: "Smooth Sax Serenade" , audio: "https://media.w3wiki.net/wp-content/uploads/20231004184006/SoundHelix-Song-10.mp3" , color: [ "#CD607D" , "#c94043" ], id: uuidv4(), active: false , }, { name: "Tropical Breez" , cover: "https://media.w3wiki.net/wp-content/uploads/20231004210619/3408428b23c516b1687c748cb7de7be7.webp" , artist: "Island Rhythms" , audio: "https://media.w3wiki.net/wp-content/uploads/20231004191840/Zinda-Banda---Jawan-(1).mp3" , color: [ "#205950" , "#2ab3bf" ], id: uuidv4(), active: false , }, ]; } export default chillHop; |
These five files Library.js, LibrarySong.js, PlayerSong.js, Navb.js and Song.js will be in the components folder of src folder.
Javascript
// FileName: Library.js import React from "react" ; import LibrarySong from "./LibrarySong" ; const Library = ({ songs, setCurrentSong, audioRef, isPlaying, setSongs, setLibraryStatus, libraryStatus, }) => { return ( <div className={`library ${libraryStatus ? "active" : "" }`}> <h2 style={{ color: "white" }}>All songs</h2> <div className= "library-songs" > {songs.map((song) => ( <LibrarySong setSongs={setSongs} isPlaying={isPlaying} audioRef={audioRef} songs={songs} song={song} setCurrentSong={setCurrentSong} id={song.id} key={song.id} /> ))} </div> </div> ); }; export default Library; |
Javascript
// FileName: LibrarySong.js import React from "react" ; const LibrarySong = ({ song, songs, setCurrentSong, audioRef, isPlaying, setSongs, id, }) => { const songSelectHandler = async () => { await setCurrentSong(song); //active const newSongs = songs.map((song) => { if (song.id === id) { return { ...song, active: true , }; } else { return { ...song, active: false , }; } }); setSongs(newSongs); //check if song is playing if (isPlaying) audioRef.current.play(); }; return ( <div onClick={songSelectHandler} className={`library-song ${song.active ? "selected" : "" }`} > <img src={song.cover} alt={song.name} /> <div className= "song-description" > <h3>{song.name}</h3> <h4>{song.artist}</h4> </div> </div> ); }; export default LibrarySong; |
Javascript
// FileName: Navb.js import React from "react" ; const Nav = ({ setLibraryStatus, libraryStatus }) => { return ( <nav> <h1>w3wiki Music Player</h1> <button onClick={() => { setLibraryStatus(!libraryStatus); }} > <h4>Library</h4> </button> </nav> ); }; export default Nav; |
Javascript
// FileName: PlayerSong.js import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" ; import { faPlay, faAngleLeft, faAngleRight, faPause, } from "@fortawesome/free-solid-svg-icons" ; const Player = ({ currentSong, isPlaying, setIsPlaying, audioRef, setSongInfo, songInfo, songs, setCurrentSong, id, setSongs, }) => { //useEffect const activeLibraryHandler = (nextPrev) => { const newSongs = songs.map((song) => { if (song.id === nextPrev.id) { return { ...song, active: true , }; } else { return { ...song, active: false , }; } }); setSongs(newSongs); console.log( "Hey from useEffect form player JS" ); }; //Event Handlers const dragHandler = (e) => { audioRef.current.currentTime = e.target.value; setSongInfo({ ...songInfo, currentTime: e.target.value }); }; const playSongHandler = () => { if (isPlaying) { audioRef.current.pause(); setIsPlaying(!isPlaying); } else { audioRef.current.play(); setIsPlaying(!isPlaying); } }; const getTime = (time) => Math.floor(time / 60) + ":" + ( "0" + Math.floor(time % 60)).slice(-2); const skipTrackHandler = async (direction) => { let currentIndex = songs.findIndex( (song) => song.id === currentSong.id ); if (direction === "skip-forward" ) { await setCurrentSong(songs[(currentIndex + 1) % songs.length]); activeLibraryHandler(songs[(currentIndex + 1) % songs.length]); } if (direction === "skip-back" ) { if ((currentIndex - 1) % songs.length === -1) { await setCurrentSong(songs[songs.length - 1]); // playAudio(isPlaying, audioRef); activeLibraryHandler(songs[songs.length - 1]); return ; } await setCurrentSong(songs[(currentIndex - 1) % songs.length]); activeLibraryHandler(songs[(currentIndex - 1) % songs.length]); } if (isPlaying) audioRef.current.play(); }; //adding the styles const trackAnim = { transform: `translateX(${songInfo.animationPercentage}%)`, }; return ( <div className= "player" > <div className= "time-control" > <p>{getTime(songInfo.currentTime)}</p> <div style={{ background: `linear-gradient(to right, ${currentSong.color[0]}, ${currentSong.color[1]})`, }} className= "track" > <input min={0} max={songInfo.duration || 0} value={songInfo.currentTime} onChange={dragHandler} type= "range" /> <div style={trackAnim} className= "animate-track" ></div> </div> <p> {songInfo.duration ? getTime(songInfo.duration) : "00:00" } </p> </div> <div className= "play-control" > <FontAwesomeIcon onClick={() => skipTrackHandler( "skip-back" )} size= "2x" className= "skip-back" icon={faAngleLeft} /> {!isPlaying ? ( <FontAwesomeIcon onClick={playSongHandler} size= "2x" className= "play" icon={faPlay} /> ) : ( <FontAwesomeIcon onClick={playSongHandler} size= "2x" className= "pause" icon={faPause} /> )} <FontAwesomeIcon onClick={() => skipTrackHandler( "skip-forward" )} size= "2x" className= "skip-forward" icon={faAngleRight} /> </div> </div> ); }; export default Player; |
Javascript
// FileName: Song.js import React from "react" ; const Song = ({ currentSong }) => { return ( <div className= "song-container" > <img src={currentSong.cover} alt={currentSong.name} /> <h2>{currentSong.name}</h2> <h3>{currentSong.artist}</h3> </div> ); }; export default Song; |
These five files library.scss, app.scss, nav.scss, player.scss and song.scss will be in the styles folder of src folder.
CSS
/* FileName: app.scss */ * { margin : 0 ; padding : 0 ; box-sizing: border-box; font-family : "Gilroy" , sans-serif ; } body { background : rgb ( 231 , 235 , 214 ); background : linear-gradient( 180 deg, rgba( 231 , 235 , 214 , 1 ) 0% , rgba( 55 , 102 , 44 , 1 ) 100% ); } @import "./library" ; @import "./player" ; @import "./song" ; @import "./nav" ; h 2 , h 3 { color : #133a1b ; } h 3 , h 4 { font-weight : 600 ; } button { font-weight : 700 ; } |
CSS
/* FileName: library.scss */ .library { position : fixed ; top : 0 ; left : 0 ; width : 20 rem; height : 100% ; background : #32522b ; box-shadow: 2px 2px 50px rgba( 0 , 0 , 0 , 0.205 ); overflow : scroll ; transform: translateX( -100% ); transition: all 0.2 s ease; opacity: 0 ; h 2 { padding : 2 rem; } } .library-song { display : flex; align-items: center ; padding : 1 rem 2 rem 1 rem 2 rem; img { width : 30% ; } &:hover { background : rgb ( 120 , 248 , 160 ); } } .song-description { padding-left : 1 rem; h 3 { color : #ffffff ; font-size : 1 rem; } h 4 { color : gray ; font-size : 0.7 rem; } } ::-webkit-scrollbar { width : 5px ; } ::-webkit-scrollbar-thumb { background : rgb ( 255 , 183 , 183 ); border-radius: 10px ; } ::-webkit-scrollbar-track { background : rgb ( 221 , 221 , 221 ); } .selected { background : rgb ( 255 , 230 , 255 ); h 3 { color : #306b26 ; } } .active { transform: translateX( 0% ); opacity: 1 ; } @media screen and ( max-width : 768px ) { .library { width : 100% ; } } |
CSS
/*FileName: nav.scss */ h 1 , h 4 { color : rgb ( 9 , 70 , 9 ); } nav { min-height : 10 vh; display : flex; justify- content : center ; align-items: center ; margin : 20px ; button { background : transparent ; border : none ; cursor : pointer ; font-size : 16px ; margin-left : 20% ; border : 2px solid rgb ( 41 , 216 , 25 ); padding : 0.8 rem; transition: all 0.3 s ease; &:hover { background : rgb ( 89 , 219 , 77 ); color : white ; } } } @media screen and ( max-width : 768px ) { nav { button { z-index : 10 ; } } } |
CSS
/* FileName: player.scss */ .player { min-height : 20 vh; display : flex; flex- direction : column; align-items: center ; justify- content : space-between; } .time-control { width : 50% ; display : flex; align-items: center ; input { width : 100% ; background-color : transparent ; cursor : pointer ; } p { padding : 1 rem; font-weight : 700 ; } } .play-control { display : flex; justify- content : space-between; align-items: center ; padding : 1 rem; width : 30% ; svg { cursor : pointer ; } } input[type= "range" ]:focus { outline : none ; } input[type= "range" ]::-webkit-slider-thumb { -webkit-appearance: none ; height : 16px ; width : 16px ; } .track { background : lightblue; width : 100% ; height : 1 rem; position : relative ; border-radius: 1 rem; overflow : hidden ; } .animate-track { background : rgb ( 204 , 204 , 204 ); width : 100% ; height : 100% ; position : absolute ; top : 0 ; left : 0 ; transform: translateX( 0% ); pointer-events: none ; } @media screen and ( max-width : 768px ) { .time-control { width : 90% ; } .play-control { width : 80% ; } } |
CSS
/* FileName: song.scss */ .song-container { min-height : 60 vh; display : flex; flex- direction : column; align-items: center ; justify- content : center ; img { width : 20% ; // } h 2 { padding : 3 rem 1 rem 1 rem 1 rem; } h 3 { font-size : 1 rem; } } @media screen and ( max-width : 768px ) { .song-container { img { width : 60% ; } } } @keyframes rotate { from { transform: rotate( 0 deg); } to { transform: rotate( 360 deg); } } .rotateSong { animation: rotate 20 s linear forwards infinite; } |
Steps to run the project:
Step 1: Type the following command in terminal.
npm start
Step 2: Open web-browser and type the following URL
http://localhost:3000/
Output: