React 构建一个音乐播放器
“音乐播放器”项目是一个使用React构建的web应用程序,为用户提供播放、暂停和管理音乐收藏的界面。它具有响应式设计,使用户可以轻松地享受自己的歌曲。它有一个单独的数据文件,用户可以使用该文件将自己的歌曲添加到列表中,并且可以听自己个性化的歌曲播放列表。
最终输出的预览: 让我们来看一下最终输出的样子。
媒体播放器应用的先决条件和使用的技术
- Node.js
- React
- JavaScript
- HTML/CSS
音乐播放器的方法和功能
音乐播放器项目包括以下功能和方法:
- 用户友好的界面:该项目展示了一个界面,带有控制按钮,用于播放、暂停、调整音量和跟踪进度。
- 音乐库管理:用户可以轻松管理自己的音乐库,添加或删除歌曲并选择要播放的曲目。
- 音频控制:应用程序提供播放、暂停和控制音量级别的选项。
- 曲目进度显示:用户可以轻松跟踪正在播放的歌曲的进度。
项目设计响应式。在桌面电脑和移动设备上都能有效运行。
使用React创建音乐播放器的步骤
步骤1: 使用以下命令创建一个新的React JS项目
npx create-react-app <<Project_Name>>
步骤2: 切换到项目目录
cd word-letter-counter <<Project_Name>>
步骤3: 使用以下命令安装此项目所需的一些npm包:
npm install --save @fortawesome/react-fontawesome
npm install --save @fortawesome/free-solid-svg-icons
npm install sass
项目结构:
更新的依赖关系在< strong> package.json **中,将会如下所示:
"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"
}
示例: 将以下代码写入相应的文件中
这两个文件 App.js 和 data.js 将位于 src 文件夹中
// 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;
// FileName: data.js
import { v4 as uuidv4 } from "uuid";
function chillHop() {
return [
{
name: "Sunrise Serenade",
cover:
"https://media.geeksforgeeks.org/wp-content/uploads/20210224040124/JSBinCollaborativeJavaScriptDebugging6.png",
artist: " Harmony Harp",
audio:
"https://media.geeksforgeeks.org/wp-content/uploads/20231004185212/Jawan-Prevue-Theme.mp3",
color: ["#205950", "#2ab3bf"],
id: uuidv4(),
active: true,
},
{
name: "Urban Groove",
cover:
"https://media.geeksforgeeks.org/wp-content/uploads/20231004210806/DemotivationalPosterfull936506.jpg",
artist: "Beatmaster B",
audio:
"https://media.geeksforgeeks.org/wp-content/uploads/20231004184006/SoundHelix-Song-10.mp3",
color: ["#EF8EA9", "#ab417f"],
id: uuidv4(),
active: false,
},
{
name: "Mystic Echo",
cover:
"https://media.geeksforgeeks.org/wp-content/uploads/20231004210619/3408428b23c516b1687c748cb7de7be7.webp",
artist: " Harmony Harp",
audio:
"https://media.geeksforgeeks.org/wp-content/uploads/20231004185212/Jawan-Prevue-Theme.mp3",
color: ["#CD607D", "#c94043"],
id: uuidv4(),
active: false,
},
{
name: "Electro Vibes",
cover:
"https://media.geeksforgeeks.org/wp-content/uploads/20231004184219/gfglogo0.jpg",
artist: "Synthwave Sensation",
audio:
"https://media.geeksforgeeks.org/wp-content/uploads/20231004191840/Zinda-Banda---Jawan-(1).mp3",
color: ["#EF8EA9", "#ab417f"],
id: uuidv4(),
active: false,
},
{
name: "Jazzy Whispers",
cover:
"https://media.geeksforgeeks.org/wp-content/uploads/20231004210806/DemotivationalPosterfull936506.jpg",
artist: "Smooth Sax Serenade",
audio:
"https://media.geeksforgeeks.org/wp-content/uploads/20231004184006/SoundHelix-Song-10.mp3",
color: ["#CD607D", "#c94043"],
id: uuidv4(),
active: false,
},
{
name: "Tropical Breez",
cover:
"https://media.geeksforgeeks.org/wp-content/uploads/20231004210619/3408428b23c516b1687c748cb7de7be7.webp",
artist: "Island Rhythms",
audio:
"https://media.geeksforgeeks.org/wp-content/uploads/20231004191840/Zinda-Banda---Jawan-(1).mp3",
color: ["#205950", "#2ab3bf"],
id: uuidv4(),
active: false,
},
];
}
export default chillHop;
这五个文件 Library.js , LibrarySong.js , PlayerSong.js , Navb.js 和 Song.js 将位于 src 文件夹下的 components 文件夹中。
// 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;
// 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;
// FileName: Navb.js
import React from "react";
const Nav = ({ setLibraryStatus, libraryStatus }) => {
return (
<nav>
<h1>GeeksforGeeks Music Player</h1>
<button
onClick={() => {
setLibraryStatus(!libraryStatus);
}}
>
<h4>Library</h4>
</button>
</nav>
);
};
export default Nav;
// 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;
// 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;
这五个文件 library.scss , app.scss , nav.scss , player.scss 和 song.scss 将位于 src 文件夹下的 styles 文件夹中。
/* 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(
180deg,
rgba(231, 235, 214, 1) 0%,
rgba(55, 102, 44, 1) 100%
);
}
@import "./library";
@import "./player";
@import "./song";
@import "./nav";
h2,
h3 {
color: #133a1b;
}
h3,
h4 {
font-weight: 600;
}
button {
font-weight: 700;
}
CSS
/* FileName: library.scss */
.library {
position: fixed;
top: 0;
left: 0;
width: 20rem;
height: 100%;
background: #32522b;
box-shadow: 2px 2px 50px rgba(0, 0, 0, 0.205);
overflow: scroll;
transform: translateX(-100%);
transition: all 0.2s ease;
opacity: 0;
h2 {
padding: 2rem;
}
}
.library-song {
display: flex;
align-items: center;
padding: 1rem 2rem 1rem 2rem;
img {
width: 30%;
}
&:hover {
background: rgb(120, 248, 160);
}
}
.song-description {
padding-left: 1rem;
h3 {
color: #ffffff;
font-size: 1rem;
}
h4 {
color: gray;
font-size: 0.7rem;
}
}
::-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);
h3 {
color: #306b26;
}
}
.active {
transform: translateX(0%);
opacity: 1;
}
@media screen and (max-width: 768px) {
.library {
width: 100%;
}
}
CSS
/*FileName: nav.scss */
h1,
h4 {
color: rgb(9, 70, 9);
}
nav {
min-height: 10vh;
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.8rem;
transition: all 0.3s 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: 20vh;
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: 1rem;
font-weight: 700;
}
}
.play-control {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
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: 1rem;
position: relative;
border-radius: 1rem;
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: 60vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
width: 20%;
//
}
h2 {
padding: 3rem 1rem 1rem 1rem;
}
h3 {
font-size: 1rem;
}
}
@media screen and (max-width: 768px) {
.song-container {
img {
width: 60%;
}
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.rotateSong {
animation: rotate 20s linear forwards infinite;
}
运行项目的步骤:
步骤1: 在终端中输入以下命令 。
npm start
步骤2: 打开网络浏览器并输入以下URL
http://localhost:3000/
输出: