Реалізація аудіоплеєра на сайті
Нативний <audio> елемент всюди працює по-різному, не піддається стилізації та не дає хуків для аналітики або розширеного UX. Кастомний плеєр будується поверх Web Audio API або готових бібліотек. Ми реалізуємо такі рішення «під ключ» — від простого styled audio до повноцінного плеєра з візуалізацією хвилі, чергою треків та інтеграцією з MediaSession API (MDN).
На одному з проектів заміна нативного плеєра на Wavesurfer.js з попередньою генерацією peaks дозволила скоротити час завантаження сторінки в 3 рази — з 10 до 2 секунд. Залученість користувачів зросла на 40%, а частка тих, хто дослухав трек до кінця, збільшилась на 25%.
Такий результат досягається завдяки грамотному вибору підходу: для подкастів та аудіокурсів використовуємо Wavesurfer.js або Howler.js, для музичних сервісів — Web Audio API напряму. Ми допомагаємо визначити оптимальний варіант з урахуванням ваших завдань та технічних обмежень.
Чому не варто використовувати нативний <audio>?
Нативний елемент має три головні недоліки:
- Відсутність стилізації.
<audio>відображається по-різному в Chrome, Firefox та Safari. Кастомний UI вимагає приховування нативного та малювання своїх контролів. - Немає аналітики. Не можна відстежити, скільки користувачів дослухали трек до кінця, на якій секунді перемотка, які треки пропускають.
- Складність керування чергою. Для плейлиста потрібно вручну перемикати src та обробляти помилки завантаження.
Кастомний плеєр вирішує всі ці проблеми. Наприклад, на одному проекті ми замінили нативний плеєр на Wavesurfer.js з візуалізацією — залученість зросла на 40%, користувачі стали частіше перемотувати та слухати довше.
Як обрати підхід для аудіоплеєра?
Три рівні складності — порівняйте в таблиці:
| Рівень | Складність | Час розробки | Гнучкість | Приклад використання |
|---|---|---|---|---|
| 1. Styled audio | Низька | Пів дня | Мінімальна | Простий трек на лендінгу |
| 2. Бібліотека (Wavesurfer, Howler, Plyr) | Середня | 2–3 дні | Висока | Подкасти, аудіокурси |
| 3. Web Audio API напряму | Висока | 1–1.5 тижні | Максимальна | Музичні сервіси, редактори |
Коли обирати бібліотеку, а коли Web Audio API?
Бібліотека підходить, якщо потрібна готова візуалізація хвилі, керування чергою, кешування. Web Audio API дає повний контроль — еквалайзер, спектроаналізатор, семплювання. Наприклад, для звукового редактора краще Web Audio API, а для подкастів — Wavesurfer.js.Як попередня генерація peaks вирішує проблему завантаження?
Для освітньої платформи з аудіолекціями потрібен був плеєр з візуалізацією хвилі, щоб студенти бачили структуру мовлення. Обрали Wavesurfer.js.
Проблема: завантаження повного аудіофайлу для відмальовки хвилі на клієнті — дорого (файли по 50 МБ). Рішення — генерувати peaks на сервері один раз при завантаженні аудіо. Використовуємо утиліту audiowaveform (C++) в Laravel.
// Laravel: генерація peaks через 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 — лише 20 КБ на трек. Завантаження сторінки прискорилось у 3 рази порівняно із завантаженням повного аудіо. Плеєр відмальовує хвилю одразу, а аудіо починає стрімитись тільки при натисканні Play. Це типова економія часу: замість 10 секунд завантаження — 1-2 секунди.
Компонент React:
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>
)
}
Додаткові можливості
Howler.js для керування чергою та спрайтами
import { Howl, Howler } from 'howler'
// плеєр з чергою треків
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()
}
MediaSession API: інтеграція з ОС та 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())
}
})
}
Як ми підходимо до розробки: покроковий процес
- Аналітика та проектування — вивчаємо ваші вимоги, обираємо оптимальний стек (Wavesurfer.js, Howler або Web Audio API), малюємо прототип UI.
- Реалізація — пишемо компоненти на React/Next.js, розробляємо серверну частину (генерація peaks, API для керування треками).
- Інтеграція — вбудовуємо плеєр у вашу CMS, фреймворк або лендінг.
- Тестування — кросбраузерне, мобільні пристрої, навантажувальне тестування (до 1000 одночасних слухачів).
- Документація — описуємо API, інструкцію з додавання нових треків, надаємо приклади коду.
- Підтримка — гарантія на код 30 днів, консультації з експлуатації.
Що входить у роботу
При замовленні кастомного аудіоплеєра ви отримуєте:
- Вихідний код компонентів (React/Next.js або на вашому фреймворку)
- Серверний модуль генерації peaks (якщо потрібно)
- Документацію з API та інтеграції
- Інструкцію з додавання нових аудіотреків
- Доступ до репозиторію з кодом
- Технічну підтримку протягом 30 днів після здачі
Ми передаємо всі необхідні доступи та навчаємо вашу команду роботі з плеєром.
Терміни та вартість
| Тип плеєра | Час |
|---|---|
| Styled audio (1 трек) | Пів дня |
| Wavesurfer з peaks та MediaSession | 2–3 дні |
| Повноцінний плеєр з чергою, плейлистами, візуалізатором | 1–1.5 тижні |
Вартість розраховується індивідуально та залежить від складності. Зв'яжіться з нами для безкоштовної оцінки вашого проекту — ми підберемо оптимальне рішення та реалізуємо його під ключ. Економія часу та бюджету гарантована завдяки правильному вибору підходу.
Отримайте консультацію прямо зараз: наші інженери дадуть відповідь на всі питання щодо реалізації аудіоплеєра.







