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

Попробуйте способ Compose
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 OS под управлением Android 14 (уровень API 34) или более поздней версии. Несмотря на множество сходств, существуют дополнительные соображения при использовании PiP на ТВ .

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

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

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

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

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

  • Измените размер окна PiP, используя функцию масштабирования «щипок-зум».

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

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

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

  • Ваше приложение может переключить видео в режим 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. Например, следующий код переключает активность в режим 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 Maps переключается в режим PiP, если пользователь нажимает кнопку «Домой» или «Недавние» во время навигации приложения. Вы можете отловить этот случай, переопределив onUserLeaveHint() :

Котлин

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

Ява

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

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

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

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

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

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

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

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

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

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

  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 до того, как система начнет выходной переход. Когда система собирается выйти из режима PiP, иерархия представлений активности выкладывается в соответствии с ее целевой конфигурацией (например, полный экран). Приложение может прикрепить прослушиватель изменения макета к своему корневому представлению или целевому представлению (например, представлению видеоплеера) для обнаружения события и обновления 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(true)
    .build())

Ява

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

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

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

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

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

С этим новым состоянием пользовательского интерфейса приложения, ориентированные на Android 15, наблюдают за вызовом обратного вызова Activity#onPictureInPictureUiStateChanged() с помощью isTransitioningToPip() сразу после начала анимации PiP. Существует множество элементов пользовательского интерфейса, которые не имеют значения для приложения, когда оно находится в режиме 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, и восстановите их, когда ваша активность снова станет полноэкранной.

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

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

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

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

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

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

В 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() см. Отображение карты Now Playing .

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

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

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

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