
Аналіз аудіо потоків HLS за допомогою Web Audio API та hls.js
https://ift.tt/lGHFcbu
1. Помилка дублювання MediaElementAudioSourceNode
Найпоширеніша помилка виглядає так:
Cannot create multiple MediaElementAudioSourceNode from the same HTMLMediaElement
Це трапляється, бо браузери дозволяють створювати лише один MediaElementAudioSourceNode на кожен елемент
2. Обмеження політики автозапуску
Сучасні браузери блокують автоматичне відтворення аудіо до взаємодії з користувачем. AudioContext створюється у стані підвішеного (suspended); потрібно явно викликати resume() після жесту користувача (клацання, торкання).
3. Синхронізація завантаження маніфесту HLS
Маніфест HLS завантажується асинхронно через hls.js. Якщо створити MediaElementAudioSourceNode до повного завантаження маніфесту та його прикріплення до елемента video, ви отримаєте мовне аудіо або помилки ініціалізації.
Зображення:
The Solution: Singleton Pattern for AudioContext Management
Щоб гарантувати одну екземпляр AudioContext та коректну роботу MediaElementAudioSourceNode, ми використовуємо шаблон Singleton. Це забезпечує:
– одну інстанцію AudioContext на застосунок
– один MediaElementAudioSourceNode на кожен медіаважок
– централізоване управління станом аудіографу
– просте повторне використання в компонентах застосунку
Базова реалізація
export class HlsAudioService {
private static instance: HlsAudioService;
public audioContext: AudioContext;
public source?: MediaElementAudioSourceNode;
public splitter?: ChannelSplitterNode;
public analysers: AnalyserNode[] = [];
private constructor() {
const AudioContextClass = window.AudioContext || (window as any).webkitAudioContext;
this.audioContext = new AudioContextClass();
}
static getInstance(): HlsAudioService {
if (!HlsAudioService.instance) {
HlsAudioService.instance = new HlsAudioService();
}
return HlsAudioService.instance;
}
async resumeContext(): Promise
if (this.audioContext.state === ‘suspended’) {
await this.audioContext.resume();
}
}
}
Ми створюємо приватний конструктор, який ініціалізує AudioContext сумісним з браузерами способом (за потреби використовуючи префікс webkit для старих версій Safari). Статичний метод getInstance() гарантує наявність лише одного екземпляра протягом застосунку.
Інтеграція з hls.js
Ключовий момент — правильна послідовність ініціалізації. Необхідно дочекатись події MANIFEST_PARSED від hls.js перед створенням аудіо вузлів:
const videoElement = document.querySelector(‘video’) as HTMLVideoElement;
const hls = new Hls();
const audioService = HlsAudioService.getInstance();
hls.on(Hls.Events.MANIFEST_PARSED, () => {
// Маніфест завантажено, безпечно створювати аудіограф
if (!audioService.source) {
audioService.source = audioService.audioContext.createMediaElementSource(videoElement);
setupAudioGraph(audioService);
}
});
hls.loadSource(‘https://ift.tt/d5rFfAg’);
hls.attachMedia(videoElement);
// Обробка жесту користувача
videoElement.addEventListener(‘play’, async () => {
await audioService.resumeContext();
});
Цей підхід гарантує, що MediaElementAudioSourceNode створюється лише після повної ініціалізації потоку через hls.js, і лише один раз.
Побудова аудіографу для аналізу
Після створення вузла джерела побудуйте ланцюг обробки. Для стереоаналізу типовою є така архітектура:
function setupAudioGraph(service: HlsAudioService) {
const { audioContext, source } = service;
// Розділяємо стерео сигнал на лівий і правий канали
service.splitter = audioContext.createChannelSplitter(2);
// Створюємо аналайзери для кожного каналу
const analyserLeft = audioContext.createAnalyser();
const analyserRight = audioContext.createAnalyser();
analyserLeft.fftSize = 2048; // частота аналізу
analyserRight.fftSize = 2048;
// Побудова ланцюга: джерело → спліттер → аналізатори → призначення
source!.connect(service.splitter);
service.splitter!.connect(analyserLeft, 0); // Лівий канал
service.splitter!.connect(analyserRight, 1); // Правий канал
// Підключаємо до виходу для відтворення
analyserLeft.connect(audioContext.destination);
analyserRight.connect(audioContext.destination);
service.analysers = [analyserLeft, analyserRight];
}
ChannelSplitterNode розділяє стерео сигнал на моно-канали. Кожен AnalyserNode забезпечує дані частот та амплітуди для свого каналу у реальному часі. Параметр fftSize визначає деталізацію аналізу частот — більше значення дає кращу роздільність, але збільшує навантаження на CPU.
Обробка поширених крайніх випадків
CORS і потоки з іншого домену
Якщо потік HLS знаходиться на іншому домені, MediaElementAudioSourceNode виводить нулі з міркувань безпеки. Рішення — додати атрибут crossOrigin до елемента video та забезпечити від сервера заголовок Access-Control-Allow-Origin:
videoElement.crossOrigin = ‘anonymous’;
Без правильної настройки CORS аналіз аудіо неможливий, хоча відтворення працює.
Стан підвішений на мобільних пристроях
На мобільних платформах (особливо iOS) AudioContext може перейти у стан suspended з метою економії батареї. Перевіряйте стан перед використанням:
async function ensureAudioContextRunning(service: HlsAudioService) {
if (service.audioContext.state === ‘suspended’) {
await service.audioContext.resume();
console.log(‘AudioContext resumed’);
}
}
Викликайте цю функцію під час подій play та canplay.
Перехід між потоками
При зміні джерела HLS (переключення якості або каналу) не створюйте заново MediaElementAudioSourceNode. Просто викликайте hls.loadSource з новою URL — існуючий аудіограф продовжує працювати.
function switchStream(newUrl: string) {
hls.loadSource(newUrl);
// джерельно-об’єктна нода залишається, повторно створювати не потрібно
}
Намацати створення джерела знову призведе до помилки, оскільки воно вже прив’язане до елемента.
Покращення продуктивності
Щоб зменшити навантаження на CPU під час візуалізації:
– зменшіть частоту оновлень: замість оновлення кожного кадру (60 FPS обмежте до 30 FPS за допомогою requestAnimationFrame та підрахунку кадрів);
– налаштуйте fftSize: прості індикатори рівня потребують fftSize = 256, докладні спектрограми — 2048 або 4096. Менші значення зменшують затримки та навантаження;
– Web workers: можна винести обробку даних з AnalyserNode у worker, щоб уникнути блокування основного потоку. Проте вузли аудіо самі мають залишатись на головному потоці.
Використання
З отриманих даних можна реалізувати різні візуалізації та аналізи:
– PPM/VU-метри — відображення поточного рівня сигналу з різними інтеграційними часами
– Спектрограми — в режимі реального часу візуалізація спектра частот за допомогою getByteFrequencyData()
– Детектор тиші — аналіз амплітуди для виявлення пауз у звуці
– Еквалайзери — розділення частот на діапазони для регулювання гучності
Усі ці завдання використовують дані від AnalyserNode, який надає масив значень від 0 до 255 для кожного частотного діапазону.
Сумісність браузерів
Таблиця сумісності:
– Chrome (Desktop): Web Audio API v14+, Native HLS: Ні, HLS.js (MSE): Так
– Firefox (Desktop): Web Audio API v25+, Native HLS: Ні, HLS.js (MSE): Так
– Safari (Desktop): Web Audio API v6.1+, Native HLS: Так, HLS.js (MSE): Не потрібен
– Edge: Web Audio API повний, Native HLS: Ні, HLS.js (MSE): Так
– Chrome Mobile: Web Audio API повний, Native HLS: Так (Android), HLS.js (MSE): Так
– Safari iOS: Web Audio API v6+, Native HLS: Так, HLS.js (MSE): Ні (MSE)
– Firefox Android: Web Audio API повний, Native HLS: Ні, HLS.js (MSE): Так
Важливо: Safari на iOS не підтримує Media Source Extensions, тож hls.js не працюватиме. На щастя, iOS має вбудовану підтримку HLS, і ви можете безпосередньо використовувати video.src. Web Audio API працює з ним коректно.
Посилання на помилки/ referencia
Таблиця помилок з рішеннями:
– Cannot create multiple MediaElementAudioSourceNode — створювати MediaElementAudioSourceNode двічі для одного елемента: використовуйте патерн Singleton для збереження одного посилання
– AudioContext was not allowed to start — AudioContext створено до взаємодії з користувачем: викликайте audioContext.resume() після жесту користувача (клік, торкання)
– MediaElementAudioSourceNode outputs zeroes — CORS обмеження для аудіо з іншого домену: додайте crossOrigin=”anonymous” до video
– HLS manifest not loading — маніфест завантажується до ініціалізації AudioContext: дочекайтесь MANIFEST_PARSED перед створенням джерела
Висновок
Аналіз аудіо з потоків HLS у браузері можливий за правильною схемою. Основні висновки:
– Singleton для AudioContext запобігає повторному створенню вузлів і спрощує управління станом
– Очікування MANIFEST_PARSED від hls.js гарантує коректну ініціалізацію
– Управління автозапуском через resume() забезпечує крос-платформенну сумісність
– Налаштування CORS є обов’язковим для потоків з різних доменів
Повна робоча реалізація доступна за адресою github.com/ABurov30/AudioContext (посилання в тексті), де можна ознайомитися з готовою інтеграцією всіх описаних технік.
Цей підхід дозволяє будувати надійні веб-додатки для потокового перегляду відео з розширеним аудіоаналізом без потреби встановлювати додаткові плагіни чи розширення.
HI-FI News
via DEV Community https://dev.to
12 травня 2026 р. 17:12
May 12, 2026 at 05:12PM

Залишити відповідь