和我一起写一个音乐播放器,听一首最伟大的作品

2023-10-07 19:47:57 浏览数 (2)

网上都在说仿一个网易云什么的,能不能高级一点点(因为听不了JAY 的歌啊)!!!在本文中,我们将使用 React 和 ts-audio 仿造流行音乐流媒体服务 Spotify 构建一个类似的音乐播放器。让你构建一个音乐播放器简单又快速!

因为 Spotify 未提供公共的音乐资源 API,所以我们将会使用一组虚拟数据。 话不多说,我们开始~

什么是 ts-audio?

ts-audio 是一个能够使 AudioContext API 更易于交互的第三方库。 它能为开发者提供播放、暂停等方法,并允许你创建播放列表。 总的来说,ts-audio 提供以下功能与特征:

  • 一个简单的 API,它抽象了 AudioContext API 的复杂性
  • 提供跨浏览器支持
  • 轻松创建音频播放列表
  • 适用于任何能够编译成 JavaScript 的语言

开始使用 ts-audio 构建

让我们首先使用以下命令创建一个新的 React 应用程序:

代码语言:javascript复制
npx create-react-app ts-audio
// or
yarn create react-app ts-audio

接下来,我们安装 ts-audio 包:

代码语言:javascript复制
yarn add ts-audio

ts-audio 有两个核心组件,AudioAudioPlaylist

使用 Audio 组件

Audio 组件允许我们传入要播放的一首歌曲。 它还为我们提供了某些方法,例如 play()、pause()、stop() 等等。打开 App.js 填入如下内容:

代码语言:javascript复制
// App.js

import Audio from 'ts-audio';
import GreatestWorkOfArt from './music/GreatestWorkOfArt.mp3';

export default function App() {
  const audio = Audio({
    file: GreatestWorkOfArt
  })

  const play = () => {
    audio.play()
  }

    const pause = () => {
    audio.pause()
  }

    const stop = () => {
    audio.stop()
  }

  return (
    <>
      <button onClick={play}>Play</button>
      <button onClick={pause}>Pause</button>
      <button onClick={stop}>Stop</button>
    </>
  )
}

在上面的代码块中,我们从 ts-audio 导入了 Audio 组件和我们想要播放的歌曲——最伟大的作品(此处必须拥有名字)。 接着,我们创建了一个音频实例 audio,将其设置为导入的 Audio 组件,然后将导入的音乐传递给 Audio 元素暴露出来的 file 参数。

后面的代码中,我们利用了 ts-audio 提供给的方法,比如 play() 和 pause(),通过按钮上绑定的点击事件函数调用它们。

使用 AudioPlaylist 组件

AudioPlaylist 组件允许我们传入多首歌曲,但它们必须在一个数组中,否则 ts-audio 不会播放它们。 AudioPlaylist 组件为我们提供了 play()、pause()、stop()、next() 和 prev() 等方法。

下面的代码块解释了如何使用 AudioPlaylist 组件:

代码语言:javascript复制
// App.js

import { AudioPlaylist } from 'ts-audio';
import GreatestWorkOfArt from './music/GreatestWorkOfArt.mp3';
import Mojito from './music/Mojito.mp3';

export default function App() {
  const playlist = AudioPlaylist({
    files: [GreatestWorkOfArt, Mojito]
  })

  const play = () => {
    playlist.play()
  }

  const pause = () => {
    playlist.pause()
  }

  const next = () => {
    playlist.next()
  }

  const previous = () => {
    playlist.prev()
  }

  const stop = () => {
    playlist.stop()
  }

  return (
    <>
      <button onClick={play}>Play</button>
      <button onClick={pause}>Pause</button>
      <button onClick={next}>Next</button>
      <button onClick={prev}>Prev</button>
      <button onClick={stop}>Stop</button>
    </>
  )
}

除了播放多首歌曲之前,我们的音乐播放器还应该具备以下功能:

  • 每当我们单击下一个或上一个时,会将艺术家更改为当前歌曲的艺术家
  • 将图像更改为当前歌曲的图像
  • 将歌曲名称更改为当前歌曲

接下来,让我们来实现上述功能。

