Создание клона Spotify с помощью React и ts-audio

Автор: Фортуна Икечи✏️

Музыкальные плееры – это устройства или приложения, которые позволяют прослушивать аудиофайлы и записи. Существует множество музыкальных плееров, но в этой статье мы создадим клон популярного сервиса потокового воспроизведения музыки Spotify с помощью React и ts-audio.

Можно было бы ожидать, что в этом руководстве будет использоваться API Spotify, однако Spotify и другие музыкальные базы данных не предоставляют в теле ответа ссылку или URL для потокового воспроизведения. API Spotify предоставляет URL для предварительного просмотра, но продолжительность песен ограничена всего 30 секундами, а этого недостаточно для нашего примера. Поэтому мы не будем использовать API Spotify или делать какие-либо запросы к каким-либо музыкальным API или базам данных.

Вместо этого мы будем работать с фиктивными данными, состоящими из песен и изображений. Однако если вам попадется API с потоковой ссылкой, вы также можете применить методы, использованные в этой статье. Полный код этого руководства вы можете найти в репозитории GitHub. Давайте приступим!

  • Что такое ts-audio?
  • Создание клона Spotify с помощью ts-audio
    • Использование компонента Audio
    • Использование компонента AudioPlaylist
    • Создание функциональных возможностей
  • Решение проблем: Несоответствие деталей песни
  • Добавление стиля

Что такое ts-audio?

ts-audio – это агностическая библиотека, которая упрощает взаимодействие с API AudioContext. ts-audio предоставляет вам такие методы, как воспроизведение, пауза и другие, а также позволяет создавать списки воспроизведения. ts-audio предлагает следующие возможности:

  • Включает простой API, абстрагирующий сложность API AudioContext.
  • Предлагает кросс-браузерную поддержку
  • упрощает создание аудио плейлиста
  • Работает с любым языком, который компилируется в JavaScript

Создание клона Spotify с помощью ts-audio

Давайте начнем с создания нового приложения React с помощью команды ниже:

npx create-react-app ts-audio
Войти в полноэкранный режим Выйти из полноэкранного режима

Если вы используете Yarn, выполните следующую команду:

yarn create react-app ts-audio
Войти в полноэкранный режим Выйти из полноэкранного режима

В остальной части руководства я буду использовать Yarn. Далее мы установим пакет ts-audio следующим образом:

yarn add ts-audio
Войти в полноэкранный режим Выйти из полноэкранного режима

По своей сути ts-audio состоит из двух компонентов, Audio и AudioPlaylist. Компоненты представляют собой функции, которые мы можем вызывать с определенными параметрами.

Использование компонента Audio

Компонент Audio позволяет нам передать одну песню для воспроизведения. Он также предоставляет нам определенные методы, такие как play(), pause(), stop() и другие:

// App.js

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

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

  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>
    </>
  )
}
Вход в полноэкранный режим Выход из полноэкранного режима

В приведенном выше блоке кода мы импортировали компонент Audio из ts-audio и песню, которую хотим воспроизвести. Мы создали экземпляр аудио, установили его на импортированный компонент Audio, а затем передали импортированную музыку в параметр file, открываемый элементом Audio. Мы воспользовались методами, предоставленными нам ts-audio, такими как play() и pause(), затем передали их через функции кнопкам.

Использование компонента AudioPlaylist

Компонент AudioPlaylist позволяет нам передавать несколько песен, но они должны быть в массиве, иначе ts-audio не будет их воспроизводить. Компонент AudioPlaylist предоставляет нам такие методы, как play(), pause(), stop(), next(), и prev().

Приведенный ниже блок кода является примером использования компонента AudioPlaylist:

// App.js

import { AudioPlaylist } from 'ts-audio';
import Lazarus from './music/Lazarus.mp3';
import Sia from './music/Sia - Bird Set Free.mp3';

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

  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>
    </>
  )
}
Вход в полноэкранный режим Выход из полноэкранного режима

Музыкальный проигрыватель будет обладать следующими функциональными возможностями:

  • Смена исполнителя на исполнителя текущей песни при нажатии на next или previous
  • Изменение изображения на изображение текущей песни
  • Изменение названия песни на название текущей песни.

