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


    ⭐️ 本文首发自 前端修罗场(点击加入),是一个由 资深开发者 独立运行 的专业技术社区,我专注 Web 技术、Web3、区块链、答疑解惑、面试辅导以及职业发展现在加入,私聊我即可获取一次免费的模拟面试机会,帮你评估知识点的掌握程度,获得更全面的学习指导意见,交个朋友,少走弯路,少吃亏!

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

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

    什么是 ts-audio?

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

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

    开始使用 ts-audio 构建

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

    npx create-react-app ts-audio
    // or
    yarn create react-app ts-audio
    
    • 1
    • 2
    • 3

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

    yarn add ts-audio
    
    • 1

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

    使用 Audio 组件

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

    // 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>
        </>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

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

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

    使用 AudioPlaylist 组件

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

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

    // 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>
        </>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

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

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

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

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

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

    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>     
        </>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

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

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

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

    // 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>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    创建功能

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

    // 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>
        </>
      );
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

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

    currentSong + 1 + songs.length) % songs.length
    
    • 1

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

    currentSong - 1 + songs.length) % songs.length
    
    • 1

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

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

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

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

    下面我们来解决问题。

    解决问题

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

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

    // 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]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

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

    添加样式

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

    yarn add @fortawesome/fontawesome-svg-core
    yarn add @fortawesome/free-solid-svg-icons
    yarn add @fortawesome/react-fontawesome
    
    • 1
    • 2
    • 3

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

    // 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>
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

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

    * {
      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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

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

    结尾

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

  • 相关阅读:
    离散数学之 一阶逻辑等值演算与推理
    Springboot启动流程(源码解析)、自动装配流程(源码解析)、总结、SrpringBoot初始化数据扩展
    Python手册
    MQTT,JSON,VSCODE(C语言编程环境)心得
    DBeaver连接数据库报错:Public Key Retrieval is not allowed 的解决方案
    书店管理系统
    进程管理(五)
    room数据库的使用
    【精讲】Es6的数组的扩展方法,对象的扩展方法,字符串扩展方法,?. 对象层级深
    iOS BUG UIView转UIImage模糊失真
  • 原文地址:https://blog.csdn.net/ImagineCode/article/details/126086827