Эта история началась примерно 1.5 года назад. Связана она с проигрыванием музыки в различных браузерах и платформах, на которых они запускаются. Путь полный “боли и страдания” осознания того, что легкая на первый взгляд задача может оказаться не такой уж и легкой, а “незначительные” детали, которым не придаешь значения в самом начале, могут повлиять на всё.

Незначительные детали для самых любопытных :)
1. Подгрузка данных о каждом следующем треке из сети.
2. На каждый элемент аудио: new Audio() или <audio> нужно разрешение пользователя — пользовательское действие на странице.

Предыстория


Наверное, каждый, кто хоть раз в своей жизни писал аудиоплеер для браузеров, сталкивался с проблемой кроссбраузерности и кроссплатформенности.

Вот и я во время работы над новым MVP столкнулся с различными особенностями в отношении проигрывания аудио в браузерах.

А началось всё с того, что нужно было сделать плавное сведение (crossfade) двух треков при воспроизведении – это первая особенность. Наша команда хотела сделать смену треков как на радио. И вторая особенность — каждый последующий трек запрашивается из сети.



Изыскания


Тогда почти во всех наших проектах использовалась библиотека Sound Manager 2.

Практически сразу понимаешь, что воспроизведение одновременно двух аудио файлов на мобильных устройствах не везде одинаково работает!

В Chrome (~62 версия) для ПК треки воспроизводились как надо. На мобильных устройствах (тоже в Chrome) воспроизведение треков работало, но только при активном экране. Когда экран блокировался, следующий трек за текущим играющим не воспроизводился. Что касается iOS / macOS — воспроизведение работало аналогичным образом. Больше информации можно получить тут – раздел “Единичный аудиопоток”.

Так начались хождения за три моря поиски информации по крупицам об особенностях работы браузеров с аудио.

Хорошо, пробую решение с Web Audio без использования каких-либо библиотек. Да, эта технология предназначена для иных целей: синтез, обработка звука, для игр и т.д., нежели простое воспроизведение треков. Но ради эксперимента нужно было попробовать, так как она позволяет компоновать звуки из разных источников на один звуковой выход — колонки/наушники/динамик телефона/и т.п. Есть ребята, которые целенаправленно занимаются исследованием возможностями воспроизведения звука на мобильных устройствах с использованием Web Audio API.

После реализации выяснились определенные нюансы.

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

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

В-третьих, возникли проблемы с декодированием. Если на клиент приходили фрагменты mp3/ogg/wav файлов, то эти кусочки спокойно декодировались и воспроизводились. Но если в браузер приходили чанки mp4 файла, который выступал контейнером для HE-AAC, то их тогда декодировать не удалось. Это в некоторой степени касается и браузера Opera, в котором от версии к версии нестабильно работает воспроизведение MP3 файлов — то воспроизводит, то выдает ошибку, что данный формат не поддерживается.

В-четвертых, не отображалось / не менялось название трека на заблокированном экране на плашке с нативным аудио плеером (на iPad), в т.ч. при переключении между треками. Возможно из-за того, что для тестов использовался iPad с 9 версией iOS — другого на тот момент не было.

В итоге, на данном этапе от Web Audio пришлось отказаться. Всё-таки crossfade не для браузеров, стандартные музыкальные композиций в хорошем качестве достаточно много весят.

Раз от crossfade отказываемся, то реализуем простой fade in и fade out, в начале и в конце музыкального трека соответственно.

Код на позапрошлом шаге был немного доработан и протестирован. В результате тестов всплыли различные нюансы (показаны в таблице). Все это с использованием библиотеки Sound Manager 2.



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



Активация вкладки
В браузере Safari 9+ звук при активации вкладки не всегда появляется.

Из этого можно предположить, что выполнение JS в фоне подвергается throttling’у или поток выполнения полностью останавливается (события и таймеры). Однако, позже станет понятно, что это был отчасти корректный вывод. Ниже будет рассмотрен еще 1 нюанс, связанный с воспроизведением треков и осознанием почему звук не появляется.

Ремарка
Для работы с прогрессом (progressbar), например, его отрисовкой для трека, хорошо использовать requestAnimationFrame вместо setInterval/setTimeout. Можно избежать накопительного эффекта при деактивации (background tab) и последующей активации вкладки и временного ее подвисания, связанного с выполнением всех вычислений и перерисовок состояния прогресса.

В этот же момент возник вопрос: а как же быть с автовоспроизведением треков на ПК и на мобильных устройствах?
Под автовоспроизведением понимается — автоматический старт проигрывания трека без каких-либо действий пользователя при загрузке страницы.
Что касается Safari в отношении автоматического воспроизведения при загрузке страницы, то это невозможно, нужно взаимодействие пользователя со страницей, как и на мобильных устройствах. Это касается как видеоконтента, так и аудиоконтента.

