Получаем и переключаем WebVTT субтитры в ExoPlayer

от автора

Всем привет.
Пару дней назад появилась задача переключать WebVTT субтитры в HLS потоке.
Играем видео мы с помощью ExoPlayer и по началу казалось, что Гугл и Ко должны были бы предоставить решение из коробки вида «взял и сделал». Но реальность не совпала с ожиданием:)

Гуглинг и Хабринг не привели к результату и всё сводилось к тому, чтобы ковырять официальное демо приложение ExoPlayer.
Т.к. статья предполагает некое знакомство со структурой HLS и наличие какого-то опыта в ExoPlayer, то перейдём сразу к делу.

Вот что говорит нам документация о переключении потоков(видео, аудио, субтитры). А говорит она нам почти ничего — инициализируйте плеер с помощью DefaultTrackSelector и используйте его. Всё, ну ок)

Все субтитры любого вида(CEA-608, WebVtt) как и любые другие дорожки хранятся внутри DefaultTrackSelector и нужно уметь до них добраться. Всё делится на группы и подгруппы, и если кратко, то внутрення структура выглядит примерно следующим образом:

Попробуем теперь получить субтитры типа WebVTT, они должны хранится внутри Renderer c типом «3»(C.TRACK_TYPE_TEXT — константа в Exo):

fun getVttSubtitles(): List<String> {         val tracks = mutableListOf<String>()         // берём MappedTrackInfo         defaultTrackSelector.currentMappedTrackInfo?.let { mappedTrackInfo ->             val renderCount = mappedTrackInfo.rendererCount             for (renderIndex in 0 until renderCount) {                 // проходим по всем renderer'ам и ищем тип TEXT                 val renderType = mappedTrackInfo.getRendererType(renderIndex)                 if (renderType == C.TRACK_TYPE_TEXT) {                     val trackGroupArray = mappedTrackInfo.getTrackGroups(renderIndex)                     // проходим по всем подгруппам                     for (trackGroupArrayIndex in 0 until trackGroupArray.length) {                         val trackGroup = trackGroupArray[trackGroupArrayIndex]                         for (trackGroupIndex in 0 until trackGroup.length) {                             // проходим по всем трекам с форматом TEXT_VTT                             val format = trackGroup.getFormat(trackGroupIndex)                             if (format.sampleMimeType == MimeTypes.TEXT_VTT) {                                 tracks += format.language.orEmpty()                             }                         }                     }                 }             }         }          return tracks     } 

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

Но нам же надо знать не только о самом списке, но ещё и о выбранных вариантах(которые мы будем выбирать в будущем). По какой-то причине получить выбранные треки из DefaultTrackSelector нельзя, зато можно у самого ExoPlayer. Схема получения примерно такая же, идём вглубь, но тут мы пропускаем проход по рендерерам:

fun getSelectedVttSubtitles(): List<String> {         val selectedLangs = mutableListOf<String>()         val currentTrackSelections = exoPlayer.currentTrackSelections         for (selectionIndex in 0 until currentTrackSelections.length) {             val trackSelection = currentTrackSelections[selectionIndex]             if (trackSelection != null) {                 // проходим по все выбранным группам                 val length = trackSelection.length()                 for (trackIndex in 0 until length) {                     // ищем все выбранные треки нужного формата                     val format = trackSelection.getFormat(trackIndex)                     if (format.sampleMimeType == MimeTypes.TEXT_VTT) {                         selectedLangs += format.language.orEmpty()                     }                 }             }         }          return selectedLangs     } 

Отлично, имеем общий список и список выбранных. Как объединить и отображать на UI — не цель этой статьи. Способов много. Нам же осталось научиться устанавливать дорожку.
Для наглядности сделаем это способом в лоб, чтобы были очевидны все циклы и индексы.
Установка дорожку по её языковому коду(«ru», «en», …):

fun selectTrackByIsoCodeAndType(langCode: String) {         defaultTrackSelector.currentMappedTrackInfo?.let { mappedTrackInfo ->             // начало такое же как и в получении списка субтитров             val renderCount = mappedTrackInfo.rendererCount             for (renderIndex in 0 until renderCount) {                 // проходим по всем renderer'ам и ищем тип TEXT                 val renderType = mappedTrackInfo.getRendererType(renderIndex)                 if (renderType == C.TRACK_TYPE_TEXT) {                     val trackGroupArray = mappedTrackInfo.getTrackGroups(renderIndex)                     // проходим по всем подгруппам                     for (trackGroupArrayIndex in 0 until trackGroupArray.length) {                         val trackGroup = trackGroupArray[trackGroupArrayIndex]                         for (trackGroupIndex in 0 until trackGroup.length) {                             val format = trackGroup.getFormat(trackGroupIndex)                             // находим наш формат с нужным языковым кодом                             if (format.sampleMimeType == MimeTypes.TEXT_VTT                                 && format.language == langCode                             ) {                                 // копируем текущее состояние треков                                 val currentParams = defaultTrackSelector.buildUponParameters()                                 // очищаем предыдущие установки(если были)                                 // в рендерере                                 currentParams.clearSelectionOverride(                                     renderIndex, trackGroupArray                                 )                                  // сообщаем, что именно хотим выбрать                                 // самое сложное здесь не запутаться в индексах                                  // указываем какой трек в какой группе выбираем                                 val override = DefaultTrackSelector.SelectionOverride(                                     trackGroupArrayIndex, trackGroupIndex                                 )                                  // указываем в каком рендерере установка                                 currentParams.setSelectionOverride(                                     renderIndex,                                     trackGroupArray,                                     override                                 )                                  // устанавливаем новые параметры в селектор                                 defaultTrackSelector.setParameters(currentParams)                                  return                             }                         }                     }                 }             }         }     } 

Заключение
Мы рассмотрели основы того, как получать и переключать WebVTT субтитры в ExoPlayer’e.
В реальности этим же способом можно легко работать и с другими типами субтитров — достаточно параметризировать методы типом. Таким же образом не составит труда работа и с аудио дорожками. Конечно, именно в таком виде использовать решение довольно сложно и требуется приводить к более удобному виду.

Спасибо за внимание.

ссылка на оригинал статьи https://habr.com/ru/post/498582/


Комментарии

Добавить комментарий

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