WordPress × Wavesurfer JS

WordPress × Wavesurfer JS — наконец-то дошли руки поделиться своим опытом использования wavesurfer.js в связке с сайтом на WordPress.

WordPress × Wavesurfer JS

WordPress × Wavesurfer JS

Когда я делал вторую версию своего сайта и решил обновить раздел с музыкой мне захотелось сделать плеер с визуализаций частотной диаграммы, как у SoundCloud. Я достаточно быстро нашел wavesurfer.js и дальше начался процесс сбора информации с разных сайтов о том как его использовать. Постепенно я пришел к желаемому результату и решил им поделиться, возможно, это будет полезно для таких же начинающих разработчиков как и я.

Wavesurfer.js — это библиотека визуализации аудио с открытым исходным кодом для создания интерактивных, настраиваемых форм волны.

Я сделал шаблон страницы WordPress, который можно выбрать при создании страницы и поместил в него сам скрипт, верстку и дополнительные возможности, которые мне были нужны.

Список опций, которые есть в моём примере:

  • Аудио плеер в целом

  • Визуализация частотной диаграммы

  • Поиск по треку (кликом по частотной диаграмме)

  • Время общее и прошедшее время трека

  • Изображение обложка

  • Кнопка play /pause (иконка на кнопке меняется)

  • Регулятор громкости

  • Кнопка mute

  • Музыкальные стили в формате тегов

  • Кнопка-ссылка купить

  • Иконки-ссылки на музыкальные платформы

  • Описание трека и текст песни

Посмотрите пример страницы с плеером на моём сайте в разделе музыка.

Пример страницы с плеером Wavesurfer JS

Пример страницы с плеером Wavesurfer JS

Далее я постараюсь подробно рассказать особенности веб-разработки, которую я сделал.

Вот исходный код шаблона страницы WordPress с плеером Wavesurfer JS и дополнительными функциями.

Сразу уточню, что я использую версию скрипта wavesurfer.js 6.6.3 и размещаю её локально на своём веб-сервере. Это не последняя версия, вы можете найти её на GitHub проекта.

Код моего решения также доступен на GitHub.





Play Button

00:00:00 00:00:00
Volume
<? the_title(); ?>

ID, '%mp3_URL', true); $pcm = get_post_meta($post->ID, '%pcm', true); ?>

WordPress × Wavesurfer JS: описание решения

Далее я подробно постараюсь описать составные части решения.

Я активно использовал произвольные поля WordPress, например чтобы получить URL для .mp3 файла или PCM данные для построения визуализации звуковой волны. 

В этой части кода создаются переменные в которые записываются дынные из произвольных полей WordPress.

ID, '%mp3_URL', true);
$pcm = get_post_meta($post->ID, '%pcm', true);
?>

Вот так это выглядит в админке WordPress:

03837951a1c19a8f4d876077a01f9c60.png

Вот этот блок кода настраивает сам wavesurfer, например то как будет выглядеть визуализация волны:

const initializeWavesurfer = () => {
  return WaveSurfer.create({
    backend: "MediaElement",
    container: "#waveform",
    responsive: true,
    height: 80,
    waveColor: "#9999ff",
    progressColor: "#3300ff",
    barWidth: 2,
    barHeight: 1,
    barGap: 2,
    barRadius: 2,
  })
}

В этом блоке кода происходит подключение .mp3 файла и PCM данных для быстрого рендеринга визуализации (об этом подробнее позже):

// --------------------------------------------------------- //
// Create a new instance and load the wavesurfer
	const wavesurfer = initializeWavesurfer()
	var musicFile = ''
	var pcm = ;
	wavesurfer.load(musicFile, pcm)

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

Я уже знал, что это можно решить заранее указав PCM данные, но не знал, как эти данные получить. В итоге вот этот кусок кода помогает это сделать:

/*
wavesurfer.on('waveform-ready', () => {
	
	wavesurfer.exportPCM(1024, 10000, false);
	
})
*/

Он закомментирован т. к. требуется только при размещении нового трека. Процесс немного кривой, но это подходит лично для меня:

Cначала я отключаю PCM данные вот тут:

Было:

wavesurfer.load(musicFile, pcm)

Стало:

wavesurfer.load(musicFile)

После этого плеер начинает работать в режиме загрузки аудио для построения визуализации звуковой волны. Я также убираю пометки комментария с этого кода:

wavesurfer.on('waveform-ready', () => {
	
	wavesurfer.exportPCM(1024, 10000, false);
	
})

Заполняю всю прочую информацию, которую требует шаблон и запускаю предварительный просмотр страницы WordPress, начинается индикация загрузки трека и после её завершения автоматически открывается новая вкладка браузера в которой и содержаться PCM данные.

Далее я просто копирую их и вставляю в произвольное поле WordPress для этой страницы. Дальше надо вернуть PCM данные в коде:

wavesurfer.load(musicFile, pcm) 

И закомментировать этот блок кода:

/*
wavesurfer.on('waveform-ready', () => {
	
	wavesurfer.exportPCM(1024, 10000, false);
	
})
*/