И так, на тот момент было следующее:

  1. нельзя (не желательно) воспроизводить два и более звуков одновременно;
  2. для псевдо “автовоспроизведения” трека необходимо разрешение пользователя — первое взаимодействие, позже это было названо “Продать пальчик устройству”;
  3. в фоне (background tab / lock screen) JS (все зависит от браузера):
    либо замирает полностью;
    либо подвергается throttling’у;
    либо работает также, как и при активной вкладке;
  4. можно автоматически стартовать воспроизведение без звука, но непонятно зачем (для аудио контента)?
  5. где-то далеко начинает маячить мысль, а как сделать так, чтобы JS в фоне продолжал выполняться?

В дело пошли другие библиотеки реализующие функции плеера с предположением, что возможно там есть решение для этой задачи. Несмотря на то, что было просмотрено множество issues на GitHub с описание проблем при воспроизведении треков в различных браузерах, все же была надежда на то, что вот-вот доберешься до сути: почему не работает и как сделать, чтобы работало. Как оказалось, нет…

Несколько примеров кода с видео демонстрацией работы библиотек:

  1. Sound Manager 2 — github pages, github репозиторий, видео: macOS Safari 12; iOS Safari 10 при разблокированном экране
  2. Howler
    Howler v2.0.9 — github pages, github репозиторий, видео: macOS Safari 12, iOS Safari 10
    Howler v2.0.15 — github pages, github репозиторий, видео: macOS Safari 12
    Howler v2.1.1 — github pages, github репозиторий, видео: macOS Safari 12, iOS Safari 10

Для macOS запись видео сделана без звука, поэтому нужно смотреть на индикатор громкости — изображение динамика, на вкладке.

В репозитории доступно больше видео примеров.

В интерактивном примере для Howler v2.1.1 — иногда можно услышать несколько звуков одновременно, это связано с добавлением пула разблокированных пользователем audio элементов (в будущих версиях библиотеки это должны исправить).
В чем причина неработоспособности этих библиотек?

Выше я писал: “В фоне (background tab) JS либо замирает полностью, либо подвергается throttling’у”. Так вот тут всплывает другой момент: библиотеки в коде используют создание новых аудио объектов через new Audio(). Если они создаются динамически, т.е. не используется уже существующий аудио объект, и при этом пользователь никак не взаимодействует с сайтом, неактивна вкладка или заблокирован экран, то некоторые браузеры могут посчитать, что воспроизводить звук от этого аудио элемента не следует, пока вкладка не будет снова активна или пользователь не совершит какое-либо действие.

Пример теста на github pages и в репозитории на github с использованием new Audio(). Видео: macOS Safari 12; iOS Safari 10 с разблокированным экраном.

Похоже, что какого-то универсального инструмента не существует и нужно искать какое-то другое компромиссное решение.

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

Сначала были выявлены важные моменты, которые мешали достичь желаемого результата:

  1. в браузере Safari на macOS не воспроизводятся треки при неактивной вкладке;
  2. отсутствует возможность слушать музыку в фоне (при заблокированном экране) на смартфонах, работающих на iOS и Android, хотелось бы избежать агрессивного перенаправления пользователей в мобильное приложение (в дальнейшем), так как предыдущий опыт показывает, что довольно большая часть пользователей не желает ставить мобильное приложение;
  3. плеер некорректно работает с динамическим плейлистом, т.е. когда заранее не известно, какой будет следующий трек.

Далее это позволило сформулировать цели, которые было необходимо достичь:

  1. обеспечить работу плеера в фоновом режиме — в различных браузерах и на различных платформах;
  2. позволить пользователю самому выбирать чем пользоваться: прослушивать музыку на сайте или в мобильном приложении;
  3. обеспечить возможность использовать плеер (или подход) в различных будущих проектах.

Начался новый этап поиска решения поставленной задачи. На этом этапе уже не использовались различные библиотеки, все исследования велись с использование HTML5 Audio. Итогом стало то, что был найден вариант с использованием dedicated workers. iOS это решение победить опять не позволило — воспроизведение в фоне не работает, зато получилось добиться работоспособности в Android (Chrome, Opera, Safari).

Пример теста HTML5 Audio + Dedicated Workers на github pages и в репозитории на github.

При инициализации Worker'а запрашиваются данные о текущем треке. Worker также занимается отправкой сигнала на получением состояния прогресса — сколько времени трек играет — из основного потока и на основе этих данных решает когда запросить данные о следующем треке из сети.



Также в то время был протестирован следующий пример (github pages, репозиторий на github), когда HTML5 audio тег встраивается в DOM (видео: macOS Safari 12, iOS Safari 10) и у него просто подменяется SRC при переключении между треками. На сегодняшний день на macOS в 12 Safari этот пример работает. К сожалению, сейчас нет возможности проверить работоспособность этого примера на macOS в Safari 10 и 11 версии, но на тот момент при проведении тестов этот пример не работал (autoplay policies, autoplay restrictions).

