Extensibility
SpatialFin Plugin System
SpatialFin supports standalone JavaScript plugins that let you resolve and stream content from various web sources directly inside the app.
How Plugins Work
Plugins operate on a standalone QuickJS engine, securely resolving media metadata and direct stream URLs. SpatialFin plugins share a common architecture with Grayjay plugins, making it easy to port existing scripts. Note: While some plugins designed for Grayjay might work out of the box, they have not been thoroughly tested.
Building a Plugin: PeerTube Example
A basic plugin requires just two files: a manifest.json and a script.js. Let's look at how you'd build a plugin for PeerTube (a decentralized, federated video network).
1. The Manifest (manifest.json)
This file defines the plugin's metadata, ID, and capabilities.
{
"id": "f04c8f53-2bd1-4c6c-8517-5e3e6f9a0c12",
"name": "PeerTube",
"author": "Your Name",
"description": "A plugin to browse and stream PeerTube videos.",
"version": 1,
"scriptUrl": "script.js",
"minEngineVersion": 1,
"capabilities": ["search", "resolveVideoUrl"],
"settings": []
}
2. The Logic (script.js)
The script implements the required functions. For PeerTube, you can use the global sepiasearch.org API to discover content, and the origin instance's API to resolve streaming URLs (like HLS or MP4).
// 1. Searching for videos
source.search = async function(query) {
const response = http.GET("https://sepiasearch.org/api/v1/search/videos?search=" + encodeURIComponent(query), {});
const json = JSON.parse(response.body);
return json.data.map(item => ({
id: item.uuid,
pluginId: "f04c8f53-2bd1-4c6c-8517-5e3e6f9a0c12",
title: item.name,
author: item.account ? item.account.displayName : "Unknown",
description: item.description,
thumbnailUrl: item.thumbnailUrl,
videoUrl: item.url, // Points to the origin instance
durationMs: (item.duration || 0) * 1000
}));
};
// 2. Resolving the stream
source.resolveVideoUrl = async function(url) {
// Parse the host and uuid from the videoUrl
const match = url.match(/https?:\/\/([^/]+)\/videos\/(?:watch|embed)\/([^/?]+)/);
if (!match) return null;
const host = match[1];
const uuid = match[2];
// Call the origin instance for video details
const response = http.GET(`https://${host}/api/v1/videos/${uuid}`, {});
const json = JSON.parse(response.body);
// Find the HLS playlist (.m3u8)
if (json.streamingPlaylists && json.streamingPlaylists.length > 0) {
return {
url: json.streamingPlaylists[0].playlistUrl,
mimeType: "application/x-mpegURL"
};
}
return null; // Fallback logic omitted for brevity
};
3. Testing Locally
Place your manifest.json and script.js in a folder, then serve it locally using Python:
python3 -m http.server 8765
Then, inside the SpatialFin app, go to Settings > Plugins, click "Add Plugin", and enter your local IP address: http://<your-local-ip>:8765/manifest.json.