После этого визуализация отображается моментально.

CSS-стили выглядят вот так и по ним, я думаю, комментарии излишни. Но если у вас возникнут вопросы, пожалуйста, напишите в комментариях, я постараюсь помочь.

/* Audio Player */

.music-single-container {

}
.music-single-desc {
	margin: 0 auto;
	max-width: 1600px;
	padding: 2%;
}

.audio-player-container {
 	display: grid;
	gap: 0;
	grid-template-areas:
	"play-track-name play-track-name volume-group cover"
	"wave wave wave cover"
	"currentTime currentTime totalDuration cover";
	grid-template-rows: repeat(3, auto);
	grid-template-columns: repeat(4, 1fr);
	column-gap:  2%;
	row-gap:  0;
	justify-items: stretch;
	align-items: start;
	justify-content: space-evenly;
	align-content: space-evenly;
	padding: 2%;
	margin: 0 auto;
	max-width: 1920px;
	background: linear-gradient(#eee, #fff);
}

.audio-player-container-wraper {
	background: linear-gradient(#eee, #fff);
}

.music-single-cover, .music-single-cover:hover {
	grid-area: cover;
	width: 100%;
	border: 0;
}

.music-single-cover img {
	width: 100%;
	height: auto;
	aspect-ratio: 1 / 1;
	box-shadow: var(--shadow-elevation-medium);
}

.play-track-name {
	grid-area: play-track-name;
	display: grid;
	gap: 0;
	grid-template-areas:
	"play-pause track-name"
	"play-pause track-name"
	"play-pause music-style";
	grid-template-rows: repeat(3, auto);
	grid-template-columns: 80px auto;
	column-gap:  2%;
	row-gap:  0;
	align-self : center;
}

.play-track-name h1, .play-track-name .music-style {
	margin: 8px 0;
	padding: 0;
	line-height: 1.3;
}

.track-name {
	grid-area: track-name;
	text-align: start;
	align-self : end;

}

.music-style {
	grid-area: music-style;
}

.play-button {
	grid-area: play-pause;
	align-self : center;	
	width: 80px;
	height: 80px;
	padding: 20px;
}


#loading_flag {
	padding: 25px 0;
	text-align: center;
	background: url(../img/icons/waveform-thin.svg) center center;
	color: var(--accent-color);
	font-weight: bold;
	display: none;
}

#waveform {
	height: 120px;
}

.play-button-icon, .volume-icon {
	width: 100%;
	height: auto;
	aspect-ratio: 1 / 1;
}

.play-button, .volume-button {
	background: var(--accent-color);
	line-height: 0;
	margin: 0;
	border-radius: 50%;
	cursor: pointer;
	transition: 0.2s linear;
}

.play-button:hover, .volume-button:hover {
	background: #3300cc;
	transform: scale(1.1);
}

.volume-group {
	grid-area: volume-group;
	display: grid;
	gap: 0;
	grid-template-areas:
	"volume-button volume-slider";
	grid-template-rows: repeat(1, auto);
	grid-template-columns: repeat(2, auto);
	column-gap:  20px;
	row-gap:  0;
	justify-items: stretch;
	align-items: start;
	justify-content: space-evenly;
	align-content: space-evenly;
	align-self : center;
	justify-self: end;
}

.volume-button {
	grid-area: volume-button;
	align-self : center;
	width: 40px;
	height: 40px;
	padding: 8px;
}

.volume-slider {
	grid-area: volume-slider;
	align-self : center;	
}
#currentTime {
	grid-area: currentTime;
	color: #999;
}

#totalDuration {
	grid-area: totalDuration;
	justify-self: end;
	color: #999;
}

#waveform {
	grid-area: wave;
	align-self : end;
	padding: 20px 0;
	border-radius: 8px;

}

wave{
	overflow-x: hidden!important;
	border: 0!important;
	cursor: pointer!important;
}

@media only screen and (max-width: 940px) {
	.audio-player-container {
		padding: 8vw 4vw;
		display: grid;
		gap: 0;
		grid-template-areas:
		"cover cover cover cover"
		"play-track-name play-track-name play-track-name play-track-name"
		"wave wave wave wave"
		"currentTime volume-group volume-group totalDuration";
		grid-template-rows: repeat(4, auto);
		grid-template-columns: repeat(4, 1fr);
		column-gap:  2%;
		row-gap:  2%;
		justify-items: stretch;
		align-items: start;
		justify-content: space-evenly;
		align-content: space-evenly;
	}

	#currentTime, #totalDuration {
		align-self : center;
		font-size: 0.8em;
	}

	.volume-group {
		column-gap: 2vw;
		justify-self: center;
		padding: 20px 0;
	}

	.music-single-desc {
		padding: 4%;
	}

	.play-track-name {
		column-gap:  2vw;
	}

	.play-track-name h1 {
		font-size: 1.2em;
	}

	#waveform {
	margin-left: -4vw;
	margin-right: -4vw;
	}

	.play-track-name .music-style {
		line-height: 1.9;
	}
}

