Один момент: готовим видеоленту без костылей и бубнов

Всем приветы! Меня зовут Ваня, я медиаинженер и занимаюсь разработкой видеоплатформы в Ozon — в основном бэкендом.

В апреле 2022 года мы презентовали сервис Ozon Моменты — ленту коротких видео. Главные фичи, которые мы хотели реализовать:

  • скорость отображения контента: видео должно стартовать максимально быстро, а переходы между роликами должны быть максимально бесшовными;

  • качество контента: видео должно быть приемлемого качества и хорошо выглядеть;

  • размер контента: видеофайл должен быть минимального размера;

  • универсальность контента: видео должно воспроизводиться на любом экране, будь то iPhone 69 Pro Max или тостер от Smeg.

Что мы сделали для реализации вот этого всего и на каких дрожжах, читайте под катом.

381aa11f1c8426fce7f69bfbdc26b429.jpg

Из чего состояла видеоплатформа Ozon

Мы не стали изобретать велосипедов и сделали всё вполне стандартно. На видеоплатформе существует два типа контента:

  • VoD (Video on Demand) — видео по запросу, которое уже полностью готово к просмотру;

  • Live–видео, которое генерируется реалтайм и ещё не завершилось (видеостримы).

VoD-контент двигается примерно по следующему маршруту:

Схема раздачи VoD-контента через видеоплатформу OzonСхема раздачи VoD-контента через видеоплатформу Ozon

  1. Пользователь со своим медиаконтентом отправляется к нам на uploader и пробует загрузить некий файл. На данном этапе мы со стороны видеоплатформы проверяем его на базовую валидность и пригодность (медиафайл ли это вообще, что у него внутри и т. п.). Если всё хорошо, отправляем его в хранилище для оригиналов видео (в нашем случае это S3). 

  2. После завершения загрузки видеоплатформа ставит в очередь обработки задачу на транскодинг принятого контента.

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

  4. Как только контент обработан и записан в хранилище, происходит его анализ на наличие запрещённого содержания: алкоголя, сигарет, порнографии, стороннего контента. 

  5. После апрува контент может быть отдан наружу для просмотра. Естественно, передача происходит через кэширующие слои и провайдера CDN.

В случае с live-стримингом контент течёт по пайплайну:

Схема live-стриминга через видеоплатформу OzonСхема live-стриминга через видеоплатформу Ozon

  1. Пользователь создаёт в админке стримера новый стрим, под который выделяется отдельный транскодер. Сам стример получает endpoint для стриминга на видеоплатформу.

  2. Через приложение для стриминга пользователь запускает поток на выделенный endpoint, который проксирует контент на выделенный транскодер. На стороне транскодера поток преобразуется в адаптивный и сегментируется для раздачи пользователям.

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

  4. Live-контент, так же как и VoD, раздаётся через кэширующие слои бэкенда Ozon и CDN провайдера.

Запихиваем всё в ленту

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

Причём если видеоотзывы и live-трансляции к моменту релиза Моментов (кек) уже существовали и комфортно себя чувствовали на видеоплатформе, то развлекательные ролики были новым типом и требовали к себе подхода в лучших традициях соцсетей.

Пройдёмся по контенту чуть более подробно.

Видеоотзывы

Покупатель, купив определенный товар, может записать о нём видео и загрузить в карточку товара. Это и есть видеоотзыв.

5f5d2923377ac73c2a18047cb0a8fc01.png

Live-трансляции

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

29fcc0f08fbdab5f88215205a05323da.png

Развлекательный видеоконтент

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

4582cdec7b3e69bae71ff4a41046c035.png

А что по технологиям?

На момент проектирования Ozon Моментов на видеоплатформе весь контент раздавался по HLS в адаптиве. Немного теории:

HLS

HTTP Live Streaming — протокол, придуманный корпорацией Apple, который позволяет предоставлять пользователю контент в виде нарезанных на кусочки (чанки) фрагментов, готовых к воспроизведению отдельно друг от друга. Может использоваться и для VoD, и для live-трансляции. Поддерживает возможность шифрования контента, разные качество, кодеки, языки, субтитры внутри одного HLS-потока. 

