Добавляйте видео с помощью функции «картинка в картинке» (PiP)

Попробуйте способ создания
Jetpack Compose — рекомендуемый набор инструментов пользовательского интерфейса для Android. Узнайте, как поддерживать функцию «картинка в картинке» в Compose.

Начиная с Android 8.0 (уровень API 26), Android позволяет запускать действия в режиме «картинка в картинке» (PiP). PiP — это особый тип многооконного режима, который чаще всего используется для воспроизведения видео. Он позволяет пользователю смотреть видео в небольшом окне, закрепленном в углу экрана, одновременно перемещаясь между приложениями или просматривая контент на главном экране.

PiP использует многооконные API, доступные в Android 7.0, для обеспечения прикрепленного окна наложения видео. Чтобы добавить PiP в свое приложение, вам необходимо зарегистрировать свои действия, поддерживающие PiP, при необходимости переключить свое действие в режим PiP и убедиться, что элементы пользовательского интерфейса скрыты и воспроизведение видео продолжается, когда действие находится в режиме PiP.

Окно PiP появляется в самом верхнем слое экрана, в углу, выбранном системой.

PiP также поддерживается на совместимых устройствах с ОС Android TV под управлением Android 14 (уровень API 34) или более поздней версии. Несмотря на множество сходств, при использовании PiP на телевидении следует учитывать дополнительные моменты.

Как пользователи могут взаимодействовать с окном PiP

Пользователи могут перетащить окно PiP в другое место. Начиная с Android 12, пользователи также могут:

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

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

  • Скройте окно, перетащив его к левому или правому краю. Чтобы удалить окно, коснитесь видимой части спрятанного окна или перетащите его.

  • Измените размер окна PiP, используя масштабирование.

Ваше приложение контролирует, когда текущее действие переходит в режим PiP. Вот несколько примеров:

  • Действие может перейти в режим PiP, когда пользователь нажимает кнопку «Домой» или проводит вверх по экрану «Домой». Таким образом, Карты Google продолжают отображать маршруты, пока пользователь одновременно выполняет другое действие.

  • Ваше приложение может переместить видео в режим «картинка в картинке», когда пользователь возвращается от видео для просмотра другого контента.

  • Ваше приложение может переключить видео в режим PiP, пока пользователь смотрит конец эпизода контента. На главном экране отображается рекламная или сводная информация о следующем эпизоде ​​сериала.

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

Объявить поддержку PiP

По умолчанию система не поддерживает автоматическую поддержку PiP для приложений. Если вы хотите поддерживать PiP в своем приложении, зарегистрируйте свою видеоактивность в манифесте, установив android:supportsPictureInPicture значение true . Кроме того, укажите, что ваше действие обрабатывает изменения конфигурации макета, чтобы ваше действие не перезапускалось при изменении макета во время перехода в режим PiP.

<activity android:name="VideoActivity"
    android:supportsPictureInPicture="true"
    android:configChanges=
        "screenSize|smallestScreenSize|screenLayout|orientation"
    ...

Переключите свою деятельность на PiP

Начиная с Android 12, вы можете переключить свою активность в режим PiP, установив для флага setAutoEnterEnabled значение true . При использовании этого параметра действие автоматически переключается в режим PiP по мере необходимости без необходимости явного вызова enterPictureInPictureMode() в onUserLeaveHint . И это дает дополнительное преимущество, поскольку обеспечивает гораздо более плавные переходы. Подробную информацию см. в разделе «Как сделать переход в режим PiP более плавным с помощью навигации с помощью жестов» .

Если вы ориентируетесь на Android 11 или более раннюю версию, действие должно вызвать enterPictureInPictureMode() чтобы переключиться в режим PiP. Например, следующий код переключает действие в режим «картинка в картинке», когда пользователь нажимает специальную кнопку в пользовательском интерфейсе приложения:

Котлин

override fun onActionClicked(action: Action) {
    if (action.id.toInt() == R.id.lb_control_picture_in_picture) {
        activity?.enterPictureInPictureMode()
        return
    }
}

Ява

@Override
public void onActionClicked(Action action) {
    if (action.getId() == R.id.lb_control_picture_in_picture) {
        getActivity().enterPictureInPictureMode();
        return;
    }
    ...
}

