diff --git a/CinemaJellyfin/CinemaFilterFolder.cs b/CinemaJellyfin/CinemaFilterFolder.cs index 7f515bb..68a2cae 100644 --- a/CinemaJellyfin/CinemaFilterFolder.cs +++ b/CinemaJellyfin/CinemaFilterFolder.cs @@ -330,6 +330,7 @@ public abstract class CinemaFilterFolder : Folder item.ProviderIds = new Dictionary(); item.ProviderIds[CinemaPlugin.CinemaProviderName] = ""; + // Indicate just HTTP CinemaMediaSourceManager and CinemaMediaSourceController will handle the rest item.Path = "https://a/b"; if (item is IHasArtist hasArtists) diff --git a/CinemaJellyfin/CinemaMediaSourceController.cs b/CinemaJellyfin/CinemaMediaSourceController.cs new file mode 100644 index 0000000..4e66662 --- /dev/null +++ b/CinemaJellyfin/CinemaMediaSourceController.cs @@ -0,0 +1,46 @@ +using System.ComponentModel.DataAnnotations; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Plugin.Cinema; + +/// +/// The artists controller. +/// +[Route("Cinema")] +[ApiController] +public class CinemaMediaSourceController : ControllerBase +{ + private readonly CinemaMediaSourceManager _mediaManager; + + public CinemaMediaSourceController(CinemaMediaSourceManager mediaManager) + { + _mediaManager = mediaManager; + } + + /// + /// Generates a download link using the given provider and its identifier. + /// + /// Item id. + /// External resource redirect. + /// Link not found. + [HttpGet("{provider}/{ident}/{name}/link")] + [ProducesResponseType(StatusCodes.Status302Found)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetExternalIdInfos( + [FromRoute, Required] string provider, + [FromRoute, Required] string ident, + [FromRoute, Optional] string? name, + CancellationToken cancel) + { + try + { + return Redirect((await _mediaManager.GenerateLink(provider, ident, name, cancel)).ToString()); + } + catch (Exception) + { + return NotFound(); + } + } +} \ No newline at end of file diff --git a/CinemaJellyfin/CinemaMediaSourceManager.cs b/CinemaJellyfin/CinemaMediaSourceManager.cs index 31ec2fe..a4b2be5 100644 --- a/CinemaJellyfin/CinemaMediaSourceManager.cs +++ b/CinemaJellyfin/CinemaMediaSourceManager.cs @@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Cinema.Webshare; using CinemaLib.API; +using MediaBrowser.Controller; namespace Jellyfin.Plugin.Cinema; @@ -28,15 +29,18 @@ public class CinemaMediaSourceManager : IMediaSourceManager private const double WebshareFreeBitrate = 300000 * 8; private static readonly TimeSpan VersionValidityTimeout = TimeSpan.FromMinutes(180); + private static readonly TimeSpan LinkValidityTimeout = VersionValidityTimeout; private static CinemaMediaSourceManager? _instance; private readonly IMediaSourceManager _inner; private readonly ILibraryManager _libraryManager; + private readonly IServerApplicationHost _host; private readonly IHttpContextAccessor _http; private readonly ConcurrentDictionary _videoVersions; + private readonly ConcurrentDictionary _links; - public CinemaMediaSourceManager(ICinemaInnerMediaSourceManager innerMediaSourceManager, ILibraryManager libraryManager, IServiceProvider svc, IHttpContextAccessor http) + public CinemaMediaSourceManager(ICinemaInnerMediaSourceManager innerMediaSourceManager, ILibraryManager libraryManager, IServerApplicationHost host, IServiceProvider svc, IHttpContextAccessor http) { if (innerMediaSourceManager == null || svc == null) throw new ArgumentNullException(); @@ -48,7 +52,9 @@ public class CinemaMediaSourceManager : IMediaSourceManager this._libraryManager = libraryManager; this._http = http; + this._host = host; this._videoVersions = new ConcurrentDictionary(); + this._links = new ConcurrentDictionary(); _instance = this; } @@ -152,17 +158,40 @@ public class CinemaMediaSourceManager : IMediaSourceManager public List GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User? user = null) { // Intercept for CinemaItems - if (item == null + HttpContext? ctx = _http.HttpContext; + if (ctx == null + || item == null || item.ProviderIds == null || !item.ProviderIds.ContainsKey(CinemaPlugin.CinemaProviderName) || string.IsNullOrEmpty(item.ExternalId)) return _inner.GetStaticMediaSources(item, enablePathSubstitution, user); + List result = GetPlaybackMediaSources(item, user, false, enablePathSubstitution, default).GetAwaiter().GetResult(); + + // HACK Prevent crash + if (result.Count == 0) + result.Append(new MediaSourceInfo() { MediaStreams = new List() }); + + return result; + } + + public async Task> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) + { + // Intercept for CinemaItems + HttpContext? ctx = _http.HttpContext; + if (ctx == null + || item == null + || item.ProviderIds == null + || !item.ProviderIds.ContainsKey(CinemaPlugin.CinemaProviderName) + || string.IsNullOrEmpty(item.ExternalId)) + return await _inner.GetPlaybackMediaSources(item, user, allowMediaProbe, enablePathSubstitution, cancellationToken); + List result = new List(); + Uri thisServerBaseUri = new Uri(_host.GetSmartApiUrl(ctx.Request)); switch (item) { case Video video: - VersionSetEntry ver = GetVersionSet(video, out BaseItem? videoPrimary, default).GetAwaiter().GetResult(); + VersionSetEntry ver = await GetVersionSet(video, out BaseItem? videoPrimary, default); if (ver.Versions != null) { IEnumerable