В состав HLS обычно входят:

  1. Мастер манифест, в котором указываются доступные качества контента, их параметры (разрешение, кодеки, количество каналов звука и так далее) и ссылки на скачивание этих качеств.
    Пример:

#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=779162,RESOLUTION=360x640,FRAME-RATE=30.000,CODECS="avc1.4d401e"
index-f1-v1.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1428476,RESOLUTION=540x960,FRAME-RATE=30.000,CODECS="avc1.4d401f"
index-f2-v1.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2523337,RESOLUTION=720x1280,FRAME-RATE=30.000,CODECS="avc1.4d401f"
index-f3-v1.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=5480232,RESOLUTION=1080x1920,FRAME-RATE=30.000,CODECS="avc1.4d4028"
index-f4-v1.m3u8
  1. Медиаплейлисты, в которых описаны все сегменты выбранного качества, параметры сегментов и ссылки на них.
    Пример:

#EXTM3U
#EXT-X-VERSION:7
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:0

#EXT-X-MAP:URI="stream_0_init.m4s"
#EXTINF:2.000000,
stream_0_seg_1.m4s

#EXTINF:2.000000,
stream_0_seg_2.m4s

#EXTINF:2.000000,
stream_0_seg_3.m4s

#EXTINF:0.633333,
stream_0_seg_4.m4s

#EXT-X-ENDLIST
  1. Медиасегменты, которые являются кусочками (чанками) медиаконтента.

Адаптивное вещание

Это способ раздачи контента для его воспроизведения без буферизаций на стороне клиента с учётом пропускной способности его интернет-соединения. Для адаптивного стриминга исходный медиапоток транскодируется в потоки разного размера и веса (например, в качество 1080р, 720р, 480р и 360р), которые режутся в одних и тех же местах на сегменты для синхронизации перехода с одного качества на другое. Картинка для наглядности:

d3839a0e5265904512dbf76ab74b91fc.png

Кодеки мы решили использовать самые стандартные:

Выбирали кодеки по простому правилу: чем распространённее, тем лучше. H.264 — это самый распространённый кодек, который может быть декодирован на 99,9% устройств с экраном на борту. AAC также считается стандартным для устройств, способных воспроизводить цифровые звуки.

Примечание: естественно, есть более навороченные кодеки с лучшей степенью сжатия (типа H.265 или Opus), но мы их не используем (пока что) в связи с тем, что есть необходимость свести к минимуму используемые типы контента.

В данном решении всё вроде бы выглядит неплохо:

  • адаптив для пользователей с разным каналом (будь они в центре Москвы на 10G или в деревне Средние Петушки на тонком канале от вышки времён «верните мне мой 2007-ой»);

  • протокол стриминга поддерживается если не каждым, то почти каждым плеером (Siemens ME45 не в счёт);

  • software-плееры практически не требуют каких-то сакральных знаний от клиентского разработчика (Plug and Play: добавил ссылку в плеер и — оно работает).

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

  • время старта будет страдать из-за большой вложенности адаптивных плейлистов HLS (плеер сначала идёт за адаптивным плейлистом со ссылками на медиаплейлисты, потом — за медиаплейлистом с медиасегментами, потом — за init-чанками с метаинформацией о потоках для инициализации плеера, и только после этого в плеер загружается сам закодированный кодеками медиа-payload);

  • время перехода от одного видео к другому будет страдать из-за проблем с предзагрузкой HLS (например, в iOS сделать это по-простому не получится в связи с правилами безопасности системы, которая не даст воспроизвести контент до тех пор, пока он не будет скачан полностью).

Как минимум эти вещи без каких-либо внушительных затрат (разработка своего протокола, написание своих плееров, обеспечение их корректной работы и пр.) делают прямое использование HLS в ленте неприемлемым.