src 文件夹中,分别创建两个名为 imagesmusic 的文件夹。 导航到 images 文件夹并粘贴你可能需要的任何照片。 同样,在 music 文件夹中,你可以粘贴要使用的任何音频文件。

接下来,将歌曲和图片导入到 App.js 中,如下所示:

代码语言:javascript复制
import { AudioPlaylist } from 'ts-audio';

// Music import
import Intro from './music/Intro.mp3';
import GreatestWorkOfArt from './music/GreatestWorkOfArt.mp3';
import Mojito from './music/Mojito.mp3';
import StillWandering from './music/StillWandering.mp3';
import DontCry from './music/Say goodbye not to cry.mp3';
import PinkOcean from './music/Pink ocean.mp3';
import Reflection from './music/Reflection.mp3';

// Pictures import
import picture1 from './images/picture1.jpeg';
import picture2 from './images/picture2.jpeg';
import picture3 from './images/picture3.jpeg';
import picture4 from './images/picture4.jpeg';
import picture5 from './images/picture5.jpeg';
import picture6 from './images/picture6.jpeg';
import picture7 from './images/picture7.jpeg';


export default function App() {
  const songs =  [
      {
        title: 'Intro,
        artist: 'Jay',
        img_src: picture1,
        src: Intro,
      },
      {
        title: '最伟大的作品',
        artist: 'Jay',
        img_src: picture2,
        src: GreatestWorkOfArt,
      },
      {
        title: 'Mojito',
        artist: 'Jay',
        img_src: picture3,
        src: Mojito,
      },
      {
        title: '还在流浪',
        artist: 'Jay',
        img_src: picture4,
        src: StillWandering,
      },
      {
        title: '说好不哭',
        artist: 'Jay',
        img_src: picture5,
        src: DontCry,
      },
      {
        title: '粉色海洋',
        artist: 'Jay',
        img_src: picture6,
        src: PinkOcean,
      },
      {
        title: '倒影',
        artist: 'Jay',
        img_src: picture7,
        src: Reflection,
      },
    ]

  const playlist = AudioPlaylist({
      files: songs.map((song) => song.src),
    });

  const handlePlay = () => {
    playlist.play();
  };

  const handlePause = () => {
    playlist.pause();
  };

  const handleSkip = () => {
    playlist.next();
  };

  const handlePrevious = () => {
    playlist.prev();
  };

  return (
    <>
      <button onClick={handlePlay}>Play</button>
      <button onClick={handlePause}>Pause</button>
      <button onClick={handleSkip}>Next</button>
      <button onClick={handlePrevious}>Prev</button>     
    </>
  );
}

在上面的代码块中,我们导入了歌曲和图像。 接下来,我们创建了一个包含对象的歌曲数组。 每个对象都有一个标题、艺术家、导入图像的 img_src 和导入歌曲的 src

之后,我们通过歌曲数组映射到歌曲的 src,我们将其传递给 files 参数。 请记住,我们必须将它作为一个数组传入,然后 map() 方法通过调用一个函数来生成一个新的数组。

我们还创建了我们的方法并将它们传递给各种按钮。 我们将创建一个 Player.js 文件来处理按钮的逻辑,用于处理 App.js 中的功能:

代码语言:javascript复制
// Player.js
export default function Player({ play, pause, next, prev }) {
  return (
    <div className="c-player--controls">
      <button onClick={play}>Play</button>
      <button onClick={pause}>Pause</button>
      <button onClick={next}>Next</button>
      <button onClick={prev}>Previous</button> 
    </div>
  );
}

在上面的代码块中,我们创建了一个 Player.js 文件,然后捕获来自 App.js 的 props,最后将它们传递给按钮。

创建功能

为了为我们的应用程序创建功能,我们导入 useState 来获取歌曲的当前索引。 然后我们将图像设置为当前照片,将艺术家设置为当前艺术家,将标题设置为当前标题:

代码语言:javascript复制
// App.js

import React, { useState } from 'react';
import Player from './Player';
import { AudioPlaylist } from 'ts-audio';
// Music import

// Pictures import

