File Transformation Integration Plan
This is a planned feature, not yet implemented.
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.
Current State
Manual injection required:
- Admin adds
<script src="/OpenWatchParty/ClientScript"></script>to Jellyfin’s Dashboard > General > Custom HTML - Browser hard refresh required after configuration
Target State
Automatic injection:
- On plugin load, detect and register with file-transformation plugin
- Transformation callback injects script tag into
index.htmlbefore</body> - Falls back gracefully to manual method if file-transformation is not installed
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 | Action |
|---|---|
plugins/jellyfin/OpenWatchParty/FileTransformationIntegration.cs |
Create |
plugins/jellyfin/OpenWatchParty/Plugin.cs |
Modify constructor |
docs/operations/installation.md |
Add automatic option |
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 Checklist
- Without file-transformation: verify manual method still works
- With file-transformation: verify automatic injection works
- Remove Custom HTML with file-transformation active: verify plugin works
- Uninstall file-transformation: verify graceful fallback
References
- jellyfin-plugin-file-transformation
- jellyfin-plugin-pages - Example integration