Получается, что для реализации видеоленты в нужном нам виде требуется что-то простое, что-то универсальное и легко интегрируемое… И имя ему (внезапно) MP4.

MP4 — это формат медиаконтейнера, являющийся частью стандарта MPEG-4. Используется для упаковки цифровых видео- и аудиопотоков, субтитров, афиш и метаданных, которые определены группой специалистов MPEG. Как и большинство современных медиаконтейнеров, MPEG-4 предусматривает возможность показа видео через интернет. Вместе с файлом передаются метаданные, содержащие необходимую для вещания информацию. Контейнер позволяет упаковывать несколько видео- и аудиопотоков, а также субтитров.

МР4-файл состоит из частей (так называемых атомов), которые отвечают за определённое поведение MP4-демультиплексора (грубо говоря, распаковщика MP4-контейнера). Вот пример структуры файла:

67839883eab093a5ee66827c34bb89f4.png

Атомы имеют в своей структуре несколько важных полей:

01f1de44cb8e9155b3b395028089d747.png

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

  • TYPE — тип атома (например, упомянутые выше MOOV, FTYP, MDAT и пр.). Он указывается для корректного раскладывания вложенности атома.

  • LARGESIZE — размер атома, в случае если параметра SIZE не хватает и атом слишком большой. Для этого в поле SIZE проставляется 1, оно игнорируется — и используется LARGESIZE.

  • DATA — непосредственно данные атома.

Атомы бывают верхнеуровневыми и вложенными. Все атомы в рамках этой статьи описывать не имеет смысла (их достаточно много, при желании можно почитать о них тут), но основные три описать всё же стоит:

  • [FTYP] — атом, указывающий на то, что файл формата MP4 (грубо говоря — MIME type);

  • [MDAT] — атом, в котором находится payload для декодера;

  • [MOOV] — атом, в котором описываются все дорожки внутри MP4-файла и их параметры для корректного декодирования.

В файле эти атомы обычно стоят в следующем порядке:

[FTYP] [MDAT] [MOOV]

Это обусловлено следующим:

  • при транскодировании медиадекодер сразу записывает полезные данные в файл;

  • как только весь ролик корректно транскодируется, атом [MDAT] закрывается, а мультиплексор понимает выходные параметры файла (его медиапотоки, их длительность и пр.);

  • в конце дописывается атом [MOOV] с указанием структуры и информации о файле.

При такой структуре MP4 есть проблемы с воспроизведением — файл не может проигрываться по HTTP сразу. Объяснение простое: для воспроизведения нужно сначала инициализировать декодеры и только после этого закидывать payload в топку плеера. Но так как все параметры для декодера находятся в атоме [MOOV] (а он, напоминаю, располагается в самом конце файла), то клиенту нужно скачать весь файл, чтобы добраться до заветных настроек. В случае если файл достаточно большой (четырёх-часовая лекция о пользе тепличных огурцов и силе земли), то быстрый старт получить вам вряд ли удастся.

Примечание: в настоящее время некоторые плееры оснащены умным воспроизведением MP4-файлов и могут сами использовать range-запросы для получения атома [MOOV] отдельно (например, Google Chrome так умеет), но уповать на это, естественно, не надо.

Для того чтобы файл сразу был доступен для воспроизведения без полной загрузки, нужно перенести атом [MOOV] ближе к его началу. Хороший MP4-файл, пригодный к неистово быстрому воспроизведению по HTTP, имеет следующую структуру:

[FTYP] [MOOV] [MDAT]

Что ж, поехали делать модно и красиво!

Итак, что же надо изменить в текущей схеме, чтобы всё получилось? По большому счёту нужно научиться отдавать контент в двух видах (HLS и MP4), при этом максимально избегая дублирования контента в хранилище и поддерживая функциональность, которая уже есть в видеоплатформе Ozon.

Для этого нужно:

  • изменяем специальные настройки для транскодера, который будет генерировать MP4-файлы с атомом [MOOV] в хедере;

  • реализовать возможность раздачи таких файлов в виде HLS-адаптива;

  • подготовить основные и дополнительные файлы для визуально бесшовного воспроизведения;

  • сделать так, чтобы live-трансляции могли быть встроены в видеоленту.