export default function App() {
  const [currentSong, setCurrentSong] = useState(0);
  const [isPlaying, setIsPlaying] = useState(false);


  // Songs Array

  const playlist =AudioPlaylist({
      files: songs.map((song) => song.src),
    });

  const handlePlay = () => {
    playlist.play();
    setIsPlaying(true);
  };

  const handlePause = () => {
    playlist.pause();
    setIsPlaying(false);
  };

  const handleSkip = () => {
    playlist.next();
    setIsPlaying(true);
    setCurrentSong(
      (currentSong) => (currentSong   1   songs.length) % songs.length
    );
  };

  const handlePrevious = () => {
    playlist.prev();
    setIsPlaying(true);
    setCurrentSong(
      (currentSong) => (currentSong - 1   songs.length) % songs.length
    );
  };
  return (
    <>
      <div className="App">
        <div className="c-player">
          <div className="c-player--details">
            {' '}
            <div className="details-img">
              {' '}
              <img src={songs[currentSong].img_src} alt="img" />
            </div>
            <h1 className="details-title">{songs[currentSong].title}</h1>
            <h2 className="details-artist">{songs[currentSong].artist}</h2>
          </div>
          <Player
            play={handlePlay}
            pause={handlePause}
            isPlaying={isPlaying}
            setIsPlaying={setIsPlaying}
            next={handleSkip}
            prev={handlePrevious}
          />
        </div>
      </div>
    </>
  );
}

我们创建了一个 currentSong 状态并将其初始值设置为 0。 当我们单击下一个按钮时,我们将按照如下公式设置 currentSong 状态的值:

代码语言:javascript复制
currentSong   1   songs.length) % songs.length

当我们单击上一个按钮时,我们将按照如下公式设置 currentSong 状态的值:

代码语言:javascript复制
currentSong - 1   songs.length) % songs.length

我们还创建了一个 isPlaying 状态来检查歌曲是否正在播放,然后我们将它作为 props 传递给 Player 组件。

最后,我们处理了更改图像、艺术家和歌曲标题的功能。

当我们启动应用程序时,似乎一切正常; 单击“下一步”按钮时图像会发生变化。

但是问题来了,播放的歌曲与屏幕上显示的图片和艺术家姓名不匹配。 有时,同时播放两首或多首歌曲。

下面我们来解决问题。

解决问题

当我们单击下一个或上一个按钮时,我们正在重新计算值并导致重新渲染。

为了阻止这种情况,我们将歌曲数组和创建的播放列表实例包装在 useMemo Hook 中,如下所示:

代码语言:javascript复制
// App.js

import React, { useState, useMemo } from 'react';
import Player from './Player';
import { AudioPlaylist } from 'ts-audio';
// Music import

// Pictures import

export default function App() {
  const [currentSong, setCurrentSong] = useState(0);

  const songs = useMemo(
    () => [
      {
        title: 'Intro,
        artist: 'Jay',
        img_src: picture1,
        src: Intro,
      },
      {
        title: '最伟大的作品',
        artist: 'Jay',
        img_src: picture2,
        src: GreatestWorkOfArt,
      },
      {
        title: 'Mojito',
        artist: 'Jay',
        img_src: picture3,
        src: Mojito,
      },
      {
        title: '还在流浪',
        artist: 'Jay',
        img_src: picture4,
        src: StillWandering,
      },
      {
        title: '说好不哭',
        artist: 'Jay',
        img_src: picture5,
        src: DontCry,
      },
      {
        title: '粉色海洋',
        artist: 'Jay',
        img_src: picture6,
        src: PinkOcean,
      },
      {
        title: '倒影',
        artist: 'Jay',
        img_src: picture7,
        src: Reflection,
      },
    ],
    []
  );

  const playlist = useMemo(() => {
    return AudioPlaylist({
      files: songs.map((song) => song.src),
    });
  }, [songs]);

useMemo Hook 能有效地缓存该值,因此它不需要重新计算,因此不会导致重新渲染。

添加样式

在本文中,我们会使用 Font Awesome Icons 中提供的图标来美化我们的 UI。 你可以使用以下命令安装 Font Awesome 包:

代码语言:javascript复制
yarn add @fortawesome/fontawesome-svg-core
yarn add @fortawesome/free-solid-svg-icons
yarn add @fortawesome/react-fontawesome

将以下代码复制并粘贴到 Player.js 文件中:

代码语言:javascript复制
// Player.js

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlay, faPause, faForward, faBackward } from '@fortawesome/free-solid-svg-icons';
export default function Player({ play, pause, next, prev, isPlaying, setIsPlaying }) {
  return (
    <div className="c-player--controls">
      <button className="skip-btn" onClick={prev}>
        <FontAwesomeIcon icon={faBackward} />
      </button>
      <button
        className="play-btn"
        onClick={() => setIsPlaying(!isPlaying ? play : pause)}
      >
        <FontAwesomeIcon icon={isPlaying ? faPause : faPlay} />
      </button>
      <button className="skip-btn" onClick={next}>
        <FontAwesomeIcon icon={faForward} />
      </button>
    </div>
  );
}

