Modo Picture in Picture en Flutter en Android, Implementación y consejos

Quiero mostrarte cómo implementar el modo Picture in Picture o imagen en imagen (como lo conozcas). 

Para eso estoy usando este plugin llamado floating, tal como puedes ver. Esta es su página oficial:

https://pub.dev/packages/floating/example

y más abajo hay un ejemplo, que —en mi humilde opinión— es un poquito enredado. Te muestro entonces mi propia implementación, que me parece más clara.

También, recuerda activar el modo PIP en Android:

android\app\src\main\AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:label="DL: Cursos/Libros"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">


        <activity
            android:name=".MainActivity"
            + android:supportsPictureInPicture="true"
            ***

Clase personalizada: CustomPIP, para el PIP

Todo comienza con una clase llamada CustomPIP, donde defino características como:

  1. el modo PIP.
  2. el modo body (modo completo).

En mi caso, por ejemplo, cuando estoy en la aplicación de academia y selecciono uno de los cursos ya adquiridos, ese curso completo es el "body".

Aquí activo el Floating, configuro sus opciones propias del widget, y dentro del StatefulWidget verifico si está habilitado, ya que no todos los sistemas Android lo permiten. Entonces hay que preguntar primero si está disponible.

class CustomPip extends StatefulWidget {
  final Widget body;
  final Widget pip;

  late PointADS point;

  final floating = Floating();

  activatePIP() {
    floating.enable(const ImmediatePiP());
  }

  CustomPip({super.key, required this.body, required this.pip});

  @override
  State<CustomPip> createState() => _CustomPipState();
}

class _CustomPipState extends State<CustomPip> {
  bool canUsePiP = false;

  @override
  void initState() {
    if (Platform.isAndroid) {
      // verifica si el pip esta disponible
      widget.floating.isPipAvailable.then((value) {
        canUsePiP = value;
        setState(() {});
      });
    }
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    if (Platform.isAndroid) {
      // verifica si el pip esta disponible
      return PiPSwitcher(
        childWhenDisabled: widget.body,
        childWhenEnabled: widget.pip,
      );
    }

    return widget.body;
  }
}

Esa información la guardo para usar más adelante. Luego activo el switcher (así lo llaman ellos), que básicamente establece qué es el body (la página completa) y cuál será el contenido en modo PIP.

Integración del PIP en la vista de detalle

Para usarlo es muy sencillo. Esta es la configuración que yo hice, y que te recomendaría.

En la página de detalle (la que estás viendo en pantalla), tengo todas las configuraciones normales. Lo que me interesa aquí es guardar una instancia del reproductor, que es lo que se colocará en el modo PIP.

Por ejemplo: aquí, si no activo el modo PIP correctamente, solo me aparece la opción para maximizar. Entonces tienes que dejarlo activado previamente.

Instanciando CustomPIP con tu vista
Aquí creo una instancia de CustomPIP, a la cual le paso todo el scaffold, es decir, la estructura completa de tu widget, que puede ser cualquier vista. También le paso el contenido que quiero conservar en el modo PIP:

  _initCustomPIP() {
    videoReproductor = VideoTutorialReproductor(
      paymentType: tutorialModel.payment?.type ?? '',
      tutorialSectionClassModel:
          tutorialModel
              .tutorialSectionsModel[appModel.selectedSectionIndex]
              .tutorialSectionClassesModel[appModel.selectedClassIndex],
      typeVideo: tutorialModel.type,
    );

    customPIP = CustomPip(
      pip: videoReproductor,
      body: Scaffold(
        ***
    );
  }

Luego creo esta propiedad (customPip) y la utilizo dentro del método build. Así de simple.

El widget se redibuja al entrar/salir en el modo PIP

Ya que cuando estamos en modo PIP, realmente, para este plugin no podemos modificar absolutamente nada de lo que se está reproduciendo. Se tiene que mantener sí o sí con lo que estaba haciendo antes, es decir, el estatus del widget.

Un punto clave: tienes que conservar el estado. Usualmente, cuando se cambia de modo PIP a completo (y viceversa), el widget tiende a redibujarse por completo, por lo que hay que implementar lógica para evitarlo.

Acepto recibir anuncios de interes sobre este Blog.

Te muestro como implementar el modo Pantalla en Pantalla desde una app en Flutter.

- Andrés Cruz

In english