Первое и главное, что нужно реализовать, — это раздача MP4-файлов в адаптивном HLS без дублирования контента. Серебряной пулей для этого будет модуль для Nginx от команды Kaltura. Основная фича этого модуля, нужная нам — это то, что он позволяет из обыкновенных MP4 нарезать на лету адаптив в HLS. Для этого нужно всего ничего:

  • развернуть Nginx с этим модулем и настроенным конфиг-файлом для раздачи нарезанного HLS (делаем Docker-контейнер с нужными версиями и конфигурациями, чтобы по нажатию кнопки поднимать N инстансов в модных кубернетесах);

  • указать upstream на что-то, откуда будут отдаваться MP4-файлы (это может быть локальное хранилище, но в нашем случае это S3);

  • указать место, откуда можно получать конфигурационные JSON-файлы, содержащие ссылки на MP4-файлы для нарезания в HLS (в нашем случае — отдельная ручка микросервиса для отдачи таких конфигурационных файлов по ID контента).

Теперь то, что получилось, нужно аккуратно вклинить в имеющийся флоу раздачи контента. Так как на походы до S3 за MP4-файлами тратится много времени, то наилучшим вариантом будет разместить ноды с nginx-vod-module максимально близко к хранилищу MP4-файлов и к проксирующей ноде. Также было бы неплохо максимально быстро ходить и до ручки микросервиса с конфигурационными файлами для контента. Таким образом, новая схема с nginx-vod-module получилась такая:

068dedebf7a41743286dd38dfc8c1b2f.png

Главная задача решена. Теперь нужно подготовить файлы для удобной раздачи в формате MP4, но чтобы при этом иметь возможность раздавать контент через nginx-vod-module. Для этого на транскодере генерируем MP4-файлы, в которых переписываем структуру, перенося атом [MOOV] в начало.

Примечание: nginx-vod-module умеет на лету преобразовывать файлы из обычных не подготовленных для раздачи по HTTP файлов MP4 уже с перенесённым атомом [MOOV]. Но зачем, если можно раздавать уже предподготовленные файлы напрямую из S3?

А что про стримы?

С генерацией VoD-контента вроде как всё хорошо и понятно. Но что делать с live-потоками? Стримы генерируются в адаптивный HLS, который не совсем удовлетворяет нас в задаче наполнения контентом ленты коротких видео. Завернуть их в MP4 нельзя, так как для этого нужен кастомный плеер (чего мы стараемся избежать). Вдобавок MP4 не поддерживает адаптивность, а с учётом качества покрытия и работы беспроводных и мобильных сетей адаптивный битрейт на трансляциях просто необходим. Ну и вдобавок хочется весь контент, поступающий в ленту, свести к одному типу, чтобы облегчить жизнь и разработчикам, и пользователям.

Новая цель — сделать из адаптивного живого HLS-потока простой и легковоспроизводимый MP4-файл.

Так как в случае с таким видеопревью перед нами не стоит задача отдавать наисвежайший контент, мы попробовали сделать фоновый воркер, который будет на стороне транскодера собирать из самых свежих медиасегментов (чанков) простой MP4-файл. Этот воркер перекладывает контент из живого HLS-потока в MP4-контейнер и отдаёт его как VoD-контент со всеми вытекающими плюшками. Убедившись, что такой вариант всех устраивает, нужно было сделать так, чтобы исключить возможность перезаписи файла во время его отдачи с транскодера и без дополнительных вызовов переупаковщиков.

Так как в FFmpeg (да, у нас, как почти у всех, для транскодирования видео используется именно он) есть возможность генерировать один набор полезных данных и укладывать их как душе угодно в рамках одной команды, то флоу внутри FFmpeg был немного допилен для генерации нескольких видов контента. А именно:

  • оставили HLS-генерацию как есть (она нас более чем устраивает);

  • добавили MP4-мультиплексор для одного из качеств, которое летит в HLS-адаптив, и укладывали его как есть (контент не требует повторного транскодинга);

  • также настроили MP4-мультиплексор для сегментирования входящего контента частями, генерируя новый MP4-файл в определённую дельту времени;

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