在上面的代码块中,我们从 App.js 文件中获取 props,然后在 Player.js 文件中处理它们。 对于样式,请将以下代码复制并粘贴到你的 index.css 文件中:

代码语言:javascript复制
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Fira Sans', sans-serif;
}
body {
  background-color: #ddd;
}
.App {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  max-width: 100vw;
}
.c-player {
  display: block;
  background-color: #0a54aa;
  max-width: 400px;
  display: block;
  margin: 0px auto;
  padding: 50px;
  border-radius: 16px;
  box-shadow: inset -6px -6px 12px rgba(0, 0, 0, 0.8),
    inset 6px 6px 12px rgba(255, 255, 255, 0.4);
}
.c-player > h4 {
  color: #fff;
  font-size: 14px;
  text-transform: uppercase;
  font-weight: 500;
  text-align: center;
}
.c-player > p {
  color: #aaa;
  font-size: 14px;
  text-align: center;
  font-weight: 600;
}
.c-player > p span {
  font-weight: 400;
}
.c-player--details .details-img {
  position: relative;
  width: fit-content;
  margin: 0 auto;
}
.c-player--details .details-img img {
  display: block;
  margin: 50px auto;
  width: 100%;
  max-width: 250px;
  border-radius: 50%;
  box-shadow: 6px 6px 12px rgba(0, 0, 0, 0.8),
    -6px -6px 12px rgba(255, 255, 255, 0.4);
}
.c-player--details .details-img:after {
  content: '';
  display: block;
  position: absolute;
  top: -25px;
  left: -25px;
  right: -25px;
  bottom: -25px;
  border-radius: 50%;
  border: 3px dashed rgb(255, 0, 0);
}
.c-player--details .details-title {
  color: #eee;
  font-size: 28px;
  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8),
    -2px -2px 4px rgba(255, 255, 255, 0.4);
  text-align: center;
  margin-bottom: 10px;
}
.c-player--details .details-artist {
  color: #aaa;
  font-size: 20px;
  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8),
    -2px -2px 4px rgba(255, 255, 255, 0.4);
  text-align: center;
  margin-bottom: 20px;
}
.c-player--controls {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 30px;
}
.c-player--controls .play-btn {
  display: flex;
  margin: 0 30px;
  padding: 20px;
  border-radius: 50%;
  box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.8),
    -4px -4px 10px rgba(255, 255, 255, 0.4),
    inset -4px -4px 10px rgba(0, 0, 0, 0.4),
    inset 4px 4px 10px rgba(255, 255, 255, 0.4);
  border: none;
  outline: none;
  background-color: #ff0000;
  color: #fff;
  font-size: 24px;
  cursor: pointer;
}
.c-player--controls .skip-btn {
  background: none;
  border: none;
  outline: none;
  cursor: pointer;
  color: rgb(77, 148, 59);
  font-size: 18px;
}

现在,一个音乐播放器大功告成啦~快去试着添加你喜欢的歌曲试一试吧

结尾

在本文中,我们了解了 ts-audio 的方法以及它是如何让处理音频文件变得更容易。 最后,我们学习了如何使用 ts-audio 结合 React构建一个可用的音乐播放器。

0 人点赞