Plugins
En la rama de desarrollo, un servicio ya no tiene que vivir en el extractor. Un plugin es un paquete aparte que añade un StreamingService a la app en tiempo de ejecución. SoundCloud, PeerTube, media.ccc.de y Bandcamp salieron así del extractor; se publican como los plugins integrados oficiales, que vienen con PipePipe 5.6.0 y posteriores. YouTube, BiliBili y NicoNico siguen en el extractor.
Los plugins se ejecutan con los permisos de la app
Un plugin es código descargado de un repositorio y ejecutado dentro de PipePipe. Nadie revisa los plugins de terceros, así que la app pide confirmación antes de activarlos y solo conviene instalar plugins de autores de confianza.
El contrato
Un plugin implementa una sola interfaz, PipePipePlugin:
public interface PipePipePlugin {
int API_VERSION_MAJOR = 1;
int API_VERSION_MINOR = 0;
@Nonnull
StreamingService createService(int serviceId, @Nonnull PluginContext context);
}El host instancia la clase indicada en entryClass con un constructor público sin argumentos y llama a createService una vez por proceso. El serviceId debe pasarse sin cambios al constructor de StreamingService. PluginContext da al plugin su id y un almacén PluginPreferences privado, donde viven los valores de sus ajustes declarados.
A partir de ahí, el plugin es un servicio normal: manejadores de enlaces y extractores detrás de los mismos métodos de StreamingService que uno integrado, consulta Añadir un servicio.
Identificadores de servicio
ServiceList guarda los servicios integrados, YouTube (0), BiliBili (5) y NicoNico (6), y acepta servicios registrados en tiempo de ejecución con register(). Los ids 1 a 4 siguen reservados para los antiguos servicios integrados: sus plugins declaran legacyServiceId, así que las suscripciones, el historial y las listas guardados con el id antiguo siguen funcionando. Cualquier otro plugin recibe un id a partir de 1000 (FIRST_DYNAMIC_PLUGIN_ID), asignado por la app y recordado para cada plugin.
plugin.json
Cada paquete lleva un manifiesto, validado por PluginManifest:
| Campo | Obligatorio | Significado |
|---|---|---|
pluginId | sí | Id en dominio inverso. No lo cambies nunca después de publicar. |
name | sí | Debe coincidir con el nombre del StreamingService. |
versionCode, versionName | sí | Aumenta versionCode en cada publicación. |
apiVersion | sí | major.minor de la API de plugins. El host carga la misma versión mayor con una menor que no supere la suya; la API actual es 1.0. |
author | sí | Se muestra en la lista de plugins. |
entryClass | sí | Clase que implementa PipePipePlugin. |
legacyServiceId | no | Solo para los antiguos servicios integrados. |
description | no | Se muestra a los usuarios. |
icon | no | PNG o WebP cuadrado y de un solo color, 512 px como máximo; la app lo tiñe. |
settings | no | Ajustes string o boolean que la app muestra para el plugin. |
login | no | Inicio de sesión por cookie: una página https abierta en una WebView, la cookie que se espera y la clave de preferencia que la guarda. |
theme | no | Colores primarios light y dark aplicados mientras el servicio está seleccionado. |
Paquetes y repositorios
Un paquete es un archivo .ppp: un zip que es a la vez un jar de JVM (archivos .class) y un contenedor dex de Android (classes.dex), con plugin.json en la raíz. Los repositorios publican un index.json que lista cada paquete con su SHA-256 y una firma Ed25519 de ese resumen. La app fija la clave pública de un plugin en la primera instalación y rechaza las actualizaciones firmadas con otra clave, así que el autor debe firmar cada publicación con la misma clave. Los paquetes tienen un límite de 32 MB.
La app conoce dos repositorios: PipePipe-builtin-plugins, el oficial, siempre listado mientras los plugins están activados; y awesome-pipepipe-plugins, una lista de la comunidad que se añade con un toque. Cualquier otro repositorio https se puede añadir por su URL. Instalar, actualizar o quitar un plugin tiene efecto tras reiniciar, porque un cargador de clases no se puede descargar.
Lo que un servicio puede declarar
El trabajo de plugins añadió a StreamingService y Stream algunos puntos de extensión que también pueden usar los servicios integrados:
getInfoCacheExpirationMillis(): cuánto tiempo quedan en la caché del cliente las infos extraídas, una hora por defecto. Redefínelo cuando las URL de los flujos caducan pronto.getImageRequestHeaders(url): cabeceras comoRefererque el cliente envía al cargar una imagen del servicio. Se llama para cada imagen, así que debe ser ligera y sin red.Stream.setRequestHeaders()yStream.setCacheable(): las cabeceras que el reproductor debe enviar en cada petición de medios de un flujo, y si puede guardarlo en caché.ServiceInstances: una interfaz opcional para servicios servidos por instancias intercambiables, como PeerTube. La app solo muestra un selector de instancia para los servicios que la implementan; el servicio valida, normaliza y guarda sus instancias por sí mismo.
Escribir un plugin
Parte de PipePipePluginTemplate. Compila contra la rama main del extractor a través de JitPack (o contra un clon local con extractorDir en local.properties), necesita el JDK 25 y el SDK de Android (build-tools;37.0.0, platforms;android-36), y construye y firma el paquete:
./gradlew :tools:genKey # una vez; mantén signing.key en secreto
./gradlew :tools:index # build/repo/<name>-<versionCode>.ppp e index.jsonEl paquete solo contiene tu propio código. El extractor, nanojson, jsoup, commons-lang3 y spotbugs-annotations los aporta la app en tiempo de ejecución; cualquier otra biblioteca debe ir incluida sin chocar con las clases de la app, o sustituirse por código propio.
Para publicar, aloja el .ppp en una URL https estable y abre una pull request en awesome-pipepipe-plugins con la entrada del índice y un enlace al código fuente. No se aceptan plugins de código cerrado. Los plugins integrados buscan mantenedores; las preguntas van a la discusión #3080.
