Преглед изворни кода

Trickplay playlist and image controller

nicknsy пре 2 година
родитељ
комит
b18d6bd356

+ 0 - 19
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -1028,25 +1028,6 @@ public class DynamicHlsController : BaseJellyfinApiController
             .ConfigureAwait(false);
     }
 
-    /// <summary>
-    /// Gets an image tiles playlist for trickplay.
-    /// </summary>
-    /// <param name="itemId">The item id.</param>
-    /// <param name="width">The width of a single tile.</param>
-    /// <param name="mediaSourceId">The media version id.</param>
-    /// <response code="200">Tiles stream returned.</response>
-    /// <returns>A <see cref="FileResult"/> containing the trickplay tiles file.</returns>
-    [HttpGet("Videos/{itemId}/tiles.m3u8")]
-    [ProducesResponseType(StatusCodes.Status200OK)]
-    [ProducesPlaylistFile]
-    public ActionResult GetTrickplayTilesHlsPlaylist(
-        [FromRoute, Required] Guid itemId,
-        [FromQuery, Required] int width,
-        [FromQuery, Required] string mediaSourceId)
-    {
-        return _dynamicHlsHelper.GetTilesHlsPlaylist(width, mediaSourceId);
-    }
-
     /// <summary>
     /// Gets a video stream using HTTP live streaming.
     /// </summary>

+ 177 - 0
Jellyfin.Api/Controllers/TrickplayController.cs

