Реализация аудиоплеера на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация аудиоплеера на сайте
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация аудиоплеера на сайте

Нативный <audio> элемент везде работает по-разному, не поддаётся стилизации и не даёт никаких хуков для аналитики или расширенного UX. Кастомный плеер строится поверх Web Audio API или готовых библиотек.

Выбор подхода

Три уровня сложности:

Уровень 1 — styled audio — прячем нативный элемент и рисуем кастомные контролы поверх него. Минимум кода, теряем прогресс-бар с волной.

Уровень 2 — библиотека — Wavesurfer.js, Howler.js, Plyr. Wavesurfer рисует waveform, Howler даёт низкоуровневый контроль с поддержкой спрайтов, Plyr — красивый UI для простых случаев.

Уровень 3 — Web Audio API напрямую — полный контроль: эквалайзер, анализатор частот, эффекты. Используется для музыкальных сервисов, подкаст-платформ.

Wavesurfer.js: плеер с визуализацией волны

npm install wavesurfer.js
import WaveSurfer from 'wavesurfer.js'
import { useEffect, useRef, useState } from 'react'

interface AudioPlayerProps {
  url: string
  title: string
  waveColor?: string
  progressColor?: string
}

export function WaveAudioPlayer({
  url, title,
  waveColor = '#94a3b8',
  progressColor = '#6366f1',
}: AudioPlayerProps) {
  const containerRef = useRef<HTMLDivElement>(null)
  const wsRef = useRef<WaveSurfer>()
  const [playing, setPlaying] = useState(false)
  const [currentTime, setCurrentTime] = useState(0)
  const [duration, setDuration] = useState(0)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    if (!containerRef.current) return

    wsRef.current = WaveSurfer.create({
      container: containerRef.current,
      waveColor,
      progressColor,
      height: 64,
      barWidth: 2,
      barGap: 1,
      barRadius: 2,
      cursorWidth: 1,
      cursorColor: progressColor,
      normalize: true,
      backend: 'WebAudio',
      // Пики можно предгенерировать на сервере и передавать как массив
      // peaks: precomputedPeaks,
    })

    wsRef.current.load(url)

    wsRef.current.on('ready', () => {
      setDuration(wsRef.current!.getDuration())
      setLoading(false)
    })

    wsRef.current.on('audioprocess', () => {
      setCurrentTime(wsRef.current!.getCurrentTime())
    })

    wsRef.current.on('finish', () => setPlaying(false))

    wsRef.current.on('error', (err) => {
      console.error('WaveSurfer error:', err)
      setLoading(false)
    })

    return () => wsRef.current?.destroy()
  }, [url, waveColor, progressColor])

  const togglePlay = () => {
    wsRef.current?.playPause()
    setPlaying(p => !p)
  }

  const formatTime = (sec: number) => {
    const m = Math.floor(sec / 60)
    const s = Math.floor(sec % 60)
    return `${m}:${s.toString().padStart(2, '0')}`
  }

  return (
    <div className="audio-player">
      <div className="audio-player__meta">
        <span>{title}</span>
        <span>{formatTime(currentTime)} / {formatTime(duration)}</span>
      </div>
      {loading && <div className="audio-player__loading">Загрузка...</div>}
      <div ref={containerRef} className="audio-player__wave" />
      <button onClick={togglePlay} disabled={loading} aria-label={playing ? 'Пауза' : 'Играть'}>
        {playing ? '⏸' : '▶'}
      </button>
    </div>
  )
}

Предгенерация peaks на сервере

Загружать полный аудиофайл для отрисовки волны — дорого для пользователя. Peaks генерируем один раз при загрузке файла:

// Laravel: генерация peaks через audiowaveform (C++ утилита)
// Установка: apt-get install audiowaveform

use Illuminate\Support\Facades\Process;

class AudioService
{
    public function generatePeaks(string $audioPath): array
    {
        $jsonPath = sys_get_temp_dir() . '/' . uniqid() . '.json';

        $result = Process::run([
            'audiowaveform',
            '-i', $audioPath,
            '-o', $jsonPath,
            '--bits', '8',
            '--pixels-per-second', '20',
        ]);

        if ($result->failed()) {
            throw new \RuntimeException('audiowaveform failed: ' . $result->errorOutput());
        }

        $data = json_decode(file_get_contents($jsonPath), true);
        unlink($jsonPath);

        return $data['data'] ?? [];
    }
}
// Передаём peaks через props — нет загрузки аудио для отрисовки
wsRef.current = WaveSurfer.create({
  container: containerRef.current,
  peaks: track.peaks,  // массив из БД
  duration: track.duration,
  // Аудиофайл загружается только при нажатии Play
})
wsRef.current.on('interaction', () => {
  wsRef.current!.load(url)
})

