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.