@@ -0,0 +1,177 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net.Mime;
+using System.Text;
+using System.Threading.Tasks;
+using Jellyfin.Api.Attributes;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Trickplay;
+using MediaBrowser.Model;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Api.Controllers;
+
+/// <summary>
+/// Trickplay controller.
+/// </summary>
+[Route("")]
+[Authorize]
+public class TrickplayController : BaseJellyfinApiController
+{
+    private readonly ILogger<TrickplayController> _logger;
+    private readonly IHttpContextAccessor _httpContextAccessor;
+    private readonly ILibraryManager _libraryManager;
+    private readonly ITrickplayManager _trickplayManager;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="TrickplayController"/> class.
+    /// </summary>
+    /// <param name="logger">Instance of the <see cref="ILogger{TrickplayController}"/> interface.</param>
+    /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
+    /// <param name="libraryManager">Instance of <see cref="ILibraryManager"/>.</param>
+    /// <param name="trickplayManager">Instance of <see cref="ITrickplayManager"/>.</param>
+    public TrickplayController(
+        ILogger<TrickplayController> logger,
+        IHttpContextAccessor httpContextAccessor,
+        ILibraryManager libraryManager,
+        ITrickplayManager trickplayManager)
+    {
+        _logger = logger;
+        _httpContextAccessor = httpContextAccessor;
+        _libraryManager = libraryManager;
+        _trickplayManager = trickplayManager;
+    }
+
+    /// <summary>
+    /// Gets an image tiles playlist for trickplay.
+    /// </summary>
+    /// <param name="itemId">The item id.</param>
+    /// <param name="width">The width of a single tile.</param>
+    /// <param name="mediaSourceId">The media version id, if using an alternate version.</param>
+    /// <response code="200">Tiles stream returned.</response>
+    /// <returns>A <see cref="FileResult"/> containing the trickplay tiles file.</returns>
+    [HttpGet("Videos/{itemId}/Trickplay/{width}/tiles.m3u8")]
+    [ProducesResponseType(StatusCodes.Status200OK)]
+    [ProducesPlaylistFile]
+    public ActionResult GetTrickplayHlsPlaylist(
+        [FromRoute, Required] Guid itemId,
+        [FromRoute, Required] int width,
+        [FromQuery] string? mediaSourceId)
+    {
+        return GetTrickplayPlaylistInternal(width, mediaSourceId ?? itemId.ToString("N"));
+    }
+
+    /// <summary>
+    /// Gets a trickplay tile grid image.
+    /// </summary>
+    /// <param name="itemId">The item id.</param>
+    /// <param name="width">The width of a single tile.</param>
+    /// <param name="index">The index of the desired tile grid.</param>
+    /// <param name="mediaSourceId">The media version id, if using an alternate version.</param>
+    /// <response code="200">Tiles image returned.</response>
+    /// <response code="200">Tiles image not found at specified index.</response>
+    /// <returns>A <see cref="FileResult"/> containing the trickplay tiles image.</returns>
+    [HttpGet("Videos/{itemId}/Trickplay/{width}/{index}.jpg")]
+    [ProducesResponseType(StatusCodes.Status200OK)]
+    [ProducesResponseType(StatusCodes.Status404NotFound)]
+    [ProducesImageFile]
+    public ActionResult GetTrickplayHlsPlaylist(
+        [FromRoute, Required] Guid itemId,
+        [FromRoute, Required] int width,
+        [FromRoute, Required] int index,
+        [FromQuery] string? mediaSourceId)
+    {
+        var item = _libraryManager.GetItemById(mediaSourceId ?? itemId.ToString("N"));
+        if (item is null)
+        {
+            return NotFound();
+        }
+
+        var path = _trickplayManager.GetTrickplayTilePath(item, width, index);
+        if (System.IO.File.Exists(path))
+        {
+            return PhysicalFile(path, MediaTypeNames.Image.Jpeg);
+        }
+
+        return NotFound();
+    }
+
+    private ActionResult GetTrickplayPlaylistInternal(int width, string mediaSourceId)
+    {
+        if (_httpContextAccessor.HttpContext is null)
+        {
+            throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext));
+        }
+
+        var tilesResolutions = _trickplayManager.GetTilesResolutions(Guid.Parse(mediaSourceId));
+        if (tilesResolutions is not null && tilesResolutions.ContainsKey(width))
+        {
+            var builder = new StringBuilder(128);
+            var tilesInfo = tilesResolutions[width];
+
+            if (tilesInfo.TileCount > 0)
+            {
+                const string urlFormat = "Trickplay/{0}/{1}.jpg?MediaSourceId={2}&api_key={3}";
+                const string decimalFormat = "{0:0.###}";
+
+                var resolution = tilesInfo.Width.ToString(CultureInfo.InvariantCulture) + "x" + tilesInfo.Height.ToString(CultureInfo.InvariantCulture);
+                var layout = tilesInfo.TileWidth.ToString(CultureInfo.InvariantCulture) + "x" + tilesInfo.TileHeight.ToString(CultureInfo.InvariantCulture);
+                var tilesPerGrid = tilesInfo.TileWidth * tilesInfo.TileHeight;
+                var tileDuration = (decimal)tilesInfo.Interval / 1000;
+                var tileGridCount = (int)Math.Ceiling((decimal)tilesInfo.TileCount / tilesPerGrid);
+
+                builder.AppendLine("#EXTM3U");
+                builder.Append("#EXT-X-TARGETDURATION:").AppendLine(tileGridCount.ToString(CultureInfo.InvariantCulture));
+                builder.AppendLine("#EXT-X-VERSION:7");
+                builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:1");
+                builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
+                builder.AppendLine("#EXT-X-IMAGES-ONLY");
+
+                for (int i = 0; i < tileGridCount; i++)
+                {
+                    // All tile grids before the last one must contain full amount of tiles.
+                    // The final grid will be 0 < count <= maxTiles
+                    if (i == tileGridCount - 1)
+                    {
+                        tilesPerGrid = tilesInfo.TileCount - (i * tilesPerGrid);
+                    }
+
+                    var infDuration = tileDuration * tilesPerGrid;
+                    var url = string.Format(
+                        CultureInfo.InvariantCulture,
+                        urlFormat,
+                        width.ToString(CultureInfo.InvariantCulture),
+                        i.ToString(CultureInfo.InvariantCulture),
+                        mediaSourceId,
+                        _httpContextAccessor.HttpContext.User.GetToken());
+
+                    // EXTINF
+                    builder.Append("#EXTINF:").Append(string.Format(CultureInfo.InvariantCulture, decimalFormat, infDuration))
+                        .AppendLine(",");
+
+                    // EXT-X-TILES
+                    builder.Append("#EXT-X-TILES:RESOLUTION=").Append(resolution).Append(",LAYOUT=").Append(layout).Append(",DURATION=")
+                        .AppendLine(string.Format(CultureInfo.InvariantCulture, decimalFormat, tileDuration));
+
+                    // URL
+                    builder.AppendLine(url);
+                }
+
+                builder.AppendLine("#EXT-X-ENDLIST");
+                return new FileContentResult(Encoding.UTF8.GetBytes(builder.ToString()), MimeTypes.GetMimeType("playlist.m3u8"));
+            }
+        }
+
+        return new FileContentResult(Array.Empty<byte>(), MimeTypes.GetMimeType("playlist.m3u8"));
+    }
+}

+ 1 - 76
Jellyfin.Api/Helpers/DynamicHlsHelper.cs