Howler.js: спрайты и управление несколькими треками

npm install howler @types/howler
import { Howl, Howler } from 'howler'

// Глобальный контроль громкости
Howler.volume(0.8)

// Аудиоспрайт — один файл, несколько звуков (для игр, UI-звуков)
const sprite = new Howl({
  src: ['/sounds/ui-sounds.webm', '/sounds/ui-sounds.mp3'],
  sprite: {
    click: [0, 150],
    success: [300, 800],
    error: [1200, 500],
    notification: [1800, 1200],
  },
})

sprite.play('success')

// Плеер с очередью треков
class AudioQueue {
  private queue: string[] = []
  private current: Howl | null = null
  private currentIndex = 0

  constructor(tracks: string[]) {
    this.queue = tracks
  }

  play(index = this.currentIndex) {
    this.current?.stop()
    this.currentIndex = index

    this.current = new Howl({
      src: [this.queue[index]],
      html5: true,  // стриминг для больших файлов
      onend: () => this.next(),
      onloaderror: (id, err) => console.error('Load error:', err),
    })

    this.current.play()
  }

  next() {
    if (this.currentIndex < this.queue.length - 1) {
      this.play(this.currentIndex + 1)
    }
  }

  prev() {
    if (this.currentIndex > 0) {
      this.play(this.currentIndex - 1)
    }
  }
}

Визуализатор частот через Web Audio API

function createFrequencyVisualizer(audioElement: HTMLAudioElement, canvas: HTMLCanvasElement) {
  const ctx = new AudioContext()
  const source = ctx.createMediaElementSource(audioElement)
  const analyser = ctx.createAnalyser()

  analyser.fftSize = 256
  source.connect(analyser)
  analyser.connect(ctx.destination)

  const bufferLength = analyser.frequencyBinCount  // 128
  const dataArray = new Uint8Array(bufferLength)
  const canvasCtx = canvas.getContext('2d')!

  function draw() {
    requestAnimationFrame(draw)
    analyser.getByteFrequencyData(dataArray)

    canvasCtx.fillStyle = '#0f172a'
    canvasCtx.fillRect(0, 0, canvas.width, canvas.height)

    const barWidth = canvas.width / bufferLength * 2.5
    let x = 0

    for (let i = 0; i < bufferLength; i++) {
      const barHeight = (dataArray[i] / 255) * canvas.height
      const hue = (i / bufferLength) * 240 + 180
      canvasCtx.fillStyle = `hsl(${hue}, 70%, 60%)`
      canvasCtx.fillRect(x, canvas.height - barHeight, barWidth, barHeight)
      x += barWidth + 1
    }
  }

  draw()
}

Keyboard shortcuts и accessibility

function setupKeyboardControls(player: WaveSurfer) {
  document.addEventListener('keydown', (e) => {
    // Только если фокус не в input/textarea
    if (['INPUT', 'TEXTAREA'].includes((e.target as Element).tagName)) return

    switch (e.code) {
      case 'Space':
        e.preventDefault()
        player.playPause()
        break
      case 'ArrowLeft':
        player.skip(-5)
        break
      case 'ArrowRight':
        player.skip(5)
        break
      case 'ArrowUp':
        player.setVolume(Math.min(1, player.getVolume() + 0.1))
        break
      case 'ArrowDown':
        player.setVolume(Math.max(0, player.getVolume() - 0.1))
        break
    }
  })
}

MediaSession API: интеграция с OS и Bluetooth

function setupMediaSession(track: { title: string; artist: string; artwork: string }) {
  if (!('mediaSession' in navigator)) return

  navigator.mediaSession.metadata = new MediaMetadata({
    title: track.title,
    artist: track.artist,
    artwork: [
      { src: track.artwork, sizes: '512x512', type: 'image/webp' },
    ],
  })

  navigator.mediaSession.setActionHandler('play', () => wavesurfer.play())
  navigator.mediaSession.setActionHandler('pause', () => wavesurfer.pause())
  navigator.mediaSession.setActionHandler('previoustrack', () => queue.prev())
  navigator.mediaSession.setActionHandler('nexttrack', () => queue.next())
  navigator.mediaSession.setActionHandler('seekto', (details) => {
    if (details.seekTime !== undefined) {
      wavesurfer.seekTo(details.seekTime / wavesurfer.getDuration())
    }
  })
}

После этого треки управляются кнопками на наушниках, в lock screen iOS/Android, в системном медиаплеере Windows.

Сроки

Простой плеер на Plyr для одного трека — полдня. Wavesurfer с визуализацией, предгенерацией peaks и MediaSession API — 2–3 дня. Полноценный музыкальный плеер с очередью, плейлистами и визуализатором — 1–1.5 недели.