Changed media bar to use the HTTP Request approach for file transformation. Much more stable

This commit is contained in:
IAmParadox27 committed 2025-08-20 01:03:50 +01:00
1 parent bf39865742
commit 78c87c70f6
5 files changed
+158 -71

No files matched your search

@@ -1,32 +1,57 @@
using System.Reflection;
using System.Text.RegularExpressions;
using Jellyfin.Plugin.MediaBar.Model;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Plugin.MediaBar.Controllers;
[Route("[controller]")]
public class MediaBarController : ControllerBase
namespace Jellyfin.Plugin.MediaBar.Controllers
{
[HttpGet("{file}")]
public ActionResult GetFile([FromRoute] string file)
[Route("[controller]")]
public class MediaBarController : ControllerBase
{
Stream fileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Jellyfin.Plugin.MediaBar.Inject." + file);
string fileContents = new StreamReader(fileStream).ReadToEnd();
[HttpGet("{file}")]
public ActionResult GetFile([FromRoute] string file)
{
Stream fileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Jellyfin.Plugin.MediaBar.Inject." + file)!;
string fileContents = new StreamReader(fileStream).ReadToEnd();
string contentType = "text/plain";
string contentType = "text/plain";
if (Path.GetExtension(file) == ".js")
{
contentType = "text/javascript";
}
else if (Path.GetExtension(file) == ".css")
{
contentType = "text/css";
}
else if (Path.GetExtension(file) == ".html")
{
contentType = "text/html";
}
if (Path.GetExtension(file) == ".js")
{
contentType = "text/javascript";
}
else if (Path.GetExtension(file) == ".css")
{
contentType = "text/css";
}
else if (Path.GetExtension(file) == ".html")
{
contentType = "text/html";
}
return Content(fileContents, contentType);
return Content(fileContents, contentType);
}
[HttpPost("Patch/IndexHtml")]
public ActionResult PatchIndexHtml([FromBody] PatchRequestPayload payload)
{
Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(MediaBarPlugin).Namespace}.Inject.index.html")!;
using TextReader reader = new StreamReader(stream);
string regex = Regex.Replace(payload.Contents!, "(</body>)", $"{reader.ReadToEnd()}$1");
return Content(regex, "text/html");
}
[HttpPost("Patch/HomeHtmlChunk")]
public ActionResult PatchHomeHtmlChunk([FromBody] PatchRequestPayload payload)
{
Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(MediaBarPlugin).Namespace}.Inject.home-html.chunk.js")!;
using TextReader reader = new StreamReader(stream);
string regex = Regex.Replace(payload.Contents!, "(id=\"homeTab\" data-index=\"0\">)", $"$1{reader.ReadToEnd()}");
return Content(regex, "text/html");
}
}
}
@@ -5,11 +5,16 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Version>1.0.0</Version>
<Version>2.0.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Jellyfin.Plugin.FileTransformation" Version="1.2.2" />
<PackageReference Include="Jellyfin.Model" Version="10.10.5" />
<PackageReference Include="Jellyfin.Controller" Version="10.10.5" />
<PackageReference Include="Jellyfin.Common" Version="10.10.5" />
<PackageReference Include="Jellyfin.Data" Version="10.10.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
@@ -25,4 +30,7 @@
<EmbeddedResource Include="Inject\slideshowpure.js" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="Exists('C:\ProgramData\Jellyfin\Server\plugins\MediaBar')">
<Exec Command="xcopy $(OutDir) &quot;C:\ProgramData\Jellyfin\Server\plugins\MediaBar&quot; /y" />
</Target>
</Project>
+9 -47
View File
@@ -1,56 +1,18 @@
using System.Reflection;
using System.Text.RegularExpressions;
using Jellyfin.Plugin.FileTransformation.Controller;
using Jellyfin.Plugin.FileTransformation.Infrastructure;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.DependencyInjection;
namespace Jellyfin.Plugin.MediaBar;
public class MediaBarPlugin : BasePlugin<BasePluginConfiguration>
namespace Jellyfin.Plugin.MediaBar
{
public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448");
public class MediaBarPlugin : BasePlugin<BasePluginConfiguration>
{
public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448");
public override string Name => "Media Bar";
public override string Name => "Media Bar";
public MediaBarPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IServiceProvider serviceProvider) : base(applicationPaths, xmlSerializer)
{
IWebFileTransformationWriteService webFileTransformationWriteService = serviceProvider.GetRequiredService<IWebFileTransformationWriteService>();
webFileTransformationWriteService.AddTransformation("index.html", IndexHtml_Transformation);
webFileTransformationWriteService.AddTransformation("home-html\\.[a-zA-z0-9]+\\.chunk\\.js", HomeHtmlChunk_Transformation);
}
private void IndexHtml_Transformation(string path, Stream contents)
{
Stream? stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{GetType().Namespace}.Inject.index.html");
using TextReader reader = new StreamReader(stream);
using var textReader = new StreamReader(contents, null, true, -1, true);
var text = textReader.ReadToEnd();
var regex = Regex.Replace(text, "(</body>)", $"{reader.ReadToEnd()}$1");
contents.Seek(0, SeekOrigin.Begin);
using var textWriter = new StreamWriter(contents, null, -1, true);
textWriter.Write(regex);
}
private void HomeHtmlChunk_Transformation(string path, Stream contents)
{
Stream? stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{GetType().Namespace}.Inject.home-html.chunk.js");
using TextReader reader = new StreamReader(stream);
using var textReader = new StreamReader(contents, null, true, -1, true);
var text = textReader.ReadToEnd();
var regex = Regex.Replace(text, "(id=\"homeTab\" data-index=\"0\">)", $"$1{reader.ReadToEnd()}");
contents.Seek(0, SeekOrigin.Begin);
using var textWriter = new StreamWriter(contents, null, -1, true);
textWriter.Write(regex);
public MediaBarPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IServiceProvider serviceProvider) : base(applicationPaths, xmlSerializer)
{
}
}
}
@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.MediaBar.Model
{
public class PatchRequestPayload
{
[JsonPropertyName("contents")]
public string? Contents { get; set; }
}
}
@@ -0,0 +1,82 @@
using System.Net.Http.Headers;
using System.Reflection;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Jellyfin.Plugin.MediaBar.Services
{
public class StartupService : IScheduledTask
{
public string Name => "MediaBar Startup";
public string Key => "Jellyfin.Plugin.MediaBar.Startup";
public string Description => "Startup Service for MediaBar";
public string Category => "Startup Services";
private readonly IServerApplicationHost m_serverApplicationHost;
private readonly ILogger<MediaBarPlugin> m_logger;
public StartupService(IServerApplicationHost serverApplicationHost, ILogger<MediaBarPlugin> logger)
{
m_serverApplicationHost = serverApplicationHost;
m_logger = logger;
}
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
List<JObject> payloads = new List<JObject>();
{
JObject payload = new JObject();
payload.Add("id", "0dfac9d7-d898-4944-900b-1c1837707279");
payload.Add("fileNamePattern", "index.html");
payload.Add("tranformationEndpoint", "/MediaBar/Patch/IndexHtml");
payloads.Add(payload);
}
{
JObject payload = new JObject();
payload.Add("id", "e6d32b76-d54b-4946-b73e-c5c9c50575c9");
payload.Add("fileNamePattern", "home-html\\.[a-zA-z0-9]+\\.chunk\\.js");
payload.Add("tranformationEndpoint", "/MediaBar/Patch/HomeHtmlChunk");
payloads.Add(payload);
}
string? publishedServerUrl = m_serverApplicationHost.GetType()
.GetProperty("PublishedServerUrl", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(m_serverApplicationHost) as string;
foreach (JObject payload in payloads)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(publishedServerUrl ?? $"http://localhost:{m_serverApplicationHost.HttpPort}");
try
{
await client.PostAsync("/FileTransformation/RegisterTransformation",
new StringContent(payload.ToString(Formatting.None),
MediaTypeHeaderValue.Parse("application/json")));
}
catch (Exception ex)
{
m_logger.LogError(ex, $"Caught exception when attempting to register file transformation. Ensure you have `File Transformation` plugin installed on your server.");
return;
}
}
}
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
yield return new TaskTriggerInfo()
{
Type = TaskTriggerInfo.TriggerStartup
};
}
}
}