@@ -118,81 +118,6 @@ public class DynamicHlsHelper
             cancellationTokenSource).ConfigureAwait(false);
     }
 
-    /// <summary>
-    /// Get trickplay tiles hls playlist.
-    /// </summary>
-    /// <param name="width">The width of a single tile.</param>
-    /// <param name="mediaSourceId">The media version id.</param>
-    /// <returns>The resulting <see cref="ActionResult"/>.</returns>
-    public ActionResult GetTilesHlsPlaylist(int width, string mediaSourceId)
-    {
-        if (_httpContextAccessor.HttpContext is null)
-        {
-            throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext));
-        }
-
-        var tilesResolutions = _trickplayManager.GetTilesResolutions(Guid.Parse(mediaSourceId));
-        if (tilesResolutions is not null && tilesResolutions.ContainsKey(width))
-        {
-            var builder = new StringBuilder(128);
-            var tilesInfo = tilesResolutions[width];
-
-            if (tilesInfo.TileCount > 0)
-            {
-                const string urlFormat = "{0}/Trickplay/{1}/{2}.jpg?&api_key={3}";
-                const string decimalFormat = "{0:0.###}";
-
-                var resolution = tilesInfo.Width.ToString(CultureInfo.InvariantCulture) + "x" + tilesInfo.Height.ToString(CultureInfo.InvariantCulture);
-                var layout = tilesInfo.TileWidth.ToString(CultureInfo.InvariantCulture) + "x" + tilesInfo.TileHeight.ToString(CultureInfo.InvariantCulture);
-                var tilesPerGrid = tilesInfo.TileWidth * tilesInfo.TileHeight;
-                var tileDuration = (decimal)tilesInfo.Interval / 1000;
-                var tileGridCount = (int)Math.Ceiling((decimal)tilesInfo.TileCount / tilesPerGrid);
-
-                builder.AppendLine("#EXTM3U");
-                builder.Append("#EXT-X-TARGETDURATION:").AppendLine(tileGridCount.ToString(CultureInfo.InvariantCulture));
-                builder.AppendLine("#EXT-X-VERSION:7");
-                builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:1");
-                builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
-                builder.AppendLine("#EXT-X-IMAGES-ONLY");
-
-                for (int i = 0; i < tileGridCount; i++)
-                {
-                    // All tile grids before the last one must contain full amount of tiles.
-                    // The final grid will be 0 < count <= maxTiles
-                    if (i == tileGridCount - 1)
-                    {
-                        tilesPerGrid = tilesInfo.TileCount - (i * tilesPerGrid);
-                    }
-
-                    var infDuration = tileDuration * tilesPerGrid;
-                    var url = string.Format(
-                        CultureInfo.InvariantCulture,
-                        urlFormat,
-                        mediaSourceId,
-                        width.ToString(CultureInfo.InvariantCulture),
-                        i.ToString(CultureInfo.InvariantCulture),
-                        _httpContextAccessor.HttpContext.User.GetToken());
-
-                    // EXTINF
-                    builder.Append("#EXTINF:").Append(string.Format(CultureInfo.InvariantCulture, decimalFormat, infDuration))
-                        .AppendLine(",");
-
-                    // EXT-X-TILES
-                    builder.Append("#EXT-X-TILES:RESOLUTION=").Append(resolution).Append(",LAYOUT=").Append(layout).Append(",DURATION=")
-                        .AppendLine(string.Format(CultureInfo.InvariantCulture, decimalFormat, tileDuration));
-
-                    // URL
-                    builder.AppendLine(url);
-                }
-
-                builder.AppendLine("#EXT-X-ENDLIST");
-                return new FileContentResult(Encoding.UTF8.GetBytes(builder.ToString()), MimeTypes.GetMimeType("playlist.m3u8"));
-            }
-        }
-
-        return new FileContentResult(Array.Empty<byte>(), MimeTypes.GetMimeType("playlist.m3u8"));
-    }
-
     private async Task<ActionResult> GetMasterPlaylistInternal(
         StreamingRequestDto streamingRequest,
         bool isHeadRequest,
@@ -633,7 +558,7 @@ public class DynamicHlsHelper
 
             var url = string.Format(
                 CultureInfo.InvariantCulture,
-                "tiles.m3u8?Width={0}&MediaSourceId={1}&api_key={2}",
+                "Trickplay/{0}/tiles.m3u8?MediaSourceId={1}&api_key={2}",
                 width.ToString(CultureInfo.InvariantCulture),
                 state.Request.MediaSourceId,
                 user.GetToken());