Возможно, вы захотите включить логику, которая переключает действие в режим PiP вместо перехода в фоновый режим. Например, Карты Google переключаются в режим PiP, если пользователь нажимает кнопку «Домой» или «Недавние» во время навигации по приложению. Вы можете уловить этот случай, переопределив onUserLeaveHint() :

Котлин

override fun onUserLeaveHint() {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode()
    }
}

Ява

@Override
public void onUserLeaveHint () {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode();
    }
}

Рекомендуется: предоставьте пользователям безупречный опыт перехода в формате PiP.

В Android 12 были внесены значительные косметические улучшения в анимированные переходы между полноэкранным режимом и окнами PiP. Мы настоятельно рекомендуем внести все применимые изменения; как только вы это сделаете, эти изменения автоматически масштабируются на большие экраны, такие как складные устройства и планшеты, без каких-либо дополнительных действий.

Если ваше приложение не содержит соответствующих обновлений, переходы PiP по-прежнему работают, но анимация менее совершенна. Например, переход из полноэкранного режима в режим «картинка в картинке» может привести к исчезновению окна «картинка в картинке» во время перехода, прежде чем оно появится снова после завершения перехода.

Эти изменения заключаются в следующем.

  • Переход в режим «картинка в картинке» становится более плавным благодаря навигации с помощью жестов.
  • Установка правильного sourceRectHint для входа и выхода из режима PiP
  • Отключение плавного изменения размера для невидеоконтента

Обратитесь к образцу Android Kotlin PictureInPicture в качестве справочного материала по обеспечению более качественного перехода.

Сделайте переходы в режим PiP более плавными благодаря навигации с помощью жестов.

Начиная с Android 12, флаг setAutoEnterEnabled обеспечивает гораздо более плавную анимацию при переходе к видеоконтенту в режиме «картинка в картинке» с помощью навигации по жестам — например, при пролистывании вверх из полноэкранного режима.

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

  1. Используйте setAutoEnterEnabled для создания PictureInPictureParams.Builder :

    Котлин

    setPictureInPictureParams(PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build())
    

    Ява

    setPictureInPictureParams(new PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build());
    
  2. Вызовите setPictureInPictureParams с обновленным PictureInPictureParams заранее. Приложение не ожидает обратного вызова onUserLeaveHint (как это было бы в Android 11).

    Например, вы можете вызвать setPictureInPictureParams при самом первом воспроизведении и при любом последующем воспроизведении, если соотношение сторон изменено.

  3. Вызовите setAutoEnterEnabled(false) , но только по мере необходимости. Например, вы, вероятно, не захотите вводить PiP, если текущее воспроизведение находится в состоянии паузы.

Установите правильный sourceRectHint для входа в режим PiP и выхода из него.

Начиная с появления PiP в Android 8.0, setSourceRectHint указывал область действия, которая видна после перехода в режим «картинка в картинке» — например, границы просмотра видео в видеоплеере.

В Android 12 система использует sourceRectHint для реализации более плавной анимации как при входе в режим PiP, так и при выходе из него.

