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

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, 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',
    })

    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()
}

Гарячі клавіши та доступність

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 тижня.