В папке src создайте две папки images и music соответственно. Перейдите в папку images и вставьте туда все фотографии, которые вам могут понадобиться. В папку music можно вставить любые аудиофайлы, которые вы хотите использовать.

В следующих репозиториях GitHub вы можете получить файлы изображений, используемые в этом руководстве, и получить аудиофайлы. Далее импортируйте песни и изображения в App.js следующим образом:

import { AudioPlaylist } from 'ts-audio';

// Music import
import Eyes from './music/01. Jon Bellion - Eyes To The Sky.mp3';
import Mood from './music/24kGoldn-Mood-Official-Audio-ft.-Iann-Dior.mp3';
import Audio from './music/audio.mp3';
import Broken from './music/Cant Be Broken .mp3';
import Lazarus from './music/Lazarus.mp3';
import Sia from './music/Sia - Bird Set Free.mp3';
import Nobody from './music/T-Classic-Nobody-Fine-Pass-You.mp3';
import Yosemite from './music/Yosemite.mp3';

// Pictures import
import EyesImg from './images/Eyes to the sky.jpeg';
import MoodImg from './images/mood.jpeg';
import AudioImg from './images/lana.jpeg';
import BrokenImg from './images/lil wayne.jpeg';
import LazarusImg from './images/dave.jpeg';
import SiaImg from './images/sia.jpeg';
import NobodyImg from './images/nobody.jpeg';
import YosemiteImg from './images/travis.jpeg';

export default function App() {
  const songs =  [
      {
        title: 'Eyes to the sky',
        artist: 'Jon Bellion',
        img_src: EyesImg,
        src: Eyes,
      },
      {
        title: 'Lazarus',
        artist: 'Dave',
        img_src: LazarusImg,
        src: Lazarus,
      },
      {
        title: 'Yosemite',
        artist: 'Travis scott',
        img_src: YosemiteImg,
        src: Yosemite,
      },
      {
        title: 'Bird set free',
        artist: 'Sia',
        img_src: SiaImg,
        src: Sia,
      },
      {
        title: 'Cant be broken',
        artist: 'Lil wayne',
        img_src: BrokenImg,
        src: Broken,
      },
      {
        title: 'Mood',
        artist: '24kGoldn',
        img_src: MoodImg,
        src: Mood,
      },
      {
        title: 'Nobody fine pass you',
        artist: 'T-Classic',
        img_src: NobodyImg,
        src: Nobody,
      },
      {
        title: 'Dark paradise',
        artist: 'Lana Del Ray',
        img_src: AudioImg,
        src: Audio,
      },
    ]

  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>     
    </>
  );
}
Войдите в полноэкранный режим Выйти из полноэкранного режима

В приведенном выше блоке кода мы импортировали песни и изображения. Затем мы создали массив песен, содержащий объекты. Каждый объект имеет title, artist, img_src для импортированных изображений и src для импортированных песен.

После этого мы отобразили массив песен, чтобы добраться до src, который мы передали в параметре files. Помните, что мы должны передать его как массив, но затем метод map() создает новый массив из вызова функции. Поэтому мы можем передать его в параметр files.

Мы также создали наши методы и передали их различным кнопкам. Мы создадим файл 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>
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

В приведенном выше блоке кода мы создали файл Player.js, затем поймали реквизиты, поступающие из App.js, и, наконец, передали их в кнопки.

Создание функциональных возможностей

Чтобы создать функциональные возможности для нашего приложения, мы импортируем 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>
    </>
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Мы создали событие состояния и установили его в ноль. Когда мы нажимаем кнопку next, мы устанавливаем состояние в сумму остатка текущего состояния, единицы, и длины песни, деленной на длину песни:

currentSong + 1 + songs.length) % songs.length
Вход в полноэкранный режим Выход из полноэкранного режима

Когда мы нажимаем на предыдущую кнопку, мы устанавливаем состояние как остаток текущей песни, минус один, плюс длина песни, деленная на длину песни:

currentSong - 1 + songs.length) % songs.length
Вход в полноэкранный режим Выход из полноэкранного режима

Мы также создали событие состояния, которое проверяет, играет песня или нет, а затем передаем его в качестве реквизита компоненту Player. Наконец, мы создали функциональные возможности для изменения изображения, исполнителей и названия песни.