/* Input Range */

input[type=range] {
  width: 100%;
  margin: 7.6px 0;
  background-color: transparent;
  -webkit-appearance: none;
}
input[type=range]:focus {
  outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
  background: rgba(153, 153, 255, 0.78);
  border: 0;
  border-radius: 25px;
  width: 100%;
  height: 4.8px;
  cursor: pointer;
}
input[type=range]::-webkit-slider-thumb {
  margin-top: -7.6px;
  width: 20px;
  height: 20px;
  background: var(--accent-color);
  border: 0;
  border-radius: 11px;
  cursor: pointer;
  -webkit-appearance: none;
}
input[type=range]:focus::-webkit-slider-runnable-track {
  background: #9e9eff;
}
input[type=range]::-moz-range-track {
  background: rgba(153, 153, 255, 0.78);
  border: 0;
  border-radius: 25px;
  width: 100%;
  height: 4.8px;
  cursor: pointer;
}
input[type=range]::-moz-range-thumb {
  width: 20px;
  height: 20px;
  background: var(--accent-color);
  border: 0;
  border-radius: 11px;
  cursor: pointer;
}
input[type=range]::-ms-track {
  background: transparent;
  border-color: transparent;
  border-width: 8.5px 0;
  color: transparent;
  width: 100%;
  height: 4.8px;
  cursor: pointer;
}
input[type=range]::-ms-fill-lower {
  background: #9494ff;
  border: 0;
  border-radius: 50px;
}
input[type=range]::-ms-fill-upper {
  background: rgba(153, 153, 255, 0.78);
  border: 0;
  border-radius: 50px;
}
input[type=range]::-ms-thumb {
  width: 20px;
  height: 20px;
  background: var(--accent-color);
  border: 0;
  border-radius: 11px;
  cursor: pointer;
  margin-top: 0px;
  /*Needed to keep the Edge thumb centred*/
}
input[type=range]:focus::-ms-fill-lower {
  background: rgba(153, 153, 255, 0.78);
}
input[type=range]:focus::-ms-fill-upper {
  background: #9e9eff;
}
/*TODO: Use one of the selectors from https://stackoverflow.com/a/20541859/7077589 and figure out
how to remove the virtical space around the range input in IE*/
@supports (-ms-ime-align:auto) {
  /* Pre-Chromium Edge only styles, selector taken from hhttps://stackoverflow.com/a/32202953/7077589 */
  input[type=range] {
    margin: 0;
    /*Edge starts the margin from the thumb, not the track as other browsers do*/
  }
}

/* Input Range */
.buy-streaming-wraper {
	background: #f5f5f5;
}
.buy-streaming-container {
	margin: 0 auto;
	padding: 1vw 2vw;
	max-width: 1600px;
	display: grid;
	gap: 0;
	grid-template-areas:
	"buy streaming-link";
	grid-template-rows: repeat(1, auto);
	grid-template-columns: repeat(2, 1fr);
	column-gap:  20px;
	row-gap:  0;
	justify-items: stretch;
	align-items: start;
	justify-content: space-evenly;
	align-content: space-evenly;
}

.streaming {
	grid-area: streaming-link;
	line-height: 0;
	align-self : center;
}
.streaming-link {
	width: 28px;
	display: none;
	border: 0;
	filter:progid:DXImageTransform.Microsoft.Alpha(opacity=60);
	-moz-opacity: 0.60;
	-khtml-opacity: 0.60;
	/* opacity: 0.60; */
}
.streaming-link:after {
	content: ''!important;
}
.streaming-link:hover {
	border: 0;
}
.streaming-link img {
	width: 100%;
	height: auto;
	aspect-ratio: 1 / 1;
	line-height: 0;
}
.buy {
	grid-area: buy;
}
.buy svg {
	width: 22px;
	vertical-align: text-bottom;
}
.buy .button, .buy .wp-block-button__link {
	margin: 0;
}


@media only screen and (max-width: 940px) {
	.buy-streaming-container {
		margin: 0 auto;
		padding: 4vw;
		display: grid;
		gap: 0;
		grid-template-areas:
		"buy"
		"streaming-link";
		grid-template-rows: repeat(2, auto);
		grid-template-columns: repeat(1, 1fr);
		column-gap:  20px;
		row-gap:  4vw;
		justify-items: stretch;
		align-items: start;
		justify-content: space-evenly;
		align-content: space-evenly;
	}
	.buy .button, .buy .wp-block-button__link {
		width: 100%;
	}
	.streaming {
		justify-self: center;
	}
	.streaming-link {
		width: 28px;
	}
}
/* Audio Player */

WordPress × Wavesurfer JS: заключение

Надеюсь эта статья будет полезна таким же начинающим разработчикам как и я.

Если у вас возникли какие-либо вопросы или предложения по моей реализации, пожалуйста, напишите мне или оставьте комментарий.

Автор: Тимофей Кузнецов aka Tiku Digital

© Habrahabr.ru