Plugins
Sur la branche de développement, un service n'a plus besoin de vivre dans l'extracteur. Un plugin est un paquet séparé qui ajoute un StreamingService à l'application à l'exécution. SoundCloud, PeerTube, media.ccc.de et Bandcamp ont quitté l'extracteur de cette façon ; ils sont publiés comme plugins intégrés officiels, fournis avec PipePipe 5.6.0 et les versions suivantes. YouTube, BiliBili et NicoNico restent dans l'extracteur.
Les plugins tournent avec les permissions de l'application
Un plugin est du code téléchargé depuis un dépôt et exécuté dans PipePipe. Personne ne relit les plugins tiers : l'application demande une confirmation avant de les activer, et il ne faut installer que des plugins d'auteurs de confiance.
Le contrat
Un plugin implémente une seule interface, PipePipePlugin :
public interface PipePipePlugin {
int API_VERSION_MAJOR = 1;
int API_VERSION_MINOR = 0;
@Nonnull
StreamingService createService(int serviceId, @Nonnull PluginContext context);
}L'hôte instancie la classe nommée dans entryClass par un constructeur public sans argument et appelle createService une fois par processus. Le serviceId doit être transmis tel quel au constructeur de StreamingService. PluginContext donne au plugin son identifiant et un stockage PluginPreferences privé, où vivent les valeurs de ses réglages déclarés.
Ensuite, le plugin est un service ordinaire : des link handlers et des extracteurs derrière les mêmes méthodes de StreamingService qu'un service intégré, voir Ajouter un service.
Identifiants de service
ServiceList garde les services intégrés, YouTube (0), BiliBili (5) et NicoNico (6), et accepte les services enregistrés à l'exécution avec register(). Les identifiants 1 à 4 restent réservés aux anciens services intégrés : leurs plugins déclarent legacyServiceId, si bien que les abonnements, l'historique et les playlists enregistrés avec l'ancien identifiant continuent de marcher. Tout autre plugin reçoit un identifiant à partir de 1000 (FIRST_DYNAMIC_PLUGIN_ID), attribué par l'application et mémorisé pour chaque plugin.
plugin.json
Chaque paquet contient un manifeste, validé par PluginManifest :
| Champ | Requis | Rôle |
|---|---|---|
pluginId | oui | Identifiant en domaine inversé. Ne le changez jamais après une publication. |
name | oui | Doit être égal au nom du StreamingService. |
versionCode, versionName | oui | Augmentez versionCode à chaque publication. |
apiVersion | oui | major.minor de l'API des plugins. L'hôte charge la même version majeure avec une mineure qui ne dépasse pas la sienne ; l'API actuelle est 1.0. |
author | oui | Affiché dans la liste des plugins. |
entryClass | oui | Classe qui implémente PipePipePlugin. |
legacyServiceId | non | Seulement pour les anciens services intégrés. |
description | non | Affichée aux utilisateurs. |
icon | non | PNG ou WebP carré et monochrome, 512 px au plus ; l'application le teinte. |
settings | non | Réglages string ou boolean que l'application affiche pour le plugin. |
login | non | Connexion par cookie : une page https ouverte dans une WebView, le cookie à attendre et la clé de préférence qui le stocke. |
theme | non | Couleurs primaires light et dark appliquées quand le service est sélectionné. |
Paquets et dépôts
Un paquet est un fichier .ppp : un zip qui est à la fois un jar JVM (fichiers .class) et un conteneur dex Android (classes.dex), avec plugin.json à la racine. Les dépôts publient un index.json qui liste chaque paquet avec son SHA-256 et une signature Ed25519 de cette empreinte. L'application fixe la clé publique d'un plugin à la première installation et refuse les mises à jour signées avec une autre clé : l'auteur d'un plugin doit donc signer chaque publication avec la même clé. Les paquets sont limités à 32 Mo.
L'application connaît deux dépôts : PipePipe-builtin-plugins, l'officiel, toujours listé quand les plugins sont activés ; et awesome-pipepipe-plugins, une liste communautaire qu'on ajoute d'un seul geste. N'importe quel autre dépôt https peut être ajouté par son URL. Installer, mettre à jour ou supprimer un plugin prend effet après un redémarrage, parce qu'un chargeur de classes ne peut pas être déchargé.
Ce qu'un service peut déclarer
Le travail sur les plugins a ajouté à StreamingService et Stream quelques points d'extension que les services intégrés peuvent aussi utiliser :
getInfoCacheExpirationMillis(): la durée pendant laquelle les infos extraites restent dans le cache du client, une heure par défaut. À redéfinir quand les URL des flux expirent vite.getImageRequestHeaders(url): des en-têtes commeRefererque le client envoie pour charger une image appartenant au service. La méthode est appelée pour chaque image : gardez-la légère et sans accès réseau.Stream.setRequestHeaders()etStream.setCacheable(): les en-têtes que le lecteur doit envoyer à chaque requête média d'un flux, et si le lecteur peut le mettre en cache.ServiceInstances: une interface optionnelle pour les services servis par des instances interchangeables, comme PeerTube. L'application n'affiche un sélecteur d'instance que pour les services qui l'implémentent ; le service valide, normalise et enregistre lui-même ses instances.
Écrire un plugin
Partez de PipePipePluginTemplate. Il compile contre la branche main de l'extracteur via JitPack (ou contre un clone local avec extractorDir dans local.properties), demande le JDK 25 et le SDK Android (build-tools;37.0.0, platforms;android-36), et construit et signe le paquet :
./gradlew :tools:genKey # une fois ; gardez signing.key secret
./gradlew :tools:index # build/repo/<name>-<versionCode>.ppp et index.jsonLe paquet ne contient que votre propre code. L'extracteur, nanojson, jsoup, commons-lang3 et spotbugs-annotations sont fournis par l'application à l'exécution ; toute autre bibliothèque doit être embarquée sans entrer en conflit avec les classes de l'application, ou remplacée par votre propre code.
Pour publier, hébergez le .ppp à une URL https stable et ouvrez une pull request sur awesome-pipepipe-plugins avec l'entrée d'index et un lien vers les sources. Les plugins au code fermé ne sont pas acceptés. Les plugins intégrés cherchent des mainteneurs ; les questions vont dans la discussion #3080.
