Главная » Хабрахабр » Адаптивный Waveform для вашего аудиосервиса

Адаптивный Waveform для вашего аудиосервиса

Радиопередача шла 40 минут плюс две музыкальные паузы. Когда мне понадобилось для сайта одной радиопередачи наладить выкладку аудио архива, помимо админки нужен был еще и аудиоплеер. Использовать Waveform в таких длинных форматах особенно удобно, поэтому как и многие музыкальные сервисы, я решил использовать это решение в оформлении плеера.

Он не адаптивен, его крайне ресурсоемко редизайнить, если он в растре.
Всем известный SOUNDCLOUD решил этот вопрос на маленьких экранах двиганием всей waveform относительно статического центра. При планируемом будущем редизайне сайта и, возможно, будущих мобильных приложений, растровый waveform тут просто клином упирался. Но я так не хочу.

Было бы глупо отказываться от его возможностей и по генерации waveform. Заливка радиопередач осуществлялась через админку, и я сразу делал более сжатые копии аудиофайлов через ffmpeg.

Алгоритм действий:

1. Генерация waveform в минимальном размере для хранения
2. Перевод в вектор (JSON)
3. Отрисовка плеера по этому массиву
4. Реализация адаптивности: равномерное сокращение массива и возврат к п.3

Генерация waveform

Каким размером выбрать итоговый растровый файл? Если мы возьмем мой дизайн плеера (он здесь уменьшен по ширине), то увидим, что на одну полоску приходится 2 пикселя (плюс 1 пиксель разделитель). Это значит, что 600px даст нам 1200px по ширине.

Ну если только не тянуть по дизайну на всю ширину 4К монитора, стоит об этом подумать, но останавливаюсь на размере 600x60px. Предполагаю, что в будущем крайне маловероятным будет необходимость в большем представлении аудиофайла.

А теперь ближе к коду:

shell_exec("ffmpeg -y -i '$name.mp3' -filter_complex 'aformat=channel_layouts=mono,compand,showwavespic=s=600x120,crop=in_w:in_h/2:0:0' -c:v png -pix_fmt pal8 -frames:v 1 '$png_path.png' > /dev/null 2>/dev/null &");

-filter_complex — подключить фильтры

aformat — работа со звуком

channel_layouts

-mono — режим моно

В этом режиме и тихие и громкие звуки будут выравнены по громкости, что позволяет получать waveform без пиков и перегрузок как на тихих так и на громких записях. -compand — это компрессор и экспандер. Форма волны как бы всегда растянута до максимума.

-showwavespic=s=600x120 — s принимает размер изображения.

Как правило, выходная АЧХ зеркально отображается вокруг оси x. -crop=in_w:in_h/2:0:0 — обрезка полученного изображения. Поэтому мы кропаем, оставляя только верхушку «айсберга».

png8 отлично подходит по качеству(lossless в нашем случае)/месту. -c:v png -pix_fmt pal8 -frames:v 1 — формат выходного изображения, цветовая палитра и только первый фрейм (анимация нам не нужна).

А '&' позволяет php не дожидаться завершения работы консоли, а продолжать дальше. > /dev/null 2>/dev/null & слать выходные и рабочие данные в пропасть.

На выходе мы получаем вот такое изображение:

4кб
Размер итогового файла 2.

Разработчики, видимо, поменяли дефолтные значения. Забавно то, что пару лет назад вместо белого был красный цвет.

Перевод waveform в вектор

Полученное изображение — это амплитуда по Y и время по X. Ее элементарно перевести в одномерный массив JSON. Где значения будут выступать в роли значений амплитуды, а время — просто их порядковый индекс.

Перевод я решил делать на лету, уж довольно быстро он делается.
Замеряем количество пикселей по Y сверху до первого другого, и переходим к следующему пикселю по X.

$a = imagecreatefrompng("test.png");
$i = 0;
$h = '60';
// horizontal movener
while ( $i < 600 ) else { $arr[$i] = $y; } $c++; } $i++;
}; echo json_encode($arr);

Итоговый массив состоит из 600 значений.

[46,28,34,35,34,35,26,33,39,29,29,30,30,30,33,33,28...]

Отрисовка плеера по JSON

Для удобной работы прогресс бара, я взял либу progressor.js у Elliot Bentley. Он ее сделал для сервиса аудио транскрипций.

76 KB github.com/ejb/progressor.js 2.

Взглянем еще раз на наш плеер.

Прогресс бар состоит из двух слоев: фон с серыми столбиками и с зелеными.

Ниже изображения отрисовываются функцией getGraph.

Смысл ее в том, чтобы рисовать столбики нужной толщины и цвета со столбиками разделителями.

