File Transformation Integration

Overview

This document describes how to integrate OpenWatchParty with the jellyfin-plugin-file-transformation to automatically inject the client script into Jellyfin’s index.html, eliminating the manual Custom HTML configuration step.

How It Works

When Jellyfin loads OpenWatchParty, the plugin detects whether the file-transformation plugin is installed. If found, it registers a transformation that automatically injects the client <script> tag into index.html before </body>. If file-transformation is not installed, the admin can still inject the script manually via Dashboard > General > Custom HTML.

File-Transformation API

Registration Payload

var payload = new {
    id = new Guid(Plugin.PluginGuid),
    fileNamePattern = @"^index\.html$",
    callbackAssembly = typeof(FileTransformationIntegration).Assembly.FullName,
    callbackClass = typeof(FileTransformationIntegration).FullName,
    callbackMethod = nameof(TransformIndexHtml)
};

Registration via Reflection

Jellyfin loads plugins in separate AssemblyLoadContext, so direct type references are impossible. Use reflection:

Assembly? ftAssembly = AssemblyLoadContext.All
    .SelectMany(ctx => ctx.Assemblies)
    .FirstOrDefault(asm => asm.FullName?.Contains(".FileTransformation") ?? false);

if (ftAssembly != null)
{
    Type? pluginInterface = ftAssembly.GetType("Jellyfin.Plugin.FileTransformation.PluginInterface");
    MethodInfo? registerMethod = pluginInterface?.GetMethod("RegisterTransformation");
    registerMethod?.Invoke(null, new object?[] { payload });
}

Callback Signature

public static string TransformIndexHtml(object payload)
{
    string? contents = payload?.GetType()
        .GetProperty("contents")?
        .GetValue(payload)?
        .ToString();

    if (string.IsNullOrEmpty(contents) || contents.Contains("/OpenWatchParty/ClientScript"))
    {
        return contents ?? string.Empty;
    }

    int bodyEndIndex = contents.LastIndexOf("</body>", StringComparison.OrdinalIgnoreCase);
    if (bodyEndIndex >= 0)
    {
        return contents.Insert(bodyEndIndex, "<script src=\"/OpenWatchParty/ClientScript\"></script>\n");
    }

    return contents;
}

Implementation Files

File Purpose
src/plugins/jellyfin/OpenWatchParty/FileTransformationIntegration.cs Registration & transformation callback
src/plugins/jellyfin/OpenWatchParty/OpenWatchPartyPlugin.csproj Newtonsoft.Json dependency
docs/operations/installation.md Installation instructions (Option A)

Error Handling

Scenario Behavior
Plugin not installed Log info, fallback to manual
Incompatible version Log warning, fallback to manual
Exception during registration Log debug, fallback to manual
Script already present Return unchanged (idempotent)

Testing

Verify the following scenarios:

Scenario Expected Behavior
Without file-transformation installed Manual method works via Custom HTML
With file-transformation installed Script is automatically injected
Custom HTML removed, file-transformation active Plugin still works
File-transformation uninstalled Graceful fallback to manual method

References


Back to top

OpenWatchParty - Synchronized watch parties for Jellyfin