diff --git a/Jellyfin.Plugin.JRay/Configuration/configPage.html b/Jellyfin.Plugin.JRay/Configuration/configPage.html
index f74e036..e94ad4c 100644
--- a/Jellyfin.Plugin.JRay/Configuration/configPage.html
+++ b/Jellyfin.Plugin.JRay/Configuration/configPage.html
@@ -29,10 +29,16 @@
Enable pause overlay
- Injects a small script into the web client that shows on-screen actors
- when playback is paused. Disabling this removes the injected script.
+ Shows the cast of the current scene when playback is paused. This
+ requires the
+
File Transformation
+ plugin, which rewrites the web client's index.html as it is served.
+ JRay never modifies index.html on disk, so there is no fallback if
+ that plugin is absent — only the overlay is affected, and every
+ other JRay feature keeps working.
+
Save
@@ -131,6 +137,32 @@
.replace(/>/g, '>').replace(/"/g, '"');
}
+ // ---- Dependency status ----
+ // Jellyfin cannot install a plugin's dependency, so the only thing
+ // that closes the gap is saying so where an admin can act on it.
+ function loadDependencyStatus() {
+ var el = document.querySelector('#JRayDependencyStatus');
+ return jrayApi('Status/Dependencies').then(function (status) {
+ el.style.display = '';
+ if (status.file_transformation_available) {
+ el.style.background = 'rgba(82,168,82,0.15)';
+ el.innerHTML = 'File Transformation detected. '
+ + 'The pause overlay is served by rewriting index.html as it is '
+ + 'sent, leaving the file on disk untouched.';
+ return;
+ }
+
+ el.style.background = 'rgba(220,160,60,0.18)';
+ el.innerHTML = 'File Transformation is not installed — the pause '
+ + 'overlay is disabled. Every other JRay feature is unaffected. '
+ + 'To enable it, add this repository in Dashboard → Plugins → '
+ + 'Repositories, then install "File Transformation" and restart: '
+ + '' + escapeHtml(status.file_transformation_manifest_url) + '';
+ }).catch(function () {
+ el.style.display = 'none';
+ });
+ }
+
// ---- Coverage ----
var JRayCoverColors = {
@@ -340,6 +372,7 @@
Dashboard.hideLoadingMsg();
});
+ loadDependencyStatus();
populateValueSelect();
loadRules();
loadCoverage();
diff --git a/Jellyfin.Plugin.JRay/Controllers/StatusController.cs b/Jellyfin.Plugin.JRay/Controllers/StatusController.cs
new file mode 100644
index 0000000..bf6e9b4
--- /dev/null
+++ b/Jellyfin.Plugin.JRay/Controllers/StatusController.cs
@@ -0,0 +1,38 @@
+using Jellyfin.Plugin.JRay.Models;
+using Jellyfin.Plugin.JRay.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Jellyfin.Plugin.JRay.Controllers;
+
+///
+/// Reports whether JRay's hard dependency on the File Transformation plugin is
+/// satisfied, for the configuration page to surface.
+///
+///
+/// Absent that plugin the pause overlay is disabled and every other JRay feature
+/// continues to work — so this is a status to display, not an error to raise.
+///
+[ApiController]
+[Route("Plugins/JRay/Status")]
+[Authorize(Roles = "Administrator")]
+// TRACES: JR-023 | PR-004
+public class StatusController : ControllerBase
+{
+ ///
+ /// Gets the dependency status.
+ ///
+ /// The dependency status.
+ [HttpGet("Dependencies")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public ActionResult GetDependencies()
+ {
+ return Ok(new DependencyStatus
+ {
+ FileTransformationAvailable = Plugin.FileTransformationAvailable,
+ OverlayEnabled = Plugin.OverlayEnabled,
+ FileTransformationManifestUrl = FileTransformationRegistration.ManifestUrl,
+ });
+ }
+}
diff --git a/Jellyfin.Plugin.JRay/Models/DependencyStatus.cs b/Jellyfin.Plugin.JRay/Models/DependencyStatus.cs
new file mode 100644
index 0000000..03e986d
--- /dev/null
+++ b/Jellyfin.Plugin.JRay/Models/DependencyStatus.cs
@@ -0,0 +1,37 @@
+using System.Text.Json.Serialization;
+
+namespace Jellyfin.Plugin.JRay.Models;
+
+///
+/// Whether JRay's one hard dependency is satisfied, and what to do if it is not.
+///
+///
+/// Jellyfin has no plugin dependency mechanism — a manifest cannot declare that
+/// another plugin is required, and nothing will install one. The gap is closed by
+/// telling the admin, on the page where they can act on it. A warning that exists
+/// only in the server log is one nobody reads.
+///
+public class DependencyStatus
+{
+ ///
+ /// Gets or sets a value indicating whether the File Transformation plugin was
+ /// found and JRay's overlay transformation registered with it.
+ ///
+ [JsonPropertyName("file_transformation_available")]
+ public bool FileTransformationAvailable { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the overlay is switched on in
+ /// configuration. It is served only when this and
+ /// hold.
+ ///
+ [JsonPropertyName("overlay_enabled")]
+ public bool OverlayEnabled { get; set; }
+
+ ///
+ /// Gets or sets the repository manifest URL an admin adds to install the
+ /// missing dependency.
+ ///
+ [JsonPropertyName("file_transformation_manifest_url")]
+ public string FileTransformationManifestUrl { get; set; } = string.Empty;
+}
diff --git a/Jellyfin.Plugin.JRay/Plugin.cs b/Jellyfin.Plugin.JRay/Plugin.cs
index 10e92be..4c1bd83 100644
--- a/Jellyfin.Plugin.JRay/Plugin.cs
+++ b/Jellyfin.Plugin.JRay/Plugin.cs
@@ -17,6 +17,7 @@ namespace Jellyfin.Plugin.JRay;
public class Plugin : BasePlugin, IHasWebPages
{
private readonly ILogger _logger;
+ private readonly bool _usingFileTransformation;
///
/// Initializes a new instance of the class.
@@ -30,8 +31,25 @@ public class Plugin : BasePlugin, IHasWebPages
Instance = this;
_logger = logger;
- WebClientPatchService.Apply(ApplicationPaths, Configuration.EnableOverlay, _logger);
- ConfigurationChanged += (_, _) => WebClientPatchService.Apply(ApplicationPaths, Configuration.EnableOverlay, _logger);
+ // The File Transformation plugin rewrites index.html as it is served.
+ // Registration is unconditional: the transformation itself checks
+ // EnableOverlay at request time, so toggling the setting takes effect
+ // without re-registering.
+ _usingFileTransformation = FileTransformationRegistration.TryRegister(_logger);
+
+ if (!_usingFileTransformation)
+ {
+ _logger.LogWarning(
+ "JRay: the pause overlay is disabled because the File Transformation plugin " +
+ "is not available. Install it from " +
+ "https://github.com/IAmParadox27/jellyfin-plugin-file-transformation. " +
+ "All other JRay features are unaffected.");
+ }
+
+ // JRay never injects into index.html. This only ever *removes* a patch
+ // left by an earlier version of JRay, identified by its own marker —
+ // see SPEC.md JR-021/JR-022.
+ WebClientPatchService.RemoveLegacyPatch(ApplicationPaths, _logger);
}
///
@@ -45,6 +63,20 @@ public class Plugin : BasePlugin, IHasWebPages
///
public static Plugin? Instance { get; private set; }
+ ///
+ /// Gets a value indicating whether the pause overlay should currently be
+ /// served. Read by the File Transformation callback at request time, so
+ /// toggling the setting takes effect without re-registering.
+ ///
+ internal static bool OverlayEnabled => Instance?.Configuration.EnableOverlay ?? false;
+
+ ///
+ /// Gets a value indicating whether the File Transformation plugin was found
+ /// at startup. When it was not, the overlay is disabled and every other JRay
+ /// feature continues to work.
+ ///
+ internal static bool FileTransformationAvailable => Instance?._usingFileTransformation ?? false;
+
///
public IEnumerable GetPages()
{
diff --git a/Jellyfin.Plugin.JRay/Services/FileTransformationRegistration.cs b/Jellyfin.Plugin.JRay/Services/FileTransformationRegistration.cs
new file mode 100644
index 0000000..99da27f
--- /dev/null
+++ b/Jellyfin.Plugin.JRay/Services/FileTransformationRegistration.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Loader;
+using System.Text.Json;
+using Jellyfin.Plugin.JRay.Models;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Plugin.JRay.Services;
+
+///
+/// Registers JRay's overlay script with the
+/// File Transformation
+/// plugin, which rewrites index.html as it is served instead of
+/// modifying the file on disk. This is non-destructive and composes with
+/// other plugins that patch the same file.
+///
+///
+/// File Transformation is referenced by reflection (rather than a NuGet
+/// package reference) so JRay still loads when it isn't installed. JRay must
+/// never bundle the assembly: a bundled copy would sit in a different
+/// AssemblyLoadContext from the real one, which is precisely the failure
+/// the reflection integration exists to avoid.
+///
+/// This is the mechanism JR-021 requires, but it does not by itself satisfy
+/// JR-021 — that requirement is a prohibition, and it stays unmet while
+/// can still write to disk.
+///
+// TRACES: JR-020, JR-023 | PR-004
+public static class FileTransformationRegistration
+{
+ ///
+ /// The marker comment written alongside the injected script tag, used to
+ /// keep the transformation idempotent.
+ ///
+ internal const string Marker = "";
+
+ ///
+ /// Repository manifest an admin adds to install the dependency. Surfaced on
+ /// the configuration page rather than only in the log, since that is where
+ /// it can be acted on.
+ ///
+ public const string ManifestUrl = "https://www.iamparadox.dev/jellyfin/plugins/manifest.json";
+
+ private const string PluginInterfaceTypeName = "Jellyfin.Plugin.FileTransformation.PluginInterface";
+ private const string RegisterMethodName = "RegisterTransformation";
+ private const string ScriptTag = "";
+ private const string BodyClose = "