Схема до изменений выглядела следующим образом:

27a8b0aa03f6fe01c1cea3408e3ea80c.png

А так она выглядит после:

6d0316b5796e1f0422c31a782e9240e7.png

Таким образом, в рамках одного запуска FFmpeg мы генерируем привычный всем live HLS-адаптив, а рядышком складываем обновляемые сегменты в формате MP4 с возможностью быстрого просмотра по HTTP. Именно последние раздаются на клиент, что решает задачу интеграции стримов в текущую парадигму ленты коротких видео.

А что там по клиентам?

На клиентах для реализации проигрывания коротких видео всё достаточно стандартно:

  • на iOS — нативный AVPlayer от Apple;

  • на Android — ExoPlayer от Google.

Мы взяли базовые плееры по основным двум причинам:

  1. Они разработаны самими производителями мобильных платформ. Поэтому меньше вероятность того, что при обновлении ОС плеер упадёт на спину и скажет: «У меня лапки, ошибка »146%_callibration_ololo_dudulka», и вообще не трогайте меня».

  2. Скорость разработки. Так как разбираться в том, как работает кастомный плеер на С/С++ с использованием GStreamer под капотом и сооружением HW-ускоренного пайплайна, без лишней необходимости не было ни желания, ни возможности.

Примечание: вставлять код стандартных плееров я, конечно же, не буду, так как его легко найти в интернетах (для особо ленивых: iOS, Android).

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

  1. Возможность использования аппаратного ускорения. 

  2. Работа более чем одного плеера одновременно.

  3. Быстрый старт.

Рассмотрим их поближе.

1. Возможность аппаратного ускорения

Так как рендеринг видео — очень энерго- и ресурсозатратная вещь, то этот пункт особенно важен. Если декодировать и рендерить видео на CPU, то это скажется на улетающей в ноль батарее и (особенно на старых устройствах) тормозах при пользовании устройства. Чтобы разгрузить CPU для осуществления более нужных вычислений, необходимо использовать плееры с аппаратным ускорением декодирования.

iOS: AVPlayer автоматически использует аппаратное декодирование для H.264 из коробки.

Android: внутри ExoPlayer также существует поддержка аппаратного декодирования для H.264.

2. Работа более чем одного плеера одновременно 

Законом не запрещено нагенерировать сразу кучу плееров и использовать их когда и как вздумается. Но нужно понимать, что ресурс мобильного устройства не бесконечен, и при запуске 100500 плееров одновременно заставить их работать с аппаратным ускорением, увы, не получится. А я напомню: видео весьма затратно декодировать по ресурсам. Поэтому нужно научиться правильно переиспользовать плееры, чтобы не убивать драгоценное время на переинициализацию и прочее.

Видеолента в Ozon Моментах спроектирована так, что одновременно пользователь не может видеть больше чем три плеера:

  • предыдущий,

  • текущий,

  • следующий.

Пример:

d0a463b4e4256a00f97701b668a1c88f.gif

Эти три плеера идут «паровозиком». Как только происходит перелистывание видео вперёд и скрывается последний плеер, он становится вперёд. Таким же образом работает свайп на предыдущий видеоплеер.

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

3. Быстрый старт 

Медленный старт видео — это комплексная проблема. Замедляет время старта огромное количество факторов:  

  • криво собранный плеер (плеер инициализируется только при поступлении в него атома с метаинформацией);

  • тяжёлый контент (завышенные параметры качества для UGC);

  • проблемы сети (потери на мобильной сети + большое RTT = медленный старт).

Примечание: UGC (user generated content) — контент, созданный пользователями, например селфи-танцы с подругой или видео с пёсиком, который смешно чихает.

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

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