var c = document.createElement("canvas");
c.width = width;
c.height = height;
var ctx = c.getContext("2d"); function getGraph(fillStyle1,fillStyle2,fillStyle3) { if (fillStyle3) { //console.log(fillStyle1); var grd = ctx.createLinearGradient(0,120,0,0); grd.addColorStop(0.5,fillStyle1); grd.addColorStop(1,fillStyle2); fillStyle1 = grd; fillStyle2 = fillStyle3; } json.forEach(function(item, i, arr) { ctx.fillStyle = fillStyle1; ctx.fillRect(i * 3, height, 2, item - height); ctx.fillStyle = fillStyle2; var next = json[i + 1]; if( item <= next ) { h2 = next; } else { h2 = item; } ctx.fillRect(i * 3 + 2, height, 1, h2 - height); }); return c.toDataURL();
}

Вот так выглядит рабочий пример без адаптивности

4. Реализация адаптивности

Теперь нам нужносократить массив JSON на клиенте до нужного размера и вот тебе адаптивность.

План А

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

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

Есть на js реализация алгоритма: Нам нужны алгоритмы ресемплинга.

largestTriangleThreeBuckets

Y. Работает она хорошо, только просит на вход такой массив, по индексам которого она получит координаты X. Работает это дело вот так: У нас массив одномерный, поэтому пришлось чутка покумекать и переделать функцию.

А здесь можно потрогать с адаптивкой как КДПВ.

Тогда можно менять ширину этого окошка. Переведите режим просмотра, где фрейм с html будет справа.

План Б — пых

Однако, мне все таки не хотелось бы нагружать клиентскую часть. К примеру, я хочу 1000 точек-5000, да на всю ширину экрана. Если у меня будет больше точек, как поведет себя это дело на мобиле? С одной стороны, в этом совершенно нет проблем, это не так вроде бы и накладно если судить по демкам алгоритма, он жует 5000 точек легко. Но с другой стороны — давать надо столько, сколько спрашивают. Вопрос дизайна.

Js вы можете этот код перенести на сервер. Элементарно, если у вас Node. А если у вас php, вы можете найти реализацию этого алгоритма на php но… зачем, подумал я.

В той же нативной либе GD, которую мы использовали для генерации JSON. Где же алгоритмы ресемплинга? Мы просто передаем с клиента параметр в пикселях требуемой ширины и ресайзим нашу waveform перед переводом в JSON.

Поэтому расширю код, написанный в начале.


$h = 60;
$width_new = 600; $a = imagecreatefrompng("$id.png");
$width_old = imagesx($a); $aa = imagecreatetruecolor($width_new, $h); imagecopyresized($aa, $a, 0, 0, 0, 0, $width_new, $h, $width_old, $h);
imagetruecolortopalette($aa, false, 2); $i = 0;
// horizontal movener
while ( $i < $width_new ) { // vertical movener $y = $h-1; $c = 0; while ( $c < $h ){ //echo imagecolorat($aa, $i, $c ); // search what color is needed if(imagecolorat($aa, $i, $c ) == "1"){ $arr[$i] = $c; break; } else { $arr[$i] = $y; } $c++; } $i++;
}; echo json_encode($arr);

После этого можно не переживать, если нужно будет поменять дизайн, ширину плеера, расширить в мобильное приложение. Все выглядит довольно гибким и очень шустрым.

Код лежит тут

.

Пасхалка.

Окно нашей комнаты выходило на две старые кирпичные 9-ти этажки, которые я помню еще подростком, знаю, что за ними открывается трамвайное кольцо, чуть дальше — старая больница, она сразу за школой, а текущее здание с офисом, где я пытаюсь находиться копаясь в воспоминаниях, это бывшая недостроенная больница, теперь уже чисто офисное помещение. Наверное, это был солнечный день. А теперь, оказывается, я бодро бьюсь током о блестящие перила, спускаясь по лестнице, и любуюсь формой искажений этого здания в отражении ближайшего жилого комплекса. Помню как в детстве здесь тренировались спецназовцы, их показывали по телевизору, бодро штурмующих бетонное сооружение, поросшее вокруг всем, чем только можно. И на ней надпись зеленой краской «Пока Борис у власти» и «Трудовая Россия». (Совсем рядом, по трамвайной линии открывается стена старого большого кладбища. Я не видел больше из наследия 90-ых более древнего памятника в городе.) Черт знает кто и когда их сделал, но по прошествии пары десятков лет они все так же читаются, но остаются совершенно невидимыми.

Думаешь, вот должно же прорасти что-то сквозь этажи что-то сюда, но за эти 5 лет из трансцендентного сюда заглядывал только мойщик окон, а из имманентного — бухгалтера с безумными глазами, которые стучат по всем дверям на этаже в поисках кого-либо, кто объяснит как им подписать платежку через безумный плагин интернет-банка из-за очередного обновления браузера.
На нашем верхнем этаже пусто, как бывает пусто в начатом пакете с гречкой: внизу куча всего и плотно: какие-то крутачи из спецгеоразведки, офис 2gis, потом очередные сеошники, а сверху — почти нет зерен.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Да будет свет

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

Где работать в ИТ #1: Voximplant

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