Чтобы правильно настроить sourceRectHint для входа и выхода из режима PiP:

  1. Создайте PictureInPictureParams используя правильные границы в качестве sourceRectHint . Мы рекомендуем также подключить к видеоплееру прослушиватель изменения макета:

    Котлин

    val mOnLayoutChangeListener =
    OnLayoutChangeListener { v: View?, oldLeft: Int,
            oldTop: Int, oldRight: Int, oldBottom: Int, newLeft: Int, newTop:
            Int, newRight: Int, newBottom: Int ->
        val sourceRectHint = Rect()
        mYourVideoView.getGlobalVisibleRect(sourceRectHint)
        val builder = PictureInPictureParams.Builder()
            .setSourceRectHint(sourceRectHint)
        setPictureInPictureParams(builder.build())
    }
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
    

    Ява

    private final View.OnLayoutChangeListener mOnLayoutChangeListener =
            (v, oldLeft, oldTop, oldRight, oldBottom, newLeft, newTop, newRight,
            newBottom) -> {
        final Rect sourceRectHint = new Rect();
        mYourVideoView.getGlobalVisibleRect(sourceRectHint);
        final PictureInPictureParams.Builder builder =
            new PictureInPictureParams.Builder()
                .setSourceRectHint(sourceRectHint);
        setPictureInPictureParams(builder.build());
    };
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener);
    
  2. При необходимости обновите sourceRectHint до того, как система начнет выходной переход. Когда система собирается выйти из режима «картинка в картинке», иерархия представлений действий выстраивается в соответствии с целевой конфигурацией (например, в полноэкранном режиме). Приложение может подключить прослушиватель изменения макета к своему корневому представлению или целевому представлению (например, представлению видеопроигрывателя), чтобы обнаружить событие и обновить sourceRectHint до начала анимации.

    Котлин

    // Listener is called immediately after the user exits PiP but before animating.
    playerView.addOnLayoutChangeListener { _, left, top, right, bottom,
                        oldLeft, oldTop, oldRight, oldBottom ->
        if (left != oldLeft
            || right != oldRight
            || top != oldTop
            || bottom != oldBottom) {
            // The playerView's bounds changed, update the source hint rect to
            // reflect its new bounds.
            val sourceRectHint = Rect()
            playerView.getGlobalVisibleRect(sourceRectHint)
            setPictureInPictureParams(
                PictureInPictureParams.Builder()
                    .setSourceRectHint(sourceRectHint)
                    .build()
            )
        }
    }
    
    

    Ява

    // Listener is called right after the user exits PiP but before animating.
    playerView.addOnLayoutChangeListener((v, left, top, right, bottom,
                        oldLeft, oldTop, oldRight, oldBottom) -> {
        if (left != oldLeft
            || right != oldRight
            || top != oldTop
            || bottom != oldBottom) {
            // The playerView's bounds changed, update the source hint rect to
            // reflect its new bounds.
            final Rect sourceRectHint = new Rect();
            playerView.getGlobalVisibleRect(sourceRectHint);
            setPictureInPictureParams(
                new PictureInPictureParams.Builder()
                    .setSourceRectHint(sourceRectHint)
                    .build());
        }
    });
    
    

Отключить плавное изменение размера для невидеоконтента

В Android 12 добавлен флаг setSeamlessResizeEnabled , который обеспечивает более плавную анимацию плавного затухания при изменении размера невидеоконтента в окне PiP. Раньше изменение размера невидеоконтента в окне PiP могло создавать резкие визуальные артефакты.

Чтобы отключить плавное изменение размера невидеоконтента:

Котлин

setPictureInPictureParams(PictureInPictureParams.Builder()
    .setSeamlessResizeEnabled(false)
    .build())

Ява

setPictureInPictureParams(new PictureInPictureParams.Builder()
    .setSeamlessResizeEnabled(false)
    .build());

Обработка пользовательского интерфейса во время PiP

Когда действие входит в режим «Картинка в картинке» (PiP) или выходит из него, система вызывает Activity.onPictureInPictureModeChanged() или Fragment.onPictureInPictureModeChanged() .

В Android 15 представлены изменения, которые обеспечивают еще более плавный переход при переходе в режим PiP. Это полезно для приложений, в которых элементы пользовательского интерфейса наложены поверх основного пользовательского интерфейса, который переходит в PiP.

Разработчики используют обратный вызов onPictureInPictureModeChanged() для определения логики, которая переключает видимость наложенных элементов пользовательского интерфейса. Этот обратный вызов запускается, когда анимация входа или выхода PiP завершена. Начиная с Android 15, класс PictureInPictureUiState включает новое состояние.

Благодаря этому новому состоянию пользовательского интерфейса приложения, предназначенные для Android 15, наблюдают, как обратный вызов Activity#onPictureInPictureUiStateChanged() вызывается с помощью isTransitioningToPip() как только начинается анимация PiP. Существует множество элементов пользовательского интерфейса, которые не имеют отношения к приложению, когда оно находится в режиме «картинка в картинке», например представления или макет, включающие такую ​​информацию, как предложения, предстоящие видео, рейтинги и заголовки. Когда приложение переходит в режим PiP, используйте обратный вызов onPictureInPictureUiStateChanged() чтобы скрыть эти элементы пользовательского интерфейса. Когда приложение переходит в полноэкранный режим из окна PiP, используйте обратный вызов onPictureInPictureModeChanged() , чтобы отобразить эти элементы, как показано в следующих примерах:

Котлин

override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
        if (pipState.isTransitioningToPip()) {
          // Hide UI elements.
        }
    }