При изменении настроек буферизации тоже нужно найти золотую середину:

  • если сильно раздуть буфер плеера, то можно очень долго не начинать проигрывать контент и время старта просядет;

  • если сделать буфер минимальным, то время старта будет крайне мало, но пользователь будет постоянно отправляться в буферизацию (видеть тот самый бесящий кружок лоадера) и качество пользовательского опыта значительно просядет.

Буфер обычно указывается во времени.

iOS: В AVPlayer на устройствах Apple за это отвечает метод preferredForwardBufferDuration класса AVPlayerItem. Параметр задаётся в секундах. Apple в документации пишут, что все за вас сами сделают, если задать параметр 0 (или оставить нетронутым).

Пример:

let url = URL(string: "https://example/path/to/video/sample.mp4")
let asset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
playerItem.preferredForwardBufferDuration = newBufferDurationValue
let player = AVPlayer(playerItem: playerItem)

Android: В плеере на устройствах с операционкой от Google за это отвечает метод setBufferDurationsMs класса DefaultLoadControl. Параметр задаётся в миллисекундах. Он обладает параметрами:

  • minBufferMs — минимальный буфер (default: 50000);

  • maxBufferMs — максимальный буфер (default: 50000);

  • bufferForPlaybackMs — буфер для старта плейбэка (default: 2500);

  • bufferForPlaybackAfterRebufferMs — буфер для выхода из буферизации (default: 5000).

Пример:

DefaultLoadControl loadControl = new DefaultLoadControl
 .Builder()
 .setBufferDurationsMs(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs)
 .createDefaultLoadControl();

Покрутив эти параметры, старт плееров на обеих платформах стал гораздо бодрее, но этого всё ещё было недостаточно, так как при быстром пролистывании видеотайтлов были заметны тормоза и лаги. Полезли, как говорится, глубже…

Чтобы отсутствие видеопотока при быстром пролистывании не бросалось в глаза, мы делаем следующее:

  • на стороне бэкенда забираем из видеоролика первый фрейм, упаковываем его в JPEG и отправляем в хранилище (это достаточно быстрая операция, учитывая, что первый фрейм находится в самом начале и весь файл декодировать не нужно);

  • на стороне микросервиса, который занимается выдачей списка видео для мобильных клиентов, появляется дополнительное поле, в котором указана ссылка до этого JPEG-файла;

  • клиент при запросе выдачи ленты получает список видео с этими ссылками и заранее скачивает их, сохраняя у себя локально (они весят всего ничего, поэтому скачивание происходит достаточно быстро);

  • та часть экрана, в которой находится видеоплеер, перекрывается картинкой с первым кадром и уже готова для отображения контента пользователю, даже если видео вообще ещё даже не начинало скачиваться. По готовности плеера (он инициализирован и набрал буфер) картинка скрывается — и видео начинает проигрываться.

Это дало более приятный и бесшовный переход между элементами в ленте видео, но и этого оказалось недостаточно. Мы заметили неприятный эффект: даже при наличии первого кадра заглушкой контент мог долго лететь до клиента, что создавало «неловкие мгновения ожидания» проигрывания видео.

Для решения этой проблемы решили сделать prefetch-схему для заблаговременной загрузки контента. Идея проста: в момент загрузки N-ного ролика в фоне идёт загрузка N+1,2,3… — причём только первых нескольких секунд, а не видео целиком. Когда приходит очередь проигрывать следующий ролик, мы смотрим его сразу же, так как начало ролика уже загружено, а оставшийся контент докачивается по необходимости. Такой подход решил проблему ступора при проигрывании следующего видео в тех случаях, когда клиент не успел загрузить видео.

Как оно вышло в итоге

В итоге мы получили ленту коротких видео, состоящих из разнообразного контента: видеообзоров, развлекательных видео и live-трансляций. Местами — вполне себе залипательно.

Естественно, есть ещё вещи, которые нужно допиливать и тюнить, но это уже совсем другая история.

2e56a43d27aeacbe0092e870415a680d.gif

© Habrahabr.ru