Когда мы запускаем приложение, кажется, что все работает; изображения меняются при нажатии на следующую кнопку. Однако воспроизводимые песни не соответствуют изображениям и названиям исполнителей, отображаемым на экране. Иногда две или более песен воспроизводятся одновременно.

Решение проблемы: Несоответствие сведений о песнях

Когда мы нажимаем на кнопки next или previous, мы пересчитываем значения и фактически вызываем повторный рендеринг. Чтобы остановить это, мы обернем массив песен и созданный экземпляр списка воспроизведения в хук useMemo, как показано ниже:

// 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: 'Eyes to the sky',
        artist: 'Jon Bellion',
        img_src: EyesImg,
        src: Eyes,
      },
      {
        title: 'Lazarus',
        artist: 'Dave',
        img_src: LazarusImg,
        src: Lazarus,
      },
      {
        title: 'Yosemite',
        artist: 'Travis scott',
        img_src: YosemiteImg,
        src: Yosemite,
      },
      {
        title: 'Bird set free',
        artist: 'Sia',
        img_src: SiaImg,
        src: Sia,
      },
      {
        title: 'Cant be broken',
        artist: 'Lil wayne',
        img_src: BrokenImg,
        src: Broken,
      },
      {
        title: 'Mood',
        artist: '24kGoldn',
        img_src: MoodImg,
        src: Mood,
      },
      {
        title: 'Nobody fine pass you',
        artist: 'T-Classic',
        img_src: NobodyImg,
        src: Nobody,
      },
      {
        title: 'Dark paradise',
        artist: 'Lana Del Ray',
        img_src: AudioImg,
        src: Audio,
      },
    ],
    []
  );

  const playlist = useMemo(() => {
    return AudioPlaylist({
      files: songs.map((song) => song.src),
    });
  }, [songs]);
Войти в полноэкранный режим Выйти из полноэкранного режима

Хук useMemo эффективно кэширует значение, чтобы его не нужно было пересчитывать, и, следовательно, не вызывать повторный рендеринг.

Добавление стиля

В этом уроке мы будем использовать иконки из Font Awesome Icons. Вы можете установить пакет Font Awesome с помощью команд, приведенных ниже:

yarn add @fortawesome/fontawesome-svg-core
yarn add @fortawesome/free-solid-svg-icons
yarn add @fortawesome/react-fontawesome
Войти в полноэкранный режим Выйти из полноэкранного режима

Скопируйте и вставьте приведенный ниже код в файл 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>
  );
}
Войти в полноэкранный режим Выйти из полноэкранного режима

В приведенном выше блоке кода мы получаем реквизиты из файла App.js, затем обрабатываем их внутри файла 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;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Заключение

В этой статье мы познакомились с ts-audio, агностической, простой в использовании библиотекой, которая работает с API AudioContext. Мы узнали о методах ts-audio и о том, как она облегчает работу с аудиофайлами. Наконец, мы узнали, как создать работающий музыкальный плеер с использованием ts-audio.


Полная видимость производственных приложений React

Отладка приложений React может быть сложной задачей, особенно когда пользователи сталкиваются с проблемами, которые трудно воспроизвести. Если вы заинтересованы в мониторинге и отслеживании состояния Redux, автоматическом обнаружении ошибок JavaScript, отслеживании медленных сетевых запросов и времени загрузки компонентов, попробуйте LogRocket.

LogRocket – это как видеорегистратор для веб- и мобильных приложений, записывающий буквально все, что происходит в вашем React-приложении. Вместо того чтобы гадать, почему возникают проблемы, вы можете собрать данные и отчитаться о том, в каком состоянии находилось ваше приложение в момент возникновения проблемы. LogRocket также отслеживает производительность вашего приложения, предоставляя такие показатели, как загрузка процессора клиента, использование памяти клиента и многое другое.

Пакет промежуточного ПО LogRocket Redux добавляет дополнительный уровень видимости пользовательских сессий. LogRocket регистрирует все действия и состояние ваших хранилищ Redux.

Модернизируйте отладку приложений React – начните мониторинг бесплатно.

Оцените статью
Procodings.ru
Добавить комментарий