Ява

@Override
public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {
        if (pipState.isTransitioningToPip()) {
          // Hide UI elements.
        }
    }

Котлин

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
        if (isInPictureInPictureMode) {
          // Unhide UI elements.
        }
    }

Ява

@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
        if (isInPictureInPictureMode) {
          // Unhide UI elements.
        }
    }

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

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

Если вашему приложению необходимо предоставить настраиваемые действия для PiP, см. раздел «Добавление элементов управления» на этой странице. Удалите другие элементы пользовательского интерфейса до того, как ваше действие перейдет в PiP, и восстановите их, когда ваше действие снова станет полноэкранным.

Добавить элементы управления

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

Если в приложении есть активный сеанс мультимедиа , появятся элементы управления «Воспроизведение», «Пауза», «Следующий» и «Предыдущий».

Вы также можете явно указать пользовательские действия, создав PictureInPictureParams с помощью PictureInPictureParams.Builder.setActions() перед входом в режим PiP, и передать параметры при входе в режим PiP с помощью enterPictureInPictureMode(android.app.PictureInPictureParams) или setPictureInPictureParams(android.app.PictureInPictureParams) . Будь осторожен. Если вы попытаетесь добавить больше, чем getMaxNumPictureInPictureActions() , вы получите только максимальное число.

Продолжение воспроизведения видео в режиме PiP

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

В Android 7.0 и более поздних версиях вам следует приостанавливать и возобновлять воспроизведение видео, когда система вызывает onStop() и onStart() вашего действия. Сделав это, вы сможете избежать необходимости проверять, находится ли ваше приложение в режиме PiP в onPause() и явно продолжать воспроизведение.

Если вы не установили для флага setAutoEnterEnabled значение true и вам нужно приостановить воспроизведение в реализации onPause() , проверьте режим PiP, вызвав isInPictureInPictureMode() и обработайте воспроизведение соответствующим образом. Например:

Котлин

override fun onPause() {
    super.onPause()
    // If called while in PiP mode, do not pause playback.
    if (isInPictureInPictureMode) {
        // Continue playback.
    } else {
        // Use existing playback logic for paused activity behavior.
    }
}

Ява

@Override
public void onPause() {
    // If called while in PiP mode, do not pause playback.
    if (isInPictureInPictureMode()) {
        // Continue playback.
        ...
    } else {
        // Use existing playback logic for paused activity behavior.
        ...
    }
}

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

Используйте одно действие воспроизведения для PiP

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

Чтобы гарантировать, что одно действие используется для запросов на воспроизведение видео и переключается в режим PiP или из него по мере необходимости, установите для android:launchMode действия значение singleTask в своем манифесте:

<activity android:name="VideoActivity"
    ...
    android:supportsPictureInPicture="true"
    android:launchMode="singleTask"
    ...

В своей деятельности переопределите onNewIntent() и обработайте новое видео, при необходимости остановив воспроизведение существующего видео.

Лучшие практики

PiP может быть отключен на устройствах с небольшим объемом оперативной памяти. Прежде чем ваше приложение будет использовать PiP, убедитесь, что оно доступно, вызвав hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) .

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

Когда действие находится в режиме PiP, по умолчанию оно не получает фокус ввода. Чтобы получать события ввода в режиме PiP, используйте MediaSession.setCallback() . Дополнительную информацию об использовании setCallback() см. в разделе «Отображение карты «Сейчас играет» .

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

Когда ваше приложение собирается перейти в режим «картинка в картинке», обратите внимание, что только самое верхнее действие входит в режим «картинка в картинке». В некоторых ситуациях, например, на многооконных устройствах, возможно, что действие, указанное ниже, теперь будет отображаться и снова станет видимым вместе с действием PiP. Вам следует обработать этот случай соответствующим образом, включая приведенное ниже действие по получению обратного вызова onResume() или onPause() . Также возможно, что пользователь может взаимодействовать с действием. Например, если у вас отображается действие списка видео и действие воспроизведения видео в режиме PiP, пользователь может выбрать новое видео из списка, и действие PiP должно обновиться соответствующим образом.

Дополнительный пример кода

Чтобы загрузить пример приложения, написанного на Kotlin, см. раздел «Пример Android PictureInPicture (Kotlin)» .