VK Видео — один из крупнейших видеосервисов в России. В декабре 2025 года его ежемесячная аудитория достигла 81,5 млн пользователей. А в первую неделю января 2026 года среднесуточная аудитория VK Видео превысила 42 млн человек (без учёта просмотров на Smart TV и встраиваемых плееров, по данным Mediascope). 

Часть аудитории пользуется сервисом на устройствах Android. Для работы с видео на Android у VK есть единое решение — SDK OneVideo Player. Оно основано на библиотеке Media3 и содержит много улучшений и доработок в части проигрывания видео. Одна из таких оптимизаций — адаптация видео под viewport.

Меня зовут Егор Баженов. Я Android-разработчик в команде видеоплатформы VK. В этой статье я расскажу, как мы внедрили адаптацию под viewport в нашу библиотеку и каких результатов это позволило достичь.

Начнём с основ: viewport и разные разрешения видео

Viewport 

Viewport (вьюпорт) — часть сайта или приложения, которую пользователь видит сразу, без прокрутки. В контексте видео вьюпорт — область видимости видео на экране устройства. 

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

Качество видео

Эта метрика определяет, насколько хорошо и чётко выглядит картинка на экране. 

Рассмотрим все качества, представленные у нас в SDK, и возьмём стандарты для соотношения сторон 16:9.

Качество видео

Соотношение пикселей 

(ширина × высота)

Количество пикселей

144p

256 × 144

36 864

240p

426 × 240

102 240

360p

640 × 360

230 400

480p (SD)

854 × 480

409 920

480p (HD)

1 280 × 720

921 600

1080p (Full HD)

1 920 × 1 080

2 073 600

1440p (Quad HD)

2 560 × 1 440

3 686 400

2160p (4k Ultra HD)

3 840 × 2 160

8 294 400

4320p (8k Ultra HD)

7 680 × 4 320

33 177 600

Видим, что с ростом качества видео пикселей становится кратно больше. Это означает, что при переходе, например, с низкого разрешения (SD) на высокое (HD, 4K и выше) объём данных для передачи и отображения одного кадра увеличивается в несколько раз. Вместе с этим растёт и объём потребляемого трафика.

Рост связан с тем, что для хранения и передачи каждого пикселя требуется определённое количество бит. Чем больше пикселей, тем больше данных нужно передать за единицу времени. Это особенно важно при потоковой передаче видео. Если скорость интернета ограничена, пользователи могут столкнуться с задержками или ухудшением качества, так как пропускная способность сети снизилась.

Связь качества видео и вьюпорта 

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

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

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

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

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

От основ к нашей реализации

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

Она состоит из двух основных частей: 

  • передачи вьюпорта плееру;

  • выбора трека с учётом размера вьюпорта.

Передача вьюпорта плееру

Передача вьюпорта у нас реализована так:

  • при создании плеера мы также передаём view (вью) для воспроизведения — это может быть TextureView или SurfaceView;

  • в этот момент создаётся класс OneVideoSurfaceHolder на базе переданной вью. Внутри будет храниться значение вьюпорта на основании её размеров.

Класс OneVideoSurfaceHolder выглядит так:

package one.video.player

import android.util.Size
import android.view.SurfaceView
import android.view.TextureView
import android.view.View

class OneVideoViewHolder {

    constructor(surfaceView: SurfaceView) {
        attachView(surfaceView)
    }

    constructor(textureView: TextureView) {
        attachView(textureView)
    }

    @Volatile
    var viewportSize: Size? = null
        private set

    private fun attachView(view: View) {
        //Size 0x0 equals null viewportSize
        val setNonEmptyViewport: (View) -> Unit = { v ->
            viewportSize = if (v.width == 0 || v.height == 0) null else Size(v.width, v.height)
        }
        setNonEmptyViewport(view)
        view.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
            setNonEmptyViewport(v)
        }
    }
}

Выбор трека с учётом размера вьюпорта

Теперь разберём, как устроен учёт размера вьюпорта при выборе треков.

У нас есть своя реализация интерфейса exoplayer.trackselection.TrackSelection на базе AdaptiveTrackSelection, которая называется OneVideoTrackSelection. Она обновляет selectedIndex, отвечающий за выбор трека, на основании значений вьюпорта, которые мы передали в конструкторе.

На уровне кода это выглядит так:

Код
@OptIn(UnstableApi::class) package one.video.exo.trackselection

import android.util.Size
import androidx.annotation.OptIn
import androidx.media3.common.Format
import androidx.media3.common.TrackGroup
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.source.chunk.MediaChunk
import androidx.media3.exoplayer.source.chunk.MediaChunkIterator
import androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection
import androidx.media3.exoplayer.upstream.BandwidthMeter

class OneVideoTrackSelection1(
    group: TrackGroup,
    tracks: IntArray,
    bandwidthMeter: BandwidthMeter,
    private val viewportSizeProvider: () -> Size?,
) : AdaptiveTrackSelection(
    group,
    tracks,
    bandwidthMeter,
) {

    private val videoTracks: List<Format> by lazy {
        List(length) {
            getFormat(it)
        }
    }

    override fun updateSelectedTrack(
        playbackPositionUs: Long,
        bufferedDurationUs: Long,
        availableDurationUs: Long,
        queue: List<MediaChunk>,
        mediaChunkIterators: Array<out MediaChunkIterator>
    ) {
        super.updateSelectedTrack(
            playbackPositionUs,
            bufferedDurationUs,
            availableDurationUs,
            queue,
            mediaChunkIterators
        )
        updateSelectedIndexByViewportSize()
    }

    private fun updateSelectedIndexByViewportSize() {
        val viewportSize = viewportSizeProvider() ?: return
        val needToAdaptToViewport = !fitsFully(viewportSize, videoTracks[selectedIndex])
        if (needToAdaptToViewport) {
            videoTracks.forEachIndexed { index, format ->
                if (fitsFully(viewportSize, format)) {
                    selectedIndex = index
                    return
                }
            }
        }
    }

    private fun fitsFully(viewportSize: Size, format: Format) =
        format.width <= viewportSize.width && format.height <= viewportSize.height
}

Особенности интеграции

При интеграции этой реализации нужно учесть, что:

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

  • VideoTracks должны быть отсортированы по размеру фрейма. Это нужно проверить отдельно, так как дефолтная реализация завязана на bitrate. У нас сортировку треков по размеру фрейма гарантирует бэкенд. 

  • Код выше показывает самую базовую имплементацию. В реальности у нас много дополнительных параметров для настройки плеера. Но они напрямую не связаны с адаптацией под вьюпорт, поэтому мы их опустили в листингах кода.

Наши результаты адаптации видео под вьюпорт

Чтобы оценить эффективность интеграции изменений, перед выкаткой в прод мы провели A/B-эксперименты. По их результатам:

  • количество зависаний снизилось на 13%;

  • приложение стало потреблять меньше трафика;

  • время показа первого кадра ускорилось на 2%;

  • FTR и ANR приложения сократились.

Одновременно с этим выросли метрики стартов и досмотров видео.

Краткие выводы

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

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