Skip to content

Plugins ​

On the development branch, a service no longer has to live in the extractor. A plugin is a separate package that adds one StreamingService to the app at runtime. SoundCloud, PeerTube, media.ccc.de, and Bandcamp left the extractor this way; they are published as the official built-in plugins, which ship with PipePipe 5.6.0 and later. YouTube, BiliBili, and NicoNico stay in the extractor.

Plugins run with the app's permissions

A plugin is code downloaded from a repository and run inside PipePipe. Nobody reviews third-party plugins, so the app asks before enabling them and users should only install plugins from authors they trust.

The contract ​

A plugin implements one interface, PipePipePlugin:

java
public interface PipePipePlugin {
    int API_VERSION_MAJOR = 1;
    int API_VERSION_MINOR = 0;

    @Nonnull
    StreamingService createService(int serviceId, @Nonnull PluginContext context);
}

The host instantiates the class named in entryClass through a public no-argument constructor and calls createService once per process. The serviceId must go to the StreamingService constructor unchanged. PluginContext gives the plugin its id and a private PluginPreferences store, where the values of its declared settings live.

From there, the plugin is an ordinary service: link handlers and extractors behind the same StreamingService methods as a built-in one, see Adding a service.

Service ids ​

ServiceList keeps the built-in services, YouTube (0), BiliBili (5), and NicoNico (6), and accepts services registered at runtime with register(). Ids 1 to 4 stay reserved for the former built-in services: their plugins declare legacyServiceId so subscriptions, history, and playlists saved with the old id keep working. Any other plugin gets an id from 1000 up (FIRST_DYNAMIC_PLUGIN_ID), assigned by the app and remembered per plugin.

plugin.json ​

Every package carries a manifest, validated by PluginManifest:

FieldRequiredMeaning
pluginIdyesReverse-domain id. Never change it after a release.
nameyesMust equal the StreamingService name.
versionCode, versionNameyesIncrease versionCode with every release.
apiVersionyesmajor.minor of the plugin API. The host loads the same major with a minor not above its own; the current API is 1.0.
authoryesShown in the plugin list.
entryClassyesClass implementing PipePipePlugin.
legacyServiceIdnoOnly for the former built-in services.
descriptionnoShown to users.
iconnoSquare single-colour PNG or WebP, at most 512 px; the app tints it.
settingsnostring or boolean settings the app renders for the plugin.
loginnoCookie login: an https page opened in a web view, the cookie to wait for, and the preference key that stores it.
themenolight and dark primary colours applied while the service is selected.

Packages and repositories ​

A package is a .ppp file: a zip that is a JVM jar (.class files) and an Android dex container (classes.dex) at the same time, with plugin.json at the root. Repositories publish an index.json listing each package with its SHA-256 and an Ed25519 signature of that digest. The app pins a plugin's public key on first install and rejects updates signed with another key, so a plugin author must sign every release with the same key. Packages are limited to 32 MB.

The app knows two repositories: PipePipe-builtin-plugins, the official one, always listed while plugins are enabled; and awesome-pipepipe-plugins, a community list users can add in one tap. Any other https repository can be added by URL. Installing, updating, or removing a plugin takes effect after a restart, because a class loader cannot be unloaded.

What a service can declare ​

The plugin work added a few hooks to StreamingService and Stream that built-in services can use as well:

  • getInfoCacheExpirationMillis(): how long extracted infos stay in the client cache, one hour by default. Override it when stream URLs expire quickly.
  • getImageRequestHeaders(url): headers such as Referer the client sends when loading an image owned by the service. It runs for every image, so keep it cheap and offline.
  • Stream.setRequestHeaders() and Stream.setCacheable(): headers the player must send for every media request of a stream, and whether the player may cache it.
  • ServiceInstances: an optional interface for services served by interchangeable instances, such as PeerTube. The app shows an instance selector only for services that implement it; the service validates, normalises, and stores the instances itself.

Writing a plugin ​

Start from PipePipePluginTemplate. It compiles against the extractor's main branch through JitPack (or a local checkout with extractorDir in local.properties), needs JDK 25 and the Android SDK (build-tools;37.0.0, platforms;android-36), and builds and signs the package:

sh
./gradlew :tools:genKey   # once; keep signing.key secret
./gradlew :tools:index    # build/repo/<name>-<versionCode>.ppp and index.json

The package holds only your own code. The extractor, nanojson, jsoup, commons-lang3, and spotbugs-annotations are provided by the app at runtime; any other library must be bundled without clashing with the app's classes, or replaced by your own code.

To publish, host the .ppp at a stable https URL and open a pull request to awesome-pipepipe-plugins with the index entry and a link to the source. Closed-source plugins are not accepted. The built-in plugins look for maintainers; questions go to discussion #3080.

Source references ​