Если подытожить, то для iOS и macOS браузер Safari не считает новый экземпляр аудио элемента активированным пользователем, если он был создан в фоновом режиме внутри какого-либо события, например, ajax, setTimeout, onended.

Далее, что касается воспроизведения треков в iOS Safari и iOS Chrome, была найдена возможность воспроизводить треки в фоновом режиме (при заблокированном экране) только с использованием HLS. Для платформ iOS и macOS этот формат является стандартом и вещание поддерживается операционной системой. Для Android Chrome и Edge также доступна нативная реализация. А для ПК в Chrome — можно использовать программные обработчики, например, hls.js, Bitmovin Player и т.д.

По ссылке на github репозиторий доступен пример кода, который охватывает самый простой вариант использования – простое проигрывание генерируемого на сервере потока воспроизведения без возможности перемотки, переключения на следующий трек и т.д. Представлены примеры с использованием: тега audio, тега video, библиотеки hls.js, и плеера от Bitmovin. Для запуска требуется Node.js.

Выводы


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

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

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

И напоследок


Объявляю небольшой конкурс, связанный с воспроизведением музыки на iOS с использованием технологии HLS.

Описание можно увидеть по ссылке на github.

Комментарии (23)


  1. Master255
    05.02.2019 14:54

    Есть универсальное решение!
    Могли бы просто спросить.
    Локальное приложение — прокси \\127.0.0.1\, запускающее другое приложение плеер (плееры) по команде браузера (ajax запросы).
    У меня в плеере всё видео так воспроизводится. Сделать аудио и в фоне… не составит труда.
    + я сделал обратную связь даже, от приложения в браузер по окончанию трека. Просто надо записать нужные данные в сокет.
    Необходимо сделать инсталлятор. Минус конечно в том что нужно ставить программу. Зато играют все форматы.
    Добавлю, что браузерные HTML5 аудио модули на сегодня в пререлизном состоянии. Не рекомендую ими пользоваться. Видео HTML5 — вообще в диковинку… наверное ещё на стадии разработки))). На сегодня мой способ — единственное нормальное решение.
    Пример на моём сайте в самом низу. Там инсталлятор видео плеера.

    п.с. генерировать m3u8 для mp3 это у вас хорошая трава))). Я разрабатываю алгоритмы для возможности воспроизведения 4к. (фильмы 100Гбайт) без задержек, а вы в 2019 году mp3 делить решили. И точно скажу, мой алгоритм не совместим с m3u8 HLS мракобесием)). Уже готов и работает.


    1. serf
      05.02.2019 15:16

      Тогда уже лучше на electron запилить полноценное локальное приложение чем прокси какой-то.


      1. Master255
        05.02.2019 15:27

        а как вы туда внедрите видеоплеер? И чем это будет лучше (меньше и быстрее) нативного приложения (прокси) для операционной системы?
        Всё же сайт предполагает гораздо более высокую скорость доступа к контенту, чем установка целого приложения.


      1. barbanel
        05.02.2019 17:39

        Тогда уже лучше на electron запилить полноценное локальное приложение
        Да епт, какой такой, прости Хосспаде, электрон??!
        Я предпочту несколько нативных приложений установить чем один комбайн в электроне.


        1. serf
          05.02.2019 19:12

          Вы ошибаетесь… электрон нужно писать с большой буквы.


    1. cjbars
      05.02.2019 15:28

      Я так понял, что у автора речь идет о веб версии плеера, к тому же речь о серсиве а не о личном приложении. Поправьте меня если я ошибаюсь )))


      1. Master255
        05.02.2019 15:39

        так всё верно… нажимаешь в браузер и играет музыка. Интерфейс плеера, даже можно перенести в браузер. Никто же не мешает. Надо делать просто…


        1. cjbars
          05.02.2019 15:41

          а как это сделать без лишних телодвижений со стороны пользователя? Просто зашел на страничку включил и поет


          1. Master255
            05.02.2019 16:14

            никак. А зачем изучать чьи-то баги, когда можно сделать красиво, просто, быстро и более функционально! Спросите пользователей, они скорее поставят программу и будут пользоваться полноценным функционалом, чем не поставят и звук будет заикаться или вовсе перестанет играть.
            А если вспомнить про аппаратное ускорение и приоритеты процессов браузера, то они совсем не такие, каких ждут пользователи. Браузер — это не мультимедиа приложение, что бы работать в фоне ещё…


            1. Fragster
              05.02.2019 18:52

              Т.е. чтобы послушать музыку на сайте — ставить приложение? Административные права в ОС, настройка файрволла и вот это все?


            1. dipiash Автор
              06.02.2019 09:15

              А зачем изучать чьи-то баги, когда можно сделать красиво, просто, быстро и более функционально! Спросите пользователей, они скорее поставят программу и будут пользоваться полноценным функционалом, чем не поставят и звук будет заикаться или вовсе перестанет играть.

              И потреять львиную долю пользователей из за того, что человек не может получить доступ к какой-то «условной фиче» здесь и сейчас?
              Нет уж, в такие игры я не играю. И предпочту охватить большинство платформ через различные решения, что бы дать человеку чем-то пользоваться в том месте, куда он пришел сразу, а не прошел через 100500 редиректов.

              А если вспомнить про аппаратное ускорение и приоритеты процессов браузера, то они совсем не такие, каких ждут пользователи.

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


              1. Master255
                06.02.2019 11:27

                ну если ваша цель срубить побольше бабла, то да. Мой вариант не подойдёт. А если дать нормальный сервис, то только мой вариант.
                Популярность != качество.
                Стать ещё одним яндекс музыка или гугл музыка… или сервисом, каких миллион… ничего интересного… да и зачем тогда играть в фоне. Копируйте код у яндекса… только название поменять не забудьте))).


    1. KYuri
      05.02.2019 19:14

      Есть универсальное решение!
      Работает на Android/iOS/masOS/Linux/BSD? Если нет, о какой универсальности тогда может быть речь?

      Добавлю, что браузерные HTML5 аудио модули на сегодня в пререлизном состоянии.
      Инфа 100%?

      Не рекомендую ими пользоваться.
      А я — рекомендую. Почему Вы думаете, что Ваша рекомендация более авторитетная?)


      1. Master255
        06.02.2019 11:33

        Инфа сотка.
        на Android/iOS/masOS/Linux/BSD работает.

        А я — рекомендую. Почему Вы думаете, что Ваша рекомендация более авторитетная?)

        потому что я протестировал их и браузерные плееры не воспроизводят все треки, которые поддерживают. Ещё не умеют. Это мы можем обсудить в профильной теме или чате, но не здесь.


        1. KYuri
          06.02.2019 12:37

          Инфа сотка.
          Ссылочку дадите почитать? А то куда ни глянь, ни плюнь — всё хорошо.

          на Android/iOS/masOS/Linux/BSD работает.
          Дайте мне ссылочку и инструкцию по установке/настройке для Debian и Android?

          потому что я протестировал их и браузерные плееры не воспроизводят все треки, которые поддерживают. Ещё не умеют.
          Я тоже тестировал. Да, не всё поддерживают. Но у Вас же самого есть статья про aurora.js, которая расширяется плагинами под различные форматы (правда, с тех пор и браузеры уже многому научились).
          А моя позиция такая: если речь идёт об удобстве пользователя, то подход должен быть другим: если что-то не поддерживается на клиенте — транскодируем на сервере.


    1. dipiash Автор
      06.02.2019 08:57

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

      Если быть честным и говорить об универсальности, то в контексте какой либо платформы. В универсальность нативных приложений на iOS / Android — я могу еще поверить, но утверждать со 100% гарантией я бы не стал. Что касается web браузеров, я бы вообще не говорил о какой-то универсальноти с учетом всего зоопарка.


      1. Master255
        06.02.2019 13:03

        дальнейшее обсуждение платное. +в карму и + во все мои комментарии.


  1. KYuri
    06.02.2019 12:15

    Похоже, что какого-то универсального инструмента не существует и нужно искать какое-то другое компромиссное решение.
    Если речь про автопереключение треков — со штатным (HTML5) аudio работает и на Safari под macOS, и на Safari под iOS.


    1. dipiash Автор
      06.02.2019 12:23

      Я же правильно вижу, что это эмулятор?
      Когда я тестировал на эмуляторе, то сталкивался с таким (на каких-то работает, на каких-то нет). Предположу, что вы заменяете src у audio тэга. На реальном девайсе это не работает :(


      1. KYuri
        06.02.2019 12:33

        1. dipiash Автор
          06.02.2019 13:05

          Что бы не быть голословным, взял реальный девайс для теста)
          Залил видео в github или ссылка на прямое скачивание с github.
          Просто показалось странным снимать экран телефона и выкладывать в статью, поэтому делал на эмуляторе.
          Все может зависить от устройства, поэтому на 100% утверждать не могу, что нигде не работает. Но у коллег с iPhone'ами на работе тоже в фоне не воспроизводится.

          upd.
          Если не блокировать экран, то треки переключаются как надо.


  1. KYuri
    06.02.2019 12:32

    Да, эмулятор. На ваших видео тоже эмулятор)
    Предположение правильное.
    Сейчас железного девайса нету. Но когда был (iPad Air под iOS 11, кажется) — всё тоже работало.


  1. Alexey2005
    06.02.2019 18:04

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