Files
stream-cinema/CinemaJellyfin/CinemaServiceRegistrator.cs
Roman Vanicek 6e80e621c0
All checks were successful
continuous-integration/drone/push Build is passing
External subtitles work, internal are broken
2025-04-14 17:39:44 +00:00

158 lines
6.8 KiB
C#

using MediaBrowser.Controller;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace Jellyfin.Plugin.Cinema;
/// <summary>
/// Register Cinema services.
/// </summary>
///
public class CinemaServiceRegistrator : IPluginServiceRegistrator
{
/// <inheritdoc />
public void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost)
{
//serviceCollection.AddSingleton<IImageProvider, CinemaImageProvider>();
serviceCollection.AddHostedService<CinemaHost>();
// HACK: Replace IMediaSourceManager as CinemaMediaSourceProvider is not called soon enough while
// entering a media item page.
Type tMsm = typeof(IMediaSourceManager);
int count = serviceCollection.Count;
for (int i = 0; i < count; i++)
{
ServiceDescriptor a = serviceCollection[i];
if (a.ServiceType == tMsm && !a.IsKeyedService)
{
Type oldImplType = a.ImplementationType!;
serviceCollection.RemoveAt(i);
serviceCollection.AddSingleton(new CinemaInnerMediaSourceManager(oldImplType));
serviceCollection.AddSingleton(oldImplType, oldImplType);
// Replace IMediaSourceManager but also need direct access to the CinemaMediaSourceManager if it itself
// also gets replaced in a longer chain
serviceCollection.AddSingleton<CinemaMediaSourceManager>();
serviceCollection.AddSingleton<IMediaSourceManager>(x => x.GetRequiredService<CinemaMediaSourceManager>());
break;
}
}
// HACK: Replace ILibraryManager as Series.GetEpisodes is not virtual
Type tLm = typeof(ILibraryManager);
int count2 = serviceCollection.Count;
for (int i = 0; i < count2; i++)
{
ServiceDescriptor a = serviceCollection[i];
if (a.ServiceType == tLm && !a.IsKeyedService)
{
Type oldImplType = a.ImplementationType!;
serviceCollection.RemoveAt(i);
serviceCollection.AddSingleton(new CinemaInnerLibraryManager(oldImplType));
serviceCollection.AddSingleton(oldImplType, oldImplType);
// Replace IMediaSourceManager but also need direct access to the CinemaLibraryManager if it itself
// also gets replaced in a longer chain
serviceCollection.AddSingleton<CinemaLibraryManager>();
serviceCollection.AddSingleton<ILibraryManager>(x => x.GetRequiredService<CinemaLibraryManager>());
break;
}
}
// HACK: Extract values for some API calls that are otherwise inaccessible
serviceCollection.AddMvc(options =>
{
options.Conventions.Add(new MediaSourceIdExtractConvention());
});
}
sealed class MediaSourceIdExtractConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
ControllerModel? mediaInfo = application.Controllers.Where(x => x.ControllerName == "MediaInfo").FirstOrDefault();
ControllerModel? dynamicHls = application.Controllers.Where(x => x.ControllerName == "DynamicHls").FirstOrDefault();
ControllerModel? subtitle = application.Controllers.Where(x => x.ControllerName == "Subtitle").FirstOrDefault();
ActionModel? getPostedPlaybackInfo, getMasterHlsVideoPlaylist, getVariantHlsVideoPlaylist, getHlsVideoSegment, getSubtitleWithTicks;
if (mediaInfo == null
|| (getPostedPlaybackInfo = mediaInfo.Actions.Where(x => x.ActionName == "GetPostedPlaybackInfo").FirstOrDefault()) == null
|| dynamicHls == null
|| (getMasterHlsVideoPlaylist = dynamicHls.Actions.Where(x => x.ActionName == "GetMasterHlsVideoPlaylist").FirstOrDefault()) == null
|| (getVariantHlsVideoPlaylist = dynamicHls.Actions.Where(x => x.ActionName == "GetVariantHlsVideoPlaylist").FirstOrDefault()) == null
|| (getHlsVideoSegment = dynamicHls.Actions.Where(x => x.ActionName == "GetHlsVideoSegment").FirstOrDefault()) == null
|| subtitle == null
|| (getSubtitleWithTicks = subtitle.Actions.Where(x => x.ActionName == "GetSubtitleWithTicks").FirstOrDefault()) == null)
throw new InvalidOperationException("Failed to register for MediaSourceId extraction from the Jellyfin's MediaInfo controller.");
getPostedPlaybackInfo.Filters.Add(new GetPostedPlaybackInfoMediaSourceIdFilter());
// Note: All DynamicHls controller actions share the same parameter name so we can
// re-use the same filter for all
getMasterHlsVideoPlaylist.Filters.Add(new DynamicHlsMediaSourceIdFilter());
getVariantHlsVideoPlaylist.Filters.Add(new DynamicHlsMediaSourceIdFilter());
getHlsVideoSegment.Filters.Add(new DynamicHlsMediaSourceIdFilter());
// Slightly different parameter name
getSubtitleWithTicks.Filters.Add(new SubtitleMediaSourceIdFilter());
}
}
sealed class GetPostedPlaybackInfoMediaSourceIdFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
string? mediaSourceId;
if (!context.ActionArguments.TryGetValue("playbackInfoDto", out object? playbackInfoO)
|| (mediaSourceId = playbackInfoO!.GetType().GetProperty("MediaSourceId")?.GetValue(playbackInfoO) as string) == null)
{
// Fallback to the obsolete query argument
if (!context.ActionArguments.TryGetValue("mediaSourceId", out object? mediaSourceIdO)
|| (mediaSourceId = mediaSourceIdO as string) == null)
// It is optional in certain situations
return;
//throw new InvalidOperationException("Cannot extract MediaSourceId from the Jellyfin's action MediaInfo.GetPostedPlaybackInfo.");
}
context.HttpContext.Items.Add(CinemaMediaSourceManager.ContextItemsMediaSourceIdKey, mediaSourceId);
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
sealed class DynamicHlsMediaSourceIdFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
string? mediaSourceId;
if (!context.ActionArguments.TryGetValue("mediaSourceId", out object? mediaSourceIdO)
|| (mediaSourceId = mediaSourceIdO as string) == null)
throw new InvalidOperationException("Cannot extract MediaSourceId from the Jellyfin's controller DynamicHls.");
context.HttpContext.Items.Add(CinemaMediaSourceManager.ContextItemsMediaSourceIdKey, mediaSourceId);
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
sealed class SubtitleMediaSourceIdFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
string? mediaSourceId;
if (!context.ActionArguments.TryGetValue("routeMediaSourceId", out object? mediaSourceIdO)
|| (mediaSourceId = mediaSourceIdO as string) == null)
throw new InvalidOperationException("Cannot extract MediaSourceId from the Jellyfin's controller Subtitle.");
context.HttpContext.Items.Add(CinemaMediaSourceManager.ContextItemsMediaSourceIdKey, mediaSourceId);
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
}