2
0
Эх сурвалжийг харах

Merge pull request #3811 from crobibero/api-cleanup

Clean up api-migration branch
Patrick Barron 4 жил өмнө
parent
commit
8385c54591
50 өөрчлөгдсөн 209 нэмэгдсэн , 5557 устгасан
  1. 0 4
      Emby.Server.Implementations/ApplicationHost.cs
  2. 1 2
      Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
  3. 4 4
      Jellyfin.Api/Controllers/AudioController.cs
  4. 1 1
      Jellyfin.Api/Controllers/BrandingController.cs
  5. 6 6
      Jellyfin.Api/Controllers/DlnaServerController.cs
  6. 2 2
      Jellyfin.Api/Controllers/DynamicHlsController.cs
  7. 2 2
      Jellyfin.Api/Controllers/HlsSegmentController.cs
  8. 14 14
      Jellyfin.Api/Controllers/ImageController.cs
  9. 2 2
      Jellyfin.Api/Controllers/ItemsController.cs
  10. 7 7
      Jellyfin.Api/Controllers/LibraryController.cs
  11. 2 2
      Jellyfin.Api/Controllers/LibraryStructureController.cs
  12. 3 4
      Jellyfin.Api/Controllers/LiveTvController.cs
  13. 7 8
      Jellyfin.Api/Controllers/MediaInfoController.cs
  14. 1 1
      Jellyfin.Api/Controllers/SessionController.cs
  15. 2 2
      Jellyfin.Api/Controllers/StartupController.cs
  16. 1 1
      Jellyfin.Api/Controllers/SubtitleController.cs
  17. 9 9
      Jellyfin.Api/Controllers/SyncPlayController.cs
  18. 2 2
      Jellyfin.Api/Controllers/SystemController.cs
  19. 5 31
      Jellyfin.Api/Controllers/TrailersController.cs
  20. 20 104
      Jellyfin.Api/Controllers/UniversalAudioController.cs
  21. 1 1
      Jellyfin.Api/Controllers/VideoHlsController.cs
  22. 3 3
      Jellyfin.Api/Controllers/VideosController.cs
  23. 0 1
      Jellyfin.Api/Helpers/RequestHelpers.cs
  24. 24 0
      Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs
  25. 18 12
      Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs
  26. 34 31
      Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs
  27. 29 33
      Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs
  28. 9 2
      Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
  29. 0 678
      MediaBrowser.Api/ApiEntryPoint.cs
  30. 0 416
      MediaBrowser.Api/BaseApiService.cs
  31. 0 13
      MediaBrowser.Api/IHasDtoOptions.cs
  32. 0 49
      MediaBrowser.Api/IHasItemFields.cs
  33. 0 24
      MediaBrowser.Api/MediaBrowser.Api.csproj
  34. 0 1008
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  35. 0 344
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  36. 0 1226
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  37. 0 126
      MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs
  38. 0 6
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  39. 0 442
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  40. 0 182
      MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
  41. 0 88
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  42. 0 44
      MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs
  43. 0 37
      MediaBrowser.Api/Playback/StreamRequest.cs
  44. 0 143
      MediaBrowser.Api/Playback/StreamState.cs
  45. 0 175
      MediaBrowser.Api/Playback/TranscodingThrottler.cs
  46. 0 23
      MediaBrowser.Api/Properties/AssemblyInfo.cs
  47. 0 26
      MediaBrowser.Api/TestService.cs
  48. 0 165
      MediaBrowser.Api/TranscodingJob.cs
  49. 0 6
      MediaBrowser.sln
  50. 0 45
      tests/Jellyfin.Api.Tests/GetPathValueTests.cs

+ 0 - 4
Emby.Server.Implementations/ApplicationHost.cs

@@ -46,7 +46,6 @@ using Emby.Server.Implementations.SyncPlay;
 using Emby.Server.Implementations.TV;
 using Emby.Server.Implementations.Updates;
 using Jellyfin.Api.Helpers;
-using MediaBrowser.Api;
 using MediaBrowser.Common;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
@@ -1032,9 +1031,6 @@ namespace Emby.Server.Implementations
                 }
             }
 
-            // Include composable parts in the Api assembly
-            yield return typeof(ApiEntryPoint).Assembly;
-
             // Include composable parts in the Model assembly
             yield return typeof(SystemInfo).Assembly;
 

+ 1 - 2
Jellyfin.Api/Auth/BaseAuthorizationHandler.cs

@@ -1,5 +1,4 @@
-using System.Net;
-using System.Security.Claims;
+using System.Security.Claims;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Data.Enums;
 using MediaBrowser.Common.Net;

+ 4 - 4
Jellyfin.Api/Controllers/AudioController.cs

@@ -144,10 +144,10 @@ namespace Jellyfin.Api.Controllers
         /// <param name="streamOptions">Optional. The streaming options.</param>
         /// <response code="200">Audio stream returned.</response>
         /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
-        [HttpGet("{itemId}/{stream=stream}.{container?}")]
-        [HttpGet("{itemId}/stream")]
-        [HttpHead("{itemId}/{stream=stream}.{container?}")]
-        [HttpHead("{itemId}/stream")]
+        [HttpGet("{itemId}/{stream=stream}.{container?}", Name = "GetAudioStreamByContainer")]
+        [HttpGet("{itemId}/stream", Name = "GetAudioStream")]
+        [HttpHead("{itemId}/{stream=stream}.{container?}", Name = "HeadAudioStreamByContainer")]
+        [HttpHead("{itemId}/stream", Name = "HeadAudioStream")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetAudioStream(
             [FromRoute] Guid itemId,

+ 1 - 1
Jellyfin.Api/Controllers/BrandingController.cs

@@ -44,7 +44,7 @@ namespace Jellyfin.Api.Controllers
         /// or a <see cref="NoContentResult"/> if the css is not configured.
         /// </returns>
         [HttpGet("Css")]
-        [HttpGet("Css.css")]
+        [HttpGet("Css.css", Name = "GetBrandingCss_2")]
         [Produces("text/css")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]

+ 6 - 6
Jellyfin.Api/Controllers/DlnaServerController.cs

@@ -42,8 +42,8 @@ namespace Jellyfin.Api.Controllers
         /// <param name="serverId">Server UUID.</param>
         /// <response code="200">Description xml returned.</response>
         /// <returns>An <see cref="OkResult"/> containing the description xml.</returns>
-        [HttpGet("{serverId}/description.xml")]
         [HttpGet("{serverId}/description")]
+        [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
         [Produces(XMLContentType)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult GetDescriptionXml([FromRoute] string serverId)
@@ -60,8 +60,8 @@ namespace Jellyfin.Api.Controllers
         /// <param name="serverId">Server UUID.</param>
         /// <response code="200">Dlna content directory returned.</response>
         /// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
-        [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml")]
         [HttpGet("{serverId}/ContentDirectory/ContentDirectory")]
+        [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_2")]
         [Produces(XMLContentType)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
@@ -75,8 +75,8 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="serverId">Server UUID.</param>
         /// <returns>Dlna media receiver registrar xml.</returns>
-        [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml")]
         [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar")]
+        [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_2")]
         [Produces(XMLContentType)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
@@ -90,8 +90,8 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="serverId">Server UUID.</param>
         /// <returns>Dlna media receiver registrar xml.</returns>
-        [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml")]
         [HttpGet("{serverId}/ConnectionManager/ConnectionManager")]
+        [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_2")]
         [Produces(XMLContentType)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
@@ -181,7 +181,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="serverId">Server UUID.</param>
         /// <param name="fileName">The icon filename.</param>
         /// <returns>Icon stream.</returns>
-        [HttpGet("{serverId}/icons/{filename}")]
+        [HttpGet("{serverId}/icons/{fileName}")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         public ActionResult GetIconId([FromRoute] string serverId, [FromRoute] string fileName)
         {
@@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <param name="fileName">The icon filename.</param>
         /// <returns>Icon stream.</returns>
-        [HttpGet("icons/{filename}")]
+        [HttpGet("icons/{fileName}")]
         public ActionResult GetIcon([FromRoute] string fileName)
         {
             return GetIconInternal(fileName);

+ 2 - 2
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -165,7 +165,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Video stream returned.</response>
         /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
         [HttpGet("/Videos/{itemId}/master.m3u8")]
-        [HttpHead("/Videos/{itemId}/master.m3u8")]
+        [HttpHead("/Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetMasterHlsVideoPlaylist(
             [FromRoute] Guid itemId,
@@ -335,7 +335,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Audio stream returned.</response>
         /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
         [HttpGet("/Audio/{itemId}/master.m3u8")]
-        [HttpHead("/Audio/{itemId}/master.m3u8")]
+        [HttpHead("/Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetMasterHlsAudioPlaylist(
             [FromRoute] Guid itemId,

+ 2 - 2
Jellyfin.Api/Controllers/HlsSegmentController.cs

@@ -50,8 +50,8 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="FileStreamResult"/> containing the audio stream.</returns>
         // Can't require authentication just yet due to seeing some requests come from Chrome without full query string
         // [Authenticated]
-        [HttpGet("/Audio/{itemId}/hls/{segmentId}/stream.mp3")]
-        [HttpGet("/Audio/{itemId}/hls/{segmentId}/stream.aac")]
+        [HttpGet("/Audio/{itemId}/hls/{segmentId}/stream.mp3", Name = "GetHlsAudioSegmentLegacyMp3")]
+        [HttpGet("/Audio/{itemId}/hls/{segmentId}/stream.aac", Name = "GetHlsAudioSegmentLegacyAac")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
         public ActionResult GetHlsAudioSegmentLegacy([FromRoute] string itemId, [FromRoute] string segmentId)

+ 14 - 14
Jellyfin.Api/Controllers/ImageController.cs

@@ -82,7 +82,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="403">User does not have permission to delete the image.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         [HttpPost("/Users/{userId}/Images/{imageType}")]
-        [HttpPost("/Users/{userId}/Images/{imageType}/{index?}")]
+        [HttpPost("/Users/{userId}/Images/{imageType}/{index?}", Name = "PostUserImage_2")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
@@ -128,7 +128,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="403">User does not have permission to delete the image.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         [HttpDelete("/Users/{userId}/Images/{itemType}")]
-        [HttpDelete("/Users/{userId}/Images/{itemType}/{index?}")]
+        [HttpDelete("/Users/{userId}/Images/{itemType}/{index?}", Name = "DeleteUserImage_2")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -167,7 +167,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="404">Item not found.</response>
         /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
         [HttpDelete("/Items/{itemId}/Images/{imageType}")]
-        [HttpDelete("/Items/{itemId}/Images/{imageType}/{imageIndex?}")]
+        [HttpDelete("/Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "DeleteItemImage_2")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="404">Item not found.</response>
         /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
         [HttpPost("/Items/{itemId}/Images/{imageType}")]
-        [HttpPost("/Items/{itemId}/Images/{imageType}/{imageIndex?}")]
+        [HttpPost("/Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "SetItemImage_2")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -342,9 +342,9 @@ namespace Jellyfin.Api.Controllers
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         [HttpGet("/Items/{itemId}/Images/{imageType}")]
-        [HttpHead("/Items/{itemId}/Images/{imageType}")]
-        [HttpGet("/Items/{itemId}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/Items/{itemId}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("/Items/{itemId}/Images/{imageType}", Name = "HeadItemImage")]
+        [HttpGet("/Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "GetItemImage_2")]
+        [HttpHead("/Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "HeadItemImage_2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetItemImage(
@@ -422,7 +422,7 @@ namespace Jellyfin.Api.Controllers
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         [HttpGet("/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}")]
-        [HttpHead("/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}")]
+        [HttpHead("/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}", Name = "HeadItemImage2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetItemImage2(
@@ -500,7 +500,7 @@ namespace Jellyfin.Api.Controllers
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         [HttpGet("/Artists/{name}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/Artists/{name}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("/Artists/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadArtistImage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetArtistImage(
@@ -578,7 +578,7 @@ namespace Jellyfin.Api.Controllers
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         [HttpGet("/Genres/{name}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/Genres/{name}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("/Genres/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadGenreImage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetGenreImage(
@@ -656,7 +656,7 @@ namespace Jellyfin.Api.Controllers
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         [HttpGet("/MusicGenres/{name}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/MusicGenres/{name}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("/MusicGenres/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadMusicGenreImage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetMusicGenreImage(
@@ -734,7 +734,7 @@ namespace Jellyfin.Api.Controllers
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         [HttpGet("/Persons/{name}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/Persons/{name}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("/Persons/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadPersonImage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetPersonImage(
@@ -812,7 +812,7 @@ namespace Jellyfin.Api.Controllers
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         [HttpGet("/Studios/{name}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/Studios/{name}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("/Studios/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadStudioImage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetStudioImage(
@@ -890,7 +890,7 @@ namespace Jellyfin.Api.Controllers
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         [HttpGet("/Users/{userId}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/Users/{userId}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("/Users/{userId}/Images/{imageType}/{imageIndex?}", Name = "HeadUserImage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetUserImage(

+ 2 - 2
Jellyfin.Api/Controllers/ItemsController.cs

@@ -30,7 +30,7 @@ namespace Jellyfin.Api.Controllers
         private readonly ILibraryManager _libraryManager;
         private readonly ILocalizationManager _localization;
         private readonly IDtoService _dtoService;
-        private readonly ILogger _logger;
+        private readonly ILogger<ItemsController> _logger;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ItemsController"/> class.
@@ -140,7 +140,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableImages">Optional, include image information in output.</param>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
         [HttpGet("/Items")]
-        [HttpGet("/Users/{uId}/Items")]
+        [HttpGet("/Users/{uId}/Items", Name = "GetItems_2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetItems(
             [FromRoute] Guid? uId,

+ 7 - 7
Jellyfin.Api/Controllers/LibraryController.cs

@@ -521,7 +521,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="tvdbId">The tvdbId.</param>
         /// <response code="204">Report success.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Library/Series/Added")]
+        [HttpPost("/Library/Series/Added", Name = "PostAddedSeries")]
         [HttpPost("/Library/Series/Updated")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -551,7 +551,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="imdbId">The imdbId.</param>
         /// <response code="204">Report success.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Library/Movies/Added")]
+        [HttpPost("/Library/Movies/Added", Name = "PostAddedMovies")]
         [HttpPost("/Library/Movies/Updated")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -679,12 +679,12 @@ namespace Jellyfin.Api.Controllers
         /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
         /// <response code="200">Similar items returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns>
-        [HttpGet("/Artists/{itemId}/Similar")]
+        [HttpGet("/Artists/{itemId}/Similar", Name = "GetSimilarArtists2")]
         [HttpGet("/Items/{itemId}/Similar")]
-        [HttpGet("/Albums/{itemId}/Similar")]
-        [HttpGet("/Shows/{itemId}/Similar")]
-        [HttpGet("/Movies/{itemId}/Similar")]
-        [HttpGet("/Trailers/{itemId}/Similar")]
+        [HttpGet("/Albums/{itemId}/Similar", Name = "GetSimilarAlbums2")]
+        [HttpGet("/Shows/{itemId}/Similar", Name = "GetSimilarShows2")]
+        [HttpGet("/Movies/{itemId}/Similar", Name = "GetSimilarMovies2")]
+        [HttpGet("/Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
             [FromRoute] Guid itemId,

+ 2 - 2
Jellyfin.Api/Controllers/LibraryStructureController.cs

@@ -249,7 +249,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult UpdateMediaPath(
             [FromQuery] string? name,
-            [FromQuery] MediaPathInfo? pathInfo)
+            [FromBody] MediaPathInfo? pathInfo)
         {
             if (string.IsNullOrWhiteSpace(name))
             {
@@ -320,7 +320,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult UpdateLibraryOptions(
             [FromQuery] string? id,
-            [FromQuery] LibraryOptions? libraryOptions)
+            [FromBody] LibraryOptions? libraryOptions)
         {
             var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(id);
 

+ 3 - 4
Jellyfin.Api/Controllers/LiveTvController.cs

@@ -23,7 +23,6 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Querying;
@@ -128,7 +127,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Channels")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
-        public ActionResult<QueryResult<BaseItemDto>> GetChannels(
+        public ActionResult<QueryResult<BaseItemDto>> GetLiveTvChannels(
             [FromQuery] ChannelType? type,
             [FromQuery] Guid? userId,
             [FromQuery] int? startIndex,
@@ -536,7 +535,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Programs")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
-        public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms(
+        public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLiveTvPrograms(
             [FromQuery] string? channelIds,
             [FromQuery] Guid? userId,
             [FromQuery] DateTime? minStartDate,
@@ -934,7 +933,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [Obsolete("This endpoint is obsolete.")]
-        public ActionResult<BaseItemDto> GetRecordingGroup([FromQuery] Guid? groupId)
+        public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute] Guid? groupId)
         {
             return NotFound();
         }

+ 7 - 8
Jellyfin.Api/Controllers/MediaInfoController.cs

@@ -7,6 +7,7 @@ using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
+using Jellyfin.Api.Models.MediaInfoDtos;
 using Jellyfin.Api.Models.VideoDtos;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
@@ -43,7 +44,7 @@ namespace Jellyfin.Api.Controllers
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IUserManager _userManager;
         private readonly IAuthorizationContext _authContext;
-        private readonly ILogger _logger;
+        private readonly ILogger<MediaInfoController> _logger;
         private readonly IServerConfigurationManager _serverConfigurationManager;
 
         /// <summary>
@@ -91,7 +92,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery] Guid? userId)
         {
-            return await GetPlaybackInfoInternal(itemId, userId, null, null).ConfigureAwait(false);
+            return await GetPlaybackInfoInternal(itemId, userId).ConfigureAwait(false);
         }
 
         /// <summary>
@@ -231,8 +232,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
         /// <param name="maxAudioChannels">The maximum number of audio channels.</param>
         /// <param name="itemId">The item id.</param>
-        /// <param name="deviceProfile">The device profile.</param>
-        /// <param name="directPlayProtocols">The direct play protocols. Default: <see cref="MediaProtocol.Http"/>.</param>
+        /// <param name="openLiveStreamDto">The open live stream dto.</param>
         /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
         /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
         /// <response code="200">Media source opened.</response>
@@ -249,8 +249,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? subtitleStreamIndex,
             [FromQuery] int? maxAudioChannels,
             [FromQuery] Guid? itemId,
-            [FromQuery] DeviceProfile? deviceProfile,
-            [FromQuery] MediaProtocol[] directPlayProtocols,
+            [FromBody] OpenLiveStreamDto openLiveStreamDto,
             [FromQuery] bool enableDirectPlay = true,
             [FromQuery] bool enableDirectStream = true)
         {
@@ -265,10 +264,10 @@ namespace Jellyfin.Api.Controllers
                 SubtitleStreamIndex = subtitleStreamIndex,
                 MaxAudioChannels = maxAudioChannels,
                 ItemId = itemId ?? Guid.Empty,
-                DeviceProfile = deviceProfile,
+                DeviceProfile = openLiveStreamDto?.DeviceProfile,
                 EnableDirectPlay = enableDirectPlay,
                 EnableDirectStream = enableDirectStream,
-                DirectPlayProtocols = directPlayProtocols ?? new[] { MediaProtocol.Http }
+                DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http }
             };
             return await OpenMediaSource(request).ConfigureAwait(false);
         }

+ 1 - 1
Jellyfin.Api/Controllers/SessionController.cs

@@ -241,7 +241,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="command">The command to send.</param>
         /// <response code="204">General command sent to session.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/{sessionId}/Command/{Command}")]
+        [HttpPost("/Sessions/{sessionId}/Command/{command}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult SendGeneralCommand(
             [FromRoute] string? sessionId,

+ 2 - 2
Jellyfin.Api/Controllers/StartupController.cs

@@ -106,7 +106,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Initial user retrieved.</response>
         /// <returns>The first user.</returns>
         [HttpGet("User")]
-        [HttpGet("FirstUser")]
+        [HttpGet("FirstUser", Name = "GetFirstUser_2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<StartupUserDto> GetFirstUser()
         {
@@ -131,7 +131,7 @@ namespace Jellyfin.Api.Controllers
         /// </returns>
         [HttpPost("User")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public async Task<ActionResult> UpdateUser([FromForm] StartupUserDto startupUserDto)
+        public async Task<ActionResult> UpdateStartupUser([FromForm] StartupUserDto startupUserDto)
         {
             var user = _userManager.Users.First();
 

+ 1 - 1
Jellyfin.Api/Controllers/SubtitleController.cs

@@ -182,7 +182,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">File returned.</response>
         /// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
         [HttpGet("/Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{format}")]
-        [HttpGet("/Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks?}/Stream.{format}")]
+        [HttpGet("/Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks?}/Stream.{format}", Name = "GetSubtitle_2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetSubtitle(
             [FromRoute, Required] Guid itemId,

+ 9 - 9
Jellyfin.Api/Controllers/SyncPlayController.cs

@@ -47,7 +47,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("New")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult CreateNewGroup()
+        public ActionResult SyncPlayCreateGroup()
         {
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             _syncPlayManager.NewGroup(currentSession, CancellationToken.None);
@@ -62,7 +62,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("Join")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult JoinGroup([FromQuery, Required] Guid groupId)
+        public ActionResult SyncPlayJoinGroup([FromQuery, Required] Guid groupId)
         {
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
 
@@ -82,7 +82,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("Leave")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult LeaveGroup()
+        public ActionResult SyncPlayLeaveGroup()
         {
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             _syncPlayManager.LeaveGroup(currentSession, CancellationToken.None);
@@ -97,7 +97,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="IEnumerable{GrouüInfoView}"/> containing the available SyncPlay groups.</returns>
         [HttpGet("List")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<IEnumerable<GroupInfoView>> GetSyncPlayGroups([FromQuery] Guid? filterItemId)
+        public ActionResult<IEnumerable<GroupInfoView>> SyncPlayGetGroups([FromQuery] Guid? filterItemId)
         {
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             return Ok(_syncPlayManager.ListGroups(currentSession, filterItemId.HasValue ? filterItemId.Value : Guid.Empty));
@@ -110,7 +110,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("Play")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult Play()
+        public ActionResult SyncPlayPlay()
         {
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             var syncPlayRequest = new PlaybackRequest()
@@ -128,7 +128,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("Pause")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult Pause()
+        public ActionResult SyncPlayPause()
         {
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             var syncPlayRequest = new PlaybackRequest()
@@ -147,7 +147,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("Seek")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult Seek([FromQuery] long positionTicks)
+        public ActionResult SyncPlaySeek([FromQuery] long positionTicks)
         {
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             var syncPlayRequest = new PlaybackRequest()
@@ -169,7 +169,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("Buffering")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult Buffering([FromQuery] DateTime when, [FromQuery] long positionTicks, [FromQuery] bool bufferingDone)
+        public ActionResult SyncPlayBuffering([FromQuery] DateTime when, [FromQuery] long positionTicks, [FromQuery] bool bufferingDone)
         {
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             var syncPlayRequest = new PlaybackRequest()
@@ -190,7 +190,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("Ping")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult Ping([FromQuery] double ping)
+        public ActionResult SyncPlayPing([FromQuery] double ping)
         {
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             var syncPlayRequest = new PlaybackRequest()

+ 2 - 2
Jellyfin.Api/Controllers/SystemController.cs

@@ -85,8 +85,8 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// <response code="200">Information retrieved.</response>
         /// <returns>The server name.</returns>
-        [HttpGet("Ping")]
-        [HttpPost("Ping")]
+        [HttpGet("Ping", Name = "GetPingSystem")]
+        [HttpPost("Ping", Name = "PostPingSystem")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<string> PingSystem()
         {

+ 5 - 31
Jellyfin.Api/Controllers/TrailersController.cs

@@ -1,14 +1,10 @@
 using System;
 using Jellyfin.Api.Constants;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Querying;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
 
 namespace Jellyfin.Api.Controllers
 {
@@ -18,32 +14,15 @@ namespace Jellyfin.Api.Controllers
     [Authorize(Policy = Policies.DefaultAuthorization)]
     public class TrailersController : BaseJellyfinApiController
     {
-        private readonly IUserManager _userManager;
-        private readonly ILibraryManager _libraryManager;
-        private readonly ILogger<ItemsController> _logger;
-        private readonly IDtoService _dtoService;
-        private readonly ILocalizationManager _localizationManager;
+        private readonly ItemsController _itemsController;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="TrailersController"/> class.
         /// </summary>
-        /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
-        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
-        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
-        /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
-        /// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param>
-        public TrailersController(
-            ILoggerFactory loggerFactory,
-            IUserManager userManager,
-            ILibraryManager libraryManager,
-            IDtoService dtoService,
-            ILocalizationManager localizationManager)
+        /// <param name="itemsController">Instance of <see cref="ItemsController"/>.</param>
+        public TrailersController(ItemsController itemsController)
         {
-            _userManager = userManager;
-            _libraryManager = libraryManager;
-            _dtoService = dtoService;
-            _localizationManager = localizationManager;
-            _logger = loggerFactory.CreateLogger<ItemsController>();
+            _itemsController = itemsController;
         }
 
         /// <summary>
@@ -214,12 +193,7 @@ namespace Jellyfin.Api.Controllers
         {
             var includeItemTypes = "Trailer";
 
-            return new ItemsController(
-                _userManager,
-                _libraryManager,
-                _localizationManager,
-                _dtoService,
-                _logger)
+            return _itemsController
                 .GetItems(
                     userId,
                     userId,

+ 20 - 104
Jellyfin.Api/Controllers/UniversalAudioController.cs

@@ -7,21 +7,12 @@ using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Api.Models.VideoDtos;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
 
 namespace Jellyfin.Api.Controllers
 {
@@ -30,72 +21,28 @@ namespace Jellyfin.Api.Controllers
     /// </summary>
     public class UniversalAudioController : BaseJellyfinApiController
     {
-        private readonly ILoggerFactory _loggerFactory;
-        private readonly IUserManager _userManager;
-        private readonly ILibraryManager _libraryManager;
-        private readonly IDeviceManager _deviceManager;
-        private readonly IDlnaManager _dlnaManager;
-        private readonly IMediaEncoder _mediaEncoder;
-        private readonly IFileSystem _fileSystem;
-        private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IAuthorizationContext _authorizationContext;
-        private readonly INetworkManager _networkManager;
-        private readonly IServerConfigurationManager _serverConfigurationManager;
-        private readonly TranscodingJobHelper _transcodingJobHelper;
-        private readonly IConfiguration _configuration;
-        private readonly ISubtitleEncoder _subtitleEncoder;
-        private readonly IHttpClientFactory _httpClientFactory;
+        private readonly MediaInfoController _mediaInfoController;
+        private readonly DynamicHlsController _dynamicHlsController;
+        private readonly AudioController _audioController;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="UniversalAudioController"/> class.
         /// </summary>
-        /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
-        /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
-        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
-        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
-        /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
-        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
-        /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
-        /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
-        /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
         /// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
-        /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
-        /// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> interface.</param>
-        /// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
-        /// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
-        /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
+        /// <param name="mediaInfoController">Instance of the <see cref="MediaInfoController"/>.</param>
+        /// <param name="dynamicHlsController">Instance of the <see cref="DynamicHlsController"/>.</param>
+        /// <param name="audioController">Instance of the <see cref="AudioController"/>.</param>
         public UniversalAudioController(
-            ILoggerFactory loggerFactory,
-            IServerConfigurationManager serverConfigurationManager,
-            IUserManager userManager,
-            ILibraryManager libraryManager,
-            IMediaEncoder mediaEncoder,
-            IFileSystem fileSystem,
-            IDlnaManager dlnaManager,
-            IDeviceManager deviceManager,
-            IMediaSourceManager mediaSourceManager,
             IAuthorizationContext authorizationContext,
-            INetworkManager networkManager,
-            TranscodingJobHelper transcodingJobHelper,
-            IConfiguration configuration,
-            ISubtitleEncoder subtitleEncoder,
-            IHttpClientFactory httpClientFactory)
+            MediaInfoController mediaInfoController,
+            DynamicHlsController dynamicHlsController,
+            AudioController audioController)
         {
-            _userManager = userManager;
-            _libraryManager = libraryManager;
-            _mediaEncoder = mediaEncoder;
-            _fileSystem = fileSystem;
-            _dlnaManager = dlnaManager;
-            _deviceManager = deviceManager;
-            _mediaSourceManager = mediaSourceManager;
             _authorizationContext = authorizationContext;
-            _networkManager = networkManager;
-            _loggerFactory = loggerFactory;
-            _serverConfigurationManager = serverConfigurationManager;
-            _transcodingJobHelper = transcodingJobHelper;
-            _configuration = configuration;
-            _subtitleEncoder = subtitleEncoder;
-            _httpClientFactory = httpClientFactory;
+            _mediaInfoController = mediaInfoController;
+            _dynamicHlsController = dynamicHlsController;
+            _audioController = audioController;
         }
 
         /// <summary>
@@ -122,9 +69,9 @@ namespace Jellyfin.Api.Controllers
         /// <response code="302">Redirected to remote audio stream.</response>
         /// <returns>A <see cref="Task"/> containing the audio file.</returns>
         [HttpGet("/Audio/{itemId}/universal")]
-        [HttpGet("/Audio/{itemId}/{universal=universal}.{container?}")]
-        [HttpHead("/Audio/{itemId}/universal")]
-        [HttpHead("/Audio/{itemId}/{universal=universal}.{container?}")]
+        [HttpGet("/Audio/{itemId}/{universal=universal}.{container?}", Name = "GetUniversalAudioStream_2")]
+        [HttpHead("/Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")]
+        [HttpHead("/Audio/{itemId}/{universal=universal}.{container?}", Name = "HeadUniversalAudioStream_2")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status302Found)]
@@ -151,8 +98,7 @@ namespace Jellyfin.Api.Controllers
             var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
             _authorizationContext.GetAuthorizationInfo(Request).DeviceId = deviceId;
 
-            var mediaInfoController = new MediaInfoController(_mediaSourceManager, _deviceManager, _libraryManager, _networkManager, _mediaEncoder, _userManager, _authorizationContext, _loggerFactory.CreateLogger<MediaInfoController>(), _serverConfigurationManager);
-            var playbackInfoResult = await mediaInfoController.GetPostedPlaybackInfo(
+            var playbackInfoResult = await _mediaInfoController.GetPostedPlaybackInfo(
                 itemId,
                 userId,
                 maxStreamingBitrate,
@@ -180,21 +126,6 @@ namespace Jellyfin.Api.Controllers
             var isStatic = mediaSource.SupportsDirectStream;
             if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
             {
-                var dynamicHlsController = new DynamicHlsController(
-                    _libraryManager,
-                    _userManager,
-                    _dlnaManager,
-                    _authorizationContext,
-                    _mediaSourceManager,
-                    _serverConfigurationManager,
-                    _mediaEncoder,
-                    _fileSystem,
-                    _subtitleEncoder,
-                    _configuration,
-                    _deviceManager,
-                    _transcodingJobHelper,
-                    _networkManager,
-                    _loggerFactory.CreateLogger<DynamicHlsController>());
                 var transcodingProfile = deviceProfile.TranscodingProfiles[0];
 
                 // hls segment container can only be mpegts or fmp4 per ffmpeg documentation
@@ -203,10 +134,10 @@ namespace Jellyfin.Api.Controllers
 
                 if (isHeadRequest)
                 {
-                    dynamicHlsController.Request.Method = HttpMethod.Head.Method;
+                    _dynamicHlsController.Request.Method = HttpMethod.Head.Method;
                 }
 
-                return await dynamicHlsController.GetMasterHlsAudioPlaylist(
+                return await _dynamicHlsController.GetMasterHlsAudioPlaylist(
                     itemId,
                     ".m3u8",
                     isStatic,
@@ -261,27 +192,12 @@ namespace Jellyfin.Api.Controllers
             }
             else
             {
-                var audioController = new AudioController(
-                    _dlnaManager,
-                    _userManager,
-                    _authorizationContext,
-                    _libraryManager,
-                    _mediaSourceManager,
-                    _serverConfigurationManager,
-                    _mediaEncoder,
-                    _fileSystem,
-                    _subtitleEncoder,
-                    _configuration,
-                    _deviceManager,
-                    _transcodingJobHelper,
-                    _httpClientFactory);
-
                 if (isHeadRequest)
                 {
-                    audioController.Request.Method = HttpMethod.Head.Method;
+                    _audioController.Request.Method = HttpMethod.Head.Method;
                 }
 
-                return await audioController.GetAudioStream(
+                return await _audioController.GetAudioStream(
                     itemId,
                     isStatic ? null : ("." + mediaSource.TranscodingContainer),
                     isStatic,

+ 1 - 1
Jellyfin.Api/Controllers/VideoHlsController.cs

@@ -49,7 +49,7 @@ namespace Jellyfin.Api.Controllers
         private readonly IConfiguration _configuration;
         private readonly IDeviceManager _deviceManager;
         private readonly TranscodingJobHelper _transcodingJobHelper;
-        private readonly ILogger _logger;
+        private readonly ILogger<VideoHlsController> _logger;
         private readonly EncodingOptions _encodingOptions;
 
         /// <summary>

+ 3 - 3
Jellyfin.Api/Controllers/VideosController.cs

@@ -316,10 +316,10 @@ namespace Jellyfin.Api.Controllers
         /// <param name="streamOptions">Optional. The streaming options.</param>
         /// <response code="200">Video stream returned.</response>
         /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
-        [HttpGet("{itemId}/{stream=stream}.{container?}")]
+        [HttpGet("{itemId}/{stream=stream}.{container?}", Name = "GetVideoStream_2")]
         [HttpGet("{itemId}/stream")]
-        [HttpHead("{itemId}/{stream=stream}.{container?}")]
-        [HttpHead("{itemId}/stream")]
+        [HttpHead("{itemId}/{stream=stream}.{container?}", Name = "HeadVideoStream_2")]
+        [HttpHead("{itemId}/stream", Name = "HeadVideoStream")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetVideoStream(
             [FromRoute] Guid itemId,

+ 0 - 1
Jellyfin.Api/Helpers/RequestHelpers.cs

@@ -5,7 +5,6 @@ using System.Net;
 using Jellyfin.Data.Enums;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 using Microsoft.AspNetCore.Http;
 

+ 24 - 0
Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs

@@ -0,0 +1,24 @@
+using System.Diagnostics.CodeAnalysis;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.MediaInfo;
+
+namespace Jellyfin.Api.Models.MediaInfoDtos
+{
+    /// <summary>
+    /// Open live stream dto.
+    /// </summary>
+    public class OpenLiveStreamDto
+    {
+        /// <summary>
+        /// Gets or sets the device profile.
+        /// </summary>
+        public DeviceProfile? DeviceProfile { get; set; }
+
+        /// <summary>
+        /// Gets or sets the device play protocols.
+        /// </summary>
+        [SuppressMessage("Microsoft.Performance", "CA1819:DontReturnArrays", MessageId = "DevicePlayProtocols", Justification = "Imported from ServiceStack")]
+        [SuppressMessage("Microsoft.Performance", "SA1011:ClosingBracketsSpace", MessageId = "DevicePlayProtocols", Justification = "Imported from ServiceStack")]
+        public MediaProtocol[]? DirectPlayProtocols { get; set; }
+    }
+}

+ 18 - 12
MediaBrowser.Api/System/ActivityLogWebSocketListener.cs → Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs

@@ -5,34 +5,35 @@ using MediaBrowser.Model.Activity;
 using MediaBrowser.Model.Events;
 using Microsoft.Extensions.Logging;
 
-namespace MediaBrowser.Api.System
+namespace Jellyfin.Api.WebSocketListeners
 {
     /// <summary>
     /// Class SessionInfoWebSocketListener.
     /// </summary>
     public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<ActivityLogEntry[], WebSocketListenerState>
     {
-        /// <summary>
-        /// Gets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        protected override string Name => "ActivityLogEntry";
-
         /// <summary>
         /// The _kernel.
         /// </summary>
         private readonly IActivityManager _activityManager;
 
-        public ActivityLogWebSocketListener(ILogger<ActivityLogWebSocketListener> logger, IActivityManager activityManager) : base(logger)
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ActivityLogWebSocketListener"/> class.
+        /// </summary>
+        /// <param name="logger">Instance of the <see cref="ILogger{ActivityLogWebSocketListener}"/> interface.</param>
+        /// <param name="activityManager">Instance of the <see cref="IActivityManager"/> interface.</param>
+        public ActivityLogWebSocketListener(ILogger<ActivityLogWebSocketListener> logger, IActivityManager activityManager)
+            : base(logger)
         {
             _activityManager = activityManager;
             _activityManager.EntryCreated += OnEntryCreated;
         }
 
-        private void OnEntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
-        {
-            SendData(true);
-        }
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        protected override string Name => "ActivityLogEntry";
 
         /// <summary>
         /// Gets the data to send.
@@ -50,5 +51,10 @@ namespace MediaBrowser.Api.System
 
             base.Dispose(dispose);
         }
+
+        private void OnEntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
+        {
+            SendData(true);
+        }
     }
 }

+ 34 - 31
MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs → Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs

@@ -6,7 +6,7 @@ using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 
-namespace MediaBrowser.Api.ScheduledTasks
+namespace Jellyfin.Api.WebSocketListeners
 {
     /// <summary>
     /// Class ScheduledTasksWebSocketListener.
@@ -17,42 +17,27 @@ namespace MediaBrowser.Api.ScheduledTasks
         /// Gets or sets the task manager.
         /// </summary>
         /// <value>The task manager.</value>
-        private ITaskManager TaskManager { get; set; }
+        private readonly ITaskManager _taskManager;
 
         /// <summary>
-        /// Gets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        protected override string Name => "ScheduledTasksInfo";
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ScheduledTasksWebSocketListener" /> class.
+        /// Initializes a new instance of the <see cref="ScheduledTasksWebSocketListener"/> class.
         /// </summary>
+        /// <param name="logger">Instance of the <see cref="ILogger{ScheduledTasksWebSocketListener}"/> interface.</param>
+        /// <param name="taskManager">Instance of the <see cref="ITaskManager"/> interface.</param>
         public ScheduledTasksWebSocketListener(ILogger<ScheduledTasksWebSocketListener> logger, ITaskManager taskManager)
             : base(logger)
         {
-            TaskManager = taskManager;
+            _taskManager = taskManager;
 
-            TaskManager.TaskExecuting += TaskManager_TaskExecuting;
-            TaskManager.TaskCompleted += TaskManager_TaskCompleted;
+            _taskManager.TaskExecuting += OnTaskExecuting;
+            _taskManager.TaskCompleted += OnTaskCompleted;
         }
 
-        void TaskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
-        {
-            SendData(true);
-            e.Task.TaskProgress -= Argument_TaskProgress;
-        }
-
-        void TaskManager_TaskExecuting(object sender, GenericEventArgs<IScheduledTaskWorker> e)
-        {
-            SendData(true);
-            e.Argument.TaskProgress += Argument_TaskProgress;
-        }
-
-        void Argument_TaskProgress(object sender, GenericEventArgs<double> e)
-        {
-            SendData(false);
-        }
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        protected override string Name => "ScheduledTasksInfo";
 
         /// <summary>
         /// Gets the data to send.
@@ -60,18 +45,36 @@ namespace MediaBrowser.Api.ScheduledTasks
         /// <returns>Task{IEnumerable{TaskInfo}}.</returns>
         protected override Task<IEnumerable<TaskInfo>> GetDataToSend()
         {
-            return Task.FromResult(TaskManager.ScheduledTasks
+            return Task.FromResult(_taskManager.ScheduledTasks
                 .OrderBy(i => i.Name)
                 .Select(ScheduledTaskHelpers.GetTaskInfo)
                 .Where(i => !i.IsHidden));
         }
 
+        /// <inheritdoc />
         protected override void Dispose(bool dispose)
         {
-            TaskManager.TaskExecuting -= TaskManager_TaskExecuting;
-            TaskManager.TaskCompleted -= TaskManager_TaskCompleted;
+            _taskManager.TaskExecuting -= OnTaskExecuting;
+            _taskManager.TaskCompleted -= OnTaskCompleted;
 
             base.Dispose(dispose);
         }
+
+        private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
+        {
+            SendData(true);
+            e.Task.TaskProgress -= OnTaskProgress;
+        }
+
+        private void OnTaskExecuting(object sender, GenericEventArgs<IScheduledTaskWorker> e)
+        {
+            SendData(true);
+            e.Argument.TaskProgress += OnTaskProgress;
+        }
+
+        private void OnTaskProgress(object sender, GenericEventArgs<double> e)
+        {
+            SendData(false);
+        }
     }
 }

+ 29 - 33
MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs → Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs

@@ -5,27 +5,20 @@ using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Session;
 using Microsoft.Extensions.Logging;
 
-namespace MediaBrowser.Api.Sessions
+namespace Jellyfin.Api.WebSocketListeners
 {
     /// <summary>
     /// Class SessionInfoWebSocketListener.
     /// </summary>
     public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState>
     {
-        /// <summary>
-        /// Gets the name.
-        /// </summary>
-        /// <value>The name.</value>
-        protected override string Name => "Sessions";
-
-        /// <summary>
-        /// The _kernel.
-        /// </summary>
         private readonly ISessionManager _sessionManager;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="SessionInfoWebSocketListener"/> class.
         /// </summary>
+        /// <param name="logger">Instance of the <see cref="ILogger{SessionInfoWebSocketListener}"/> interface.</param>
+        /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
         public SessionInfoWebSocketListener(ILogger<SessionInfoWebSocketListener> logger, ISessionManager sessionManager)
             : base(logger)
         {
@@ -40,6 +33,32 @@ namespace MediaBrowser.Api.Sessions
             _sessionManager.SessionActivity += OnSessionManagerSessionActivity;
         }
 
+        /// <inheritdoc />
+        protected override string Name => "Sessions";
+
+        /// <summary>
+        /// Gets the data to send.
+        /// </summary>
+        /// <returns>Task{SystemInfo}.</returns>
+        protected override Task<IEnumerable<SessionInfo>> GetDataToSend()
+        {
+            return Task.FromResult(_sessionManager.Sessions);
+        }
+
+        /// <inheritdoc />
+        protected override void Dispose(bool dispose)
+        {
+            _sessionManager.SessionStarted -= OnSessionManagerSessionStarted;
+            _sessionManager.SessionEnded -= OnSessionManagerSessionEnded;
+            _sessionManager.PlaybackStart -= OnSessionManagerPlaybackStart;
+            _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped;
+            _sessionManager.PlaybackProgress -= OnSessionManagerPlaybackProgress;
+            _sessionManager.CapabilitiesChanged -= OnSessionManagerCapabilitiesChanged;
+            _sessionManager.SessionActivity -= OnSessionManagerSessionActivity;
+
+            base.Dispose(dispose);
+        }
+
         private async void OnSessionManagerSessionActivity(object sender, SessionEventArgs e)
         {
             await SendData(false).ConfigureAwait(false);
@@ -74,28 +93,5 @@ namespace MediaBrowser.Api.Sessions
         {
             await SendData(true).ConfigureAwait(false);
         }
-
-        /// <summary>
-        /// Gets the data to send.
-        /// </summary>
-        /// <returns>Task{SystemInfo}.</returns>
-        protected override Task<IEnumerable<SessionInfo>> GetDataToSend()
-        {
-            return Task.FromResult(_sessionManager.Sessions);
-        }
-
-        /// <inheritdoc />
-        protected override void Dispose(bool dispose)
-        {
-            _sessionManager.SessionStarted -= OnSessionManagerSessionStarted;
-            _sessionManager.SessionEnded -= OnSessionManagerSessionEnded;
-            _sessionManager.PlaybackStart -= OnSessionManagerPlaybackStart;
-            _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped;
-            _sessionManager.PlaybackProgress -= OnSessionManagerPlaybackProgress;
-            _sessionManager.CapabilitiesChanged -= OnSessionManagerCapabilitiesChanged;
-            _sessionManager.SessionActivity -= OnSessionManagerSessionActivity;
-
-            base.Dispose(dispose);
-        }
     }
 }

+ 9 - 2
Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs

@@ -198,8 +198,15 @@ namespace Jellyfin.Server.Extensions
                     $"{description.ActionDescriptor.RouteValues["controller"]}_{description.RelativePath}");
 
                 // Use method name as operationId
-                c.CustomOperationIds(description =>
-                    description.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null);
+                c.CustomOperationIds(
+                    description =>
+                    {
+                        description.TryGetMethodInfo(out MethodInfo methodInfo);
+                        // Attribute name, method name, none.
+                        return description?.ActionDescriptor?.AttributeRouteInfo?.Name
+                               ?? methodInfo?.Name
+                               ?? null;
+                    });
 
                 // TODO - remove when all types are supported in System.Text.Json
                 c.AddSwaggerTypeMappings();

+ 0 - 678
MediaBrowser.Api/ApiEntryPoint.cs

@@ -1,678 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Api.Playback;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Session;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api
-{
-    /// <summary>
-    /// Class ServerEntryPoint.
-    /// </summary>
-    public class ApiEntryPoint : IServerEntryPoint
-    {
-        /// <summary>
-        /// The instance.
-        /// </summary>
-        public static ApiEntryPoint Instance;
-
-        /// <summary>
-        /// The logger.
-        /// </summary>
-        private ILogger<ApiEntryPoint> _logger;
-
-        /// <summary>
-        /// The configuration manager.
-        /// </summary>
-        private IServerConfigurationManager _serverConfigurationManager;
-
-        private readonly ISessionManager _sessionManager;
-        private readonly IFileSystem _fileSystem;
-        private readonly IMediaSourceManager _mediaSourceManager;
-
-        /// <summary>
-        /// The active transcoding jobs.
-        /// </summary>
-        private readonly List<TranscodingJob> _activeTranscodingJobs = new List<TranscodingJob>();
-
-        private readonly Dictionary<string, SemaphoreSlim> _transcodingLocks =
-            new Dictionary<string, SemaphoreSlim>();
-
-        private bool _disposed = false;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
-        /// </summary>
-        /// <param name="logger">The logger.</param>
-        /// <param name="sessionManager">The session manager.</param>
-        /// <param name="config">The configuration.</param>
-        /// <param name="fileSystem">The file system.</param>
-        /// <param name="mediaSourceManager">The media source manager.</param>
-        public ApiEntryPoint(
-            ILogger<ApiEntryPoint> logger,
-            ISessionManager sessionManager,
-            IServerConfigurationManager config,
-            IFileSystem fileSystem,
-            IMediaSourceManager mediaSourceManager)
-        {
-            _logger = logger;
-            _sessionManager = sessionManager;
-            _serverConfigurationManager = config;
-            _fileSystem = fileSystem;
-            _mediaSourceManager = mediaSourceManager;
-
-            _sessionManager.PlaybackProgress += OnPlaybackProgress;
-            _sessionManager.PlaybackStart += OnPlaybackStart;
-
-            Instance = this;
-        }
-
-        public static string[] Split(string value, char separator, bool removeEmpty)
-        {
-            if (string.IsNullOrWhiteSpace(value))
-            {
-                return Array.Empty<string>();
-            }
-
-            return removeEmpty
-                ? value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)
-                : value.Split(separator);
-        }
-
-        public SemaphoreSlim GetTranscodingLock(string outputPath)
-        {
-            lock (_transcodingLocks)
-            {
-                if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim result))
-                {
-                    result = new SemaphoreSlim(1, 1);
-                    _transcodingLocks[outputPath] = result;
-                }
-
-                return result;
-            }
-        }
-
-        private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
-        {
-            if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
-            {
-                PingTranscodingJob(e.PlaySessionId, e.IsPaused);
-            }
-        }
-
-        private void OnPlaybackProgress(object sender, PlaybackProgressEventArgs e)
-        {
-            if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
-            {
-                PingTranscodingJob(e.PlaySessionId, e.IsPaused);
-            }
-        }
-
-        /// <summary>
-        /// Runs this instance.
-        /// </summary>
-        public Task RunAsync()
-        {
-            try
-            {
-                DeleteEncodedMediaCache();
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error deleting encoded media cache");
-            }
-
-            return Task.CompletedTask;
-        }
-
-        /// <summary>
-        /// Deletes the encoded media cache.
-        /// </summary>
-        private void DeleteEncodedMediaCache()
-        {
-            var path = _serverConfigurationManager.GetTranscodePath();
-            if (!Directory.Exists(path))
-            {
-                return;
-            }
-
-            foreach (var file in _fileSystem.GetFilePaths(path, true))
-            {
-                _fileSystem.DeleteFile(file);
-            }
-        }
-
-        /// <inheritdoc />
-        public void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
-
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool dispose)
-        {
-            if (_disposed)
-            {
-                return;
-            }
-
-            if (dispose)
-            {
-                // TODO: dispose
-            }
-
-            var jobs = _activeTranscodingJobs.ToList();
-            var jobCount = jobs.Count;
-
-            IEnumerable<Task> GetKillJobs()
-            {
-                foreach (var job in jobs)
-                {
-                    yield return KillTranscodingJob(job, false, path => true);
-                }
-            }
-
-            // Wait for all processes to be killed
-            if (jobCount > 0)
-            {
-                Task.WaitAll(GetKillJobs().ToArray());
-            }
-
-            _activeTranscodingJobs.Clear();
-            _transcodingLocks.Clear();
-
-            _sessionManager.PlaybackProgress -= OnPlaybackProgress;
-            _sessionManager.PlaybackStart -= OnPlaybackStart;
-
-            _disposed = true;
-        }
-
-
-        /// <summary>
-        /// Called when [transcode beginning].
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <param name="playSessionId">The play session identifier.</param>
-        /// <param name="liveStreamId">The live stream identifier.</param>
-        /// <param name="transcodingJobId">The transcoding job identifier.</param>
-        /// <param name="type">The type.</param>
-        /// <param name="process">The process.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="state">The state.</param>
-        /// <param name="cancellationTokenSource">The cancellation token source.</param>
-        /// <returns>TranscodingJob.</returns>
-        public TranscodingJob OnTranscodeBeginning(
-            string path,
-            string playSessionId,
-            string liveStreamId,
-            string transcodingJobId,
-            TranscodingJobType type,
-            Process process,
-            string deviceId,
-            StreamState state,
-            CancellationTokenSource cancellationTokenSource)
-        {
-            lock (_activeTranscodingJobs)
-            {
-                var job = new TranscodingJob(_logger)
-                {
-                    Type = type,
-                    Path = path,
-                    Process = process,
-                    ActiveRequestCount = 1,
-                    DeviceId = deviceId,
-                    CancellationTokenSource = cancellationTokenSource,
-                    Id = transcodingJobId,
-                    PlaySessionId = playSessionId,
-                    LiveStreamId = liveStreamId,
-                    MediaSource = state.MediaSource
-                };
-
-                _activeTranscodingJobs.Add(job);
-
-                ReportTranscodingProgress(job, state, null, null, null, null, null);
-
-                return job;
-            }
-        }
-
-        public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
-        {
-            var ticks = transcodingPosition?.Ticks;
-
-            if (job != null)
-            {
-                job.Framerate = framerate;
-                job.CompletionPercentage = percentComplete;
-                job.TranscodingPositionTicks = ticks;
-                job.BytesTranscoded = bytesTranscoded;
-                job.BitRate = bitRate;
-            }
-
-            var deviceId = state.Request.DeviceId;
-
-            if (!string.IsNullOrWhiteSpace(deviceId))
-            {
-                var audioCodec = state.ActualOutputAudioCodec;
-                var videoCodec = state.ActualOutputVideoCodec;
-
-                _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
-                {
-                    Bitrate = bitRate ?? state.TotalOutputBitrate,
-                    AudioCodec = audioCodec,
-                    VideoCodec = videoCodec,
-                    Container = state.OutputContainer,
-                    Framerate = framerate,
-                    CompletionPercentage = percentComplete,
-                    Width = state.OutputWidth,
-                    Height = state.OutputHeight,
-                    AudioChannels = state.OutputAudioChannels,
-                    IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec),
-                    IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec),
-                    TranscodeReasons = state.TranscodeReasons
-                });
-            }
-        }
-
-        /// <summary>
-        /// <summary>
-        /// The progressive.
-        /// </summary>
-        /// Called when [transcode failed to start].
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <param name="type">The type.</param>
-        /// <param name="state">The state.</param>
-        public void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state)
-        {
-            lock (_activeTranscodingJobs)
-            {
-                var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
-
-                if (job != null)
-                {
-                    _activeTranscodingJobs.Remove(job);
-                }
-            }
-
-            lock (_transcodingLocks)
-            {
-                _transcodingLocks.Remove(path);
-            }
-
-            if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
-            {
-                _sessionManager.ClearTranscodingInfo(state.Request.DeviceId);
-            }
-        }
-
-        /// <summary>
-        /// Determines whether [has active transcoding job] [the specified path].
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <param name="type">The type.</param>
-        /// <returns><c>true</c> if [has active transcoding job] [the specified path]; otherwise, <c>false</c>.</returns>
-        public bool HasActiveTranscodingJob(string path, TranscodingJobType type)
-        {
-            return GetTranscodingJob(path, type) != null;
-        }
-
-        public TranscodingJob GetTranscodingJob(string path, TranscodingJobType type)
-        {
-            lock (_activeTranscodingJobs)
-            {
-                return _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
-            }
-        }
-
-        public TranscodingJob GetTranscodingJob(string playSessionId)
-        {
-            lock (_activeTranscodingJobs)
-            {
-                return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase));
-            }
-        }
-
-        /// <summary>
-        /// Called when [transcode begin request].
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <param name="type">The type.</param>
-        public TranscodingJob OnTranscodeBeginRequest(string path, TranscodingJobType type)
-        {
-            lock (_activeTranscodingJobs)
-            {
-                var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
-
-                if (job == null)
-                {
-                    return null;
-                }
-
-                OnTranscodeBeginRequest(job);
-
-                return job;
-            }
-        }
-
-        public void OnTranscodeBeginRequest(TranscodingJob job)
-        {
-            job.ActiveRequestCount++;
-
-            if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive)
-            {
-                job.StopKillTimer();
-            }
-        }
-
-        public void OnTranscodeEndRequest(TranscodingJob job)
-        {
-            job.ActiveRequestCount--;
-            _logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount);
-            if (job.ActiveRequestCount <= 0)
-            {
-                PingTimer(job, false);
-            }
-        }
-
-        internal void PingTranscodingJob(string playSessionId, bool? isUserPaused)
-        {
-            if (string.IsNullOrEmpty(playSessionId))
-            {
-                throw new ArgumentNullException(nameof(playSessionId));
-            }
-
-            _logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
-
-            List<TranscodingJob> jobs;
-
-            lock (_activeTranscodingJobs)
-            {
-                // This is really only needed for HLS.
-                // Progressive streams can stop on their own reliably
-                jobs = _activeTranscodingJobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList();
-            }
-
-            foreach (var job in jobs)
-            {
-                if (isUserPaused.HasValue)
-                {
-                    _logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
-                    job.IsUserPaused = isUserPaused.Value;
-                }
-
-                PingTimer(job, true);
-            }
-        }
-
-        private void PingTimer(TranscodingJob job, bool isProgressCheckIn)
-        {
-            if (job.HasExited)
-            {
-                job.StopKillTimer();
-                return;
-            }
-
-            var timerDuration = 10000;
-
-            if (job.Type != TranscodingJobType.Progressive)
-            {
-                timerDuration = 60000;
-            }
-
-            job.PingTimeout = timerDuration;
-            job.LastPingDate = DateTime.UtcNow;
-
-            // Don't start the timer for playback checkins with progressive streaming
-            if (job.Type != TranscodingJobType.Progressive || !isProgressCheckIn)
-            {
-                job.StartKillTimer(OnTranscodeKillTimerStopped);
-            }
-            else
-            {
-                job.ChangeKillTimerIfStarted();
-            }
-        }
-
-        /// <summary>
-        /// Called when [transcode kill timer stopped].
-        /// </summary>
-        /// <param name="state">The state.</param>
-        private async void OnTranscodeKillTimerStopped(object state)
-        {
-            var job = (TranscodingJob)state;
-
-            if (!job.HasExited && job.Type != TranscodingJobType.Progressive)
-            {
-                var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds;
-
-                if (timeSinceLastPing < job.PingTimeout)
-                {
-                    job.StartKillTimer(OnTranscodeKillTimerStopped, job.PingTimeout);
-                    return;
-                }
-            }
-
-            _logger.LogInformation("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
-
-            await KillTranscodingJob(job, true, path => true);
-        }
-
-        /// <summary>
-        /// Kills the single transcoding job.
-        /// </summary>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="playSessionId">The play session identifier.</param>
-        /// <param name="deleteFiles">The delete files.</param>
-        /// <returns>Task.</returns>
-        internal Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
-        {
-            return KillTranscodingJobs(j => string.IsNullOrWhiteSpace(playSessionId)
-                ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
-                : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase), deleteFiles);
-        }
-
-        /// <summary>
-        /// Kills the transcoding jobs.
-        /// </summary>
-        /// <param name="killJob">The kill job.</param>
-        /// <param name="deleteFiles">The delete files.</param>
-        /// <returns>Task.</returns>
-        private Task KillTranscodingJobs(Func<TranscodingJob, bool> killJob, Func<string, bool> deleteFiles)
-        {
-            var jobs = new List<TranscodingJob>();
-
-            lock (_activeTranscodingJobs)
-            {
-                // This is really only needed for HLS.
-                // Progressive streams can stop on their own reliably
-                jobs.AddRange(_activeTranscodingJobs.Where(killJob));
-            }
-
-            if (jobs.Count == 0)
-            {
-                return Task.CompletedTask;
-            }
-
-            IEnumerable<Task> GetKillJobs()
-            {
-                foreach (var job in jobs)
-                {
-                    yield return KillTranscodingJob(job, false, deleteFiles);
-                }
-            }
-
-            return Task.WhenAll(GetKillJobs());
-        }
-
-        /// <summary>
-        /// Kills the transcoding job.
-        /// </summary>
-        /// <param name="job">The job.</param>
-        /// <param name="closeLiveStream">if set to <c>true</c> [close live stream].</param>
-        /// <param name="delete">The delete.</param>
-        private async Task KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func<string, bool> delete)
-        {
-            job.DisposeKillTimer();
-
-            _logger.LogDebug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
-
-            lock (_activeTranscodingJobs)
-            {
-                _activeTranscodingJobs.Remove(job);
-
-                if (!job.CancellationTokenSource.IsCancellationRequested)
-                {
-                    job.CancellationTokenSource.Cancel();
-                }
-            }
-
-            lock (_transcodingLocks)
-            {
-                _transcodingLocks.Remove(job.Path);
-            }
-
-            lock (job.ProcessLock)
-            {
-                job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
-
-                var process = job.Process;
-
-                var hasExited = job.HasExited;
-
-                if (!hasExited)
-                {
-                    try
-                    {
-                        _logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path);
-
-                        process.StandardInput.WriteLine("q");
-
-                        // Need to wait because killing is asynchronous
-                        if (!process.WaitForExit(5000))
-                        {
-                            _logger.LogInformation("Killing ffmpeg process for {Path}", job.Path);
-                            process.Kill();
-                        }
-                    }
-                    catch (InvalidOperationException)
-                    {
-                    }
-                }
-            }
-
-            if (delete(job.Path))
-            {
-                await DeletePartialStreamFiles(job.Path, job.Type, 0, 1500).ConfigureAwait(false);
-            }
-
-            if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId))
-            {
-                try
-                {
-                    await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false);
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error closing live stream for {Path}", job.Path);
-                }
-            }
-        }
-
-        private async Task DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
-        {
-            if (retryCount >= 10)
-            {
-                return;
-            }
-
-            _logger.LogInformation("Deleting partial stream file(s) {Path}", path);
-
-            await Task.Delay(delayMs).ConfigureAwait(false);
-
-            try
-            {
-                if (jobType == TranscodingJobType.Progressive)
-                {
-                    DeleteProgressivePartialStreamFiles(path);
-                }
-                else
-                {
-                    DeleteHlsPartialStreamFiles(path);
-                }
-            }
-            catch (IOException ex)
-            {
-                _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
-
-                await DeletePartialStreamFiles(path, jobType, retryCount + 1, 500).ConfigureAwait(false);
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
-            }
-        }
-
-        /// <summary>
-        /// Deletes the progressive partial stream files.
-        /// </summary>
-        /// <param name="outputFilePath">The output file path.</param>
-        private void DeleteProgressivePartialStreamFiles(string outputFilePath)
-        {
-            if (File.Exists(outputFilePath))
-            {
-                _fileSystem.DeleteFile(outputFilePath);
-            }
-        }
-
-        /// <summary>
-        /// Deletes the HLS partial stream files.
-        /// </summary>
-        /// <param name="outputFilePath">The output file path.</param>
-        private void DeleteHlsPartialStreamFiles(string outputFilePath)
-        {
-            var directory = Path.GetDirectoryName(outputFilePath);
-            var name = Path.GetFileNameWithoutExtension(outputFilePath);
-
-            var filesToDelete = _fileSystem.GetFilePaths(directory)
-                .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1);
-
-            List<Exception> exs = null;
-            foreach (var file in filesToDelete)
-            {
-                try
-                {
-                    _logger.LogDebug("Deleting HLS file {0}", file);
-                    _fileSystem.DeleteFile(file);
-                }
-                catch (IOException ex)
-                {
-                    (exs ??= new List<Exception>(4)).Add(ex);
-                    _logger.LogError(ex, "Error deleting HLS file {Path}", file);
-                }
-            }
-
-            if (exs != null)
-            {
-                throw new AggregateException("Error deleting HLS files", exs);
-            }
-        }
-    }
-}

+ 0 - 416
MediaBrowser.Api/BaseApiService.cs

@@ -1,416 +0,0 @@
-using System;
-using System.IO;
-using System.Linq;
-using Jellyfin.Data.Enums;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api
-{
-    /// <summary>
-    /// Class BaseApiService.
-    /// </summary>
-    public abstract class BaseApiService : IService, IRequiresRequest
-    {
-        public BaseApiService(
-            ILogger<BaseApiService> logger,
-            IServerConfigurationManager serverConfigurationManager,
-            IHttpResultFactory httpResultFactory)
-        {
-            Logger = logger;
-            ServerConfigurationManager = serverConfigurationManager;
-            ResultFactory = httpResultFactory;
-        }
-
-        /// <summary>
-        /// Gets the logger.
-        /// </summary>
-        /// <value>The logger.</value>
-        protected ILogger<BaseApiService> Logger { get; }
-
-        /// <summary>
-        /// Gets or sets the server configuration manager.
-        /// </summary>
-        /// <value>The server configuration manager.</value>
-        protected IServerConfigurationManager ServerConfigurationManager { get; }
-
-        /// <summary>
-        /// Gets the HTTP result factory.
-        /// </summary>
-        /// <value>The HTTP result factory.</value>
-        protected IHttpResultFactory ResultFactory { get; }
-
-        /// <summary>
-        /// Gets or sets the request context.
-        /// </summary>
-        /// <value>The request context.</value>
-        public IRequest Request { get; set; }
-
-        public string GetHeader(string name) => Request.Headers[name];
-
-        public static string[] SplitValue(string value, char delim)
-        {
-            return value == null
-                ? Array.Empty<string>()
-                : value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
-        }
-
-        public static Guid[] GetGuids(string value)
-        {
-            if (value == null)
-            {
-                return Array.Empty<Guid>();
-            }
-
-            return value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
-                        .Select(i => new Guid(i))
-                        .ToArray();
-        }
-
-        /// <summary>
-        /// To the optimized result.
-        /// </summary>
-        /// <typeparam name="T"></typeparam>
-        /// <param name="result">The result.</param>
-        /// <returns>System.Object.</returns>
-        protected object ToOptimizedResult<T>(T result)
-            where T : class
-        {
-            return ResultFactory.GetResult(Request, result);
-        }
-
-        protected void AssertCanUpdateUser(IAuthorizationContext authContext, IUserManager userManager, Guid userId, bool restrictUserPreferences)
-        {
-            var auth = authContext.GetAuthorizationInfo(Request);
-
-            var authenticatedUser = auth.User;
-
-            // If they're going to update the record of another user, they must be an administrator
-            if ((!userId.Equals(auth.UserId) && !authenticatedUser.HasPermission(PermissionKind.IsAdministrator))
-                || (restrictUserPreferences && !authenticatedUser.EnableUserPreferenceAccess))
-            {
-                throw new SecurityException("Unauthorized access.");
-            }
-        }
-
-        /// <summary>
-        /// Gets the session.
-        /// </summary>
-        /// <returns>SessionInfo.</returns>
-        protected SessionInfo GetSession(ISessionContext sessionContext)
-        {
-            var session = sessionContext.GetSession(Request);
-
-            if (session == null)
-            {
-                throw new ArgumentException("Session not found.");
-            }
-
-            return session;
-        }
-
-        protected DtoOptions GetDtoOptions(IAuthorizationContext authContext, object request)
-        {
-            var options = new DtoOptions();
-
-            if (request is IHasItemFields hasFields)
-            {
-                options.Fields = hasFields.GetItemFields();
-            }
-
-            if (!options.ContainsField(ItemFields.RecursiveItemCount)
-                || !options.ContainsField(ItemFields.ChildCount))
-            {
-                var client = authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
-                if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
-                    client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 ||
-                    client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
-                    client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
-                {
-                    int oldLen = options.Fields.Length;
-                    var arr = new ItemFields[oldLen + 1];
-                    options.Fields.CopyTo(arr, 0);
-                    arr[oldLen] = ItemFields.RecursiveItemCount;
-                    options.Fields = arr;
-                }
-
-                if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
-                   client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 ||
-                   client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
-                   client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1 ||
-                   client.IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1 ||
-                   client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 ||
-                   client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1)
-                {
-
-                    int oldLen = options.Fields.Length;
-                    var arr = new ItemFields[oldLen + 1];
-                    options.Fields.CopyTo(arr, 0);
-                    arr[oldLen] = ItemFields.ChildCount;
-                    options.Fields = arr;
-                }
-            }
-
-            if (request is IHasDtoOptions hasDtoOptions)
-            {
-                options.EnableImages = hasDtoOptions.EnableImages ?? true;
-
-                if (hasDtoOptions.ImageTypeLimit.HasValue)
-                {
-                    options.ImageTypeLimit = hasDtoOptions.ImageTypeLimit.Value;
-                }
-
-                if (hasDtoOptions.EnableUserData.HasValue)
-                {
-                    options.EnableUserData = hasDtoOptions.EnableUserData.Value;
-                }
-
-                if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes))
-                {
-                    options.ImageTypes = hasDtoOptions.EnableImageTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
-                                                                        .Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true))
-                                                                        .ToArray();
-                }
-            }
-
-            return options;
-        }
-
-        protected MusicArtist GetArtist(string name, ILibraryManager libraryManager, DtoOptions dtoOptions)
-        {
-            if (name.IndexOf(BaseItem.SlugChar) != -1)
-            {
-                var result = GetItemFromSlugName<MusicArtist>(libraryManager, name, dtoOptions);
-
-                if (result != null)
-                {
-                    return result;
-                }
-            }
-
-            return libraryManager.GetArtist(name, dtoOptions);
-        }
-
-        protected Studio GetStudio(string name, ILibraryManager libraryManager, DtoOptions dtoOptions)
-        {
-            if (name.IndexOf(BaseItem.SlugChar) != -1)
-            {
-                var result = GetItemFromSlugName<Studio>(libraryManager, name, dtoOptions);
-
-                if (result != null)
-                {
-                    return result;
-                }
-            }
-
-            return libraryManager.GetStudio(name);
-        }
-
-        protected Genre GetGenre(string name, ILibraryManager libraryManager, DtoOptions dtoOptions)
-        {
-            if (name.IndexOf(BaseItem.SlugChar) != -1)
-            {
-                var result = GetItemFromSlugName<Genre>(libraryManager, name, dtoOptions);
-
-                if (result != null)
-                {
-                    return result;
-                }
-            }
-
-            return libraryManager.GetGenre(name);
-        }
-
-        protected MusicGenre GetMusicGenre(string name, ILibraryManager libraryManager, DtoOptions dtoOptions)
-        {
-            if (name.IndexOf(BaseItem.SlugChar) != -1)
-            {
-                var result = GetItemFromSlugName<MusicGenre>(libraryManager, name, dtoOptions);
-
-                if (result != null)
-                {
-                    return result;
-                }
-            }
-
-            return libraryManager.GetMusicGenre(name);
-        }
-
-        protected Person GetPerson(string name, ILibraryManager libraryManager, DtoOptions dtoOptions)
-        {
-            if (name.IndexOf(BaseItem.SlugChar) != -1)
-            {
-                var result = GetItemFromSlugName<Person>(libraryManager, name, dtoOptions);
-
-                if (result != null)
-                {
-                    return result;
-                }
-            }
-
-            return libraryManager.GetPerson(name);
-        }
-
-        private T GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
-            where T : BaseItem, new()
-        {
-            var result = libraryManager.GetItemList(new InternalItemsQuery
-            {
-                Name = name.Replace(BaseItem.SlugChar, '&'),
-                IncludeItemTypes = new[] { typeof(T).Name },
-                DtoOptions = dtoOptions
-            }).OfType<T>().FirstOrDefault();
-
-            result ??= libraryManager.GetItemList(new InternalItemsQuery
-            {
-                Name = name.Replace(BaseItem.SlugChar, '/'),
-                IncludeItemTypes = new[] { typeof(T).Name },
-                DtoOptions = dtoOptions
-            }).OfType<T>().FirstOrDefault();
-
-            result ??= libraryManager.GetItemList(new InternalItemsQuery
-            {
-                Name = name.Replace(BaseItem.SlugChar, '?'),
-                IncludeItemTypes = new[] { typeof(T).Name },
-                DtoOptions = dtoOptions
-            }).OfType<T>().FirstOrDefault();
-
-            return result;
-        }
-
-        /// <summary>
-        /// Gets the path segment at the specified index.
-        /// </summary>
-        /// <param name="index">The index of the path segment.</param>
-        /// <returns>The path segment at the specified index.</returns>
-        /// <exception cref="IndexOutOfRangeException" >Path doesn't contain enough segments.</exception>
-        /// <exception cref="InvalidDataException" >Path doesn't start with the base url.</exception>
-        protected internal ReadOnlySpan<char> GetPathValue(int index)
-        {
-            static void ThrowIndexOutOfRangeException()
-                => throw new IndexOutOfRangeException("Path doesn't contain enough segments.");
-
-            static void ThrowInvalidDataException()
-                => throw new InvalidDataException("Path doesn't start with the base url.");
-
-            ReadOnlySpan<char> path = Request.PathInfo;
-
-            // Remove the protocol part from the url
-            int pos = path.LastIndexOf("://");
-            if (pos != -1)
-            {
-                path = path.Slice(pos + 3);
-            }
-
-            // Remove the query string
-            pos = path.LastIndexOf('?');
-            if (pos != -1)
-            {
-                path = path.Slice(0, pos);
-            }
-
-            // Remove the domain
-            pos = path.IndexOf('/');
-            if (pos != -1)
-            {
-                path = path.Slice(pos);
-            }
-
-            // Remove base url
-            string baseUrl = ServerConfigurationManager.Configuration.BaseUrl;
-            int baseUrlLen = baseUrl.Length;
-            if (baseUrlLen != 0)
-            {
-                if (path.StartsWith(baseUrl, StringComparison.OrdinalIgnoreCase))
-                {
-                    path = path.Slice(baseUrlLen);
-                }
-                else
-                {
-                    // The path doesn't start with the base url,
-                    // how did we get here?
-                    ThrowInvalidDataException();
-                }
-            }
-
-            // Remove leading /
-            path = path.Slice(1);
-
-            // Backwards compatibility
-            const string Emby = "emby/";
-            if (path.StartsWith(Emby, StringComparison.OrdinalIgnoreCase))
-            {
-                path = path.Slice(Emby.Length);
-            }
-
-            const string MediaBrowser = "mediabrowser/";
-            if (path.StartsWith(MediaBrowser, StringComparison.OrdinalIgnoreCase))
-            {
-                path = path.Slice(MediaBrowser.Length);
-            }
-
-            // Skip segments until we are at the right index
-            for (int i = 0; i < index; i++)
-            {
-                pos = path.IndexOf('/');
-                if (pos == -1)
-                {
-                    ThrowIndexOutOfRangeException();
-                }
-
-                path = path.Slice(pos + 1);
-            }
-
-            // Remove the rest
-            pos = path.IndexOf('/');
-            if (pos != -1)
-            {
-                path = path.Slice(0, pos);
-            }
-
-            return path;
-        }
-
-        /// <summary>
-        /// Gets the name of the item by.
-        /// </summary>
-        protected BaseItem GetItemByName(string name, string type, ILibraryManager libraryManager, DtoOptions dtoOptions)
-        {
-            if (type.Equals("Person", StringComparison.OrdinalIgnoreCase))
-            {
-                return GetPerson(name, libraryManager, dtoOptions);
-            }
-            else if (type.Equals("Artist", StringComparison.OrdinalIgnoreCase))
-            {
-                return GetArtist(name, libraryManager, dtoOptions);
-            }
-            else if (type.Equals("Genre", StringComparison.OrdinalIgnoreCase))
-            {
-                return GetGenre(name, libraryManager, dtoOptions);
-            }
-            else if (type.Equals("MusicGenre", StringComparison.OrdinalIgnoreCase))
-            {
-                return GetMusicGenre(name, libraryManager, dtoOptions);
-            }
-            else if (type.Equals("Studio", StringComparison.OrdinalIgnoreCase))
-            {
-                return GetStudio(name, libraryManager, dtoOptions);
-            }
-            else if (type.Equals("Year", StringComparison.OrdinalIgnoreCase))
-            {
-                return libraryManager.GetYear(int.Parse(name));
-            }
-
-            throw new ArgumentException("Invalid type", nameof(type));
-        }
-    }
-}

+ 0 - 13
MediaBrowser.Api/IHasDtoOptions.cs

@@ -1,13 +0,0 @@
-namespace MediaBrowser.Api
-{
-    public interface IHasDtoOptions : IHasItemFields
-    {
-        bool? EnableImages { get; set; }
-
-        bool? EnableUserData { get; set; }
-
-        int? ImageTypeLimit { get; set; }
-
-        string EnableImageTypes { get; set; }
-    }
-}

+ 0 - 49
MediaBrowser.Api/IHasItemFields.cs

@@ -1,49 +0,0 @@
-using System;
-using System.Linq;
-using MediaBrowser.Model.Querying;
-
-namespace MediaBrowser.Api
-{
-    /// <summary>
-    /// Interface IHasItemFields.
-    /// </summary>
-    public interface IHasItemFields
-    {
-        /// <summary>
-        /// Gets or sets the fields.
-        /// </summary>
-        /// <value>The fields.</value>
-        string Fields { get; set; }
-    }
-
-    /// <summary>
-    /// Class ItemFieldsExtensions.
-    /// </summary>
-    public static class ItemFieldsExtensions
-    {
-        /// <summary>
-        /// Gets the item fields.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>IEnumerable{ItemFields}.</returns>
-        public static ItemFields[] GetItemFields(this IHasItemFields request)
-        {
-            var val = request.Fields;
-
-            if (string.IsNullOrEmpty(val))
-            {
-                return Array.Empty<ItemFields>();
-            }
-
-            return val.Split(',').Select(v =>
-            {
-                if (Enum.TryParse(v, true, out ItemFields value))
-                {
-                    return (ItemFields?)value;
-                }
-
-                return null;
-            }).Where(i => i.HasValue).Select(i => i.Value).ToArray();
-        }
-    }
-}

+ 0 - 24
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -1,24 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
-  <PropertyGroup>
-    <ProjectGuid>{4FD51AC5-2C16-4308-A993-C3A84F3B4582}</ProjectGuid>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
-    <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <Compile Include="..\SharedVersion.cs" />
-    <Compile Remove="Images\ImageService.cs" />
-  </ItemGroup>
-
-  <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
-    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
-    <GenerateDocumentationFile>true</GenerateDocumentationFile>
-  </PropertyGroup>
-
-</Project>

+ 0 - 1008
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1,1008 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.Playback
-{
-    /// <summary>
-    /// Class BaseStreamingService.
-    /// </summary>
-    public abstract class BaseStreamingService : BaseApiService
-    {
-        protected virtual bool EnableOutputInSubFolder => false;
-
-        /// <summary>
-        /// Gets or sets the user manager.
-        /// </summary>
-        /// <value>The user manager.</value>
-        protected IUserManager UserManager { get; private set; }
-
-        /// <summary>
-        /// Gets or sets the library manager.
-        /// </summary>
-        /// <value>The library manager.</value>
-        protected ILibraryManager LibraryManager { get; private set; }
-
-        /// <summary>
-        /// Gets or sets the iso manager.
-        /// </summary>
-        /// <value>The iso manager.</value>
-        protected IIsoManager IsoManager { get; private set; }
-
-        /// <summary>
-        /// Gets or sets the media encoder.
-        /// </summary>
-        /// <value>The media encoder.</value>
-        protected IMediaEncoder MediaEncoder { get; private set; }
-
-        protected IFileSystem FileSystem { get; private set; }
-
-        protected IDlnaManager DlnaManager { get; private set; }
-
-        protected IDeviceManager DeviceManager { get; private set; }
-
-        protected IMediaSourceManager MediaSourceManager { get; private set; }
-
-        protected IJsonSerializer JsonSerializer { get; private set; }
-
-        protected IAuthorizationContext AuthorizationContext { get; private set; }
-
-        protected EncodingHelper EncodingHelper { get; set; }
-
-        /// <summary>
-        /// Gets the type of the transcoding job.
-        /// </summary>
-        /// <value>The type of the transcoding job.</value>
-        protected abstract TranscodingJobType TranscodingJobType { get; }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
-        /// </summary>
-        protected BaseStreamingService(
-            ILogger<BaseStreamingService> logger,
-            IServerConfigurationManager serverConfigurationManager,
-            IHttpResultFactory httpResultFactory,
-            IUserManager userManager,
-            ILibraryManager libraryManager,
-            IIsoManager isoManager,
-            IMediaEncoder mediaEncoder,
-            IFileSystem fileSystem,
-            IDlnaManager dlnaManager,
-            IDeviceManager deviceManager,
-            IMediaSourceManager mediaSourceManager,
-            IJsonSerializer jsonSerializer,
-            IAuthorizationContext authorizationContext,
-            EncodingHelper encodingHelper)
-            : base(logger, serverConfigurationManager, httpResultFactory)
-        {
-            UserManager = userManager;
-            LibraryManager = libraryManager;
-            IsoManager = isoManager;
-            MediaEncoder = mediaEncoder;
-            FileSystem = fileSystem;
-            DlnaManager = dlnaManager;
-            DeviceManager = deviceManager;
-            MediaSourceManager = mediaSourceManager;
-            JsonSerializer = jsonSerializer;
-            AuthorizationContext = authorizationContext;
-
-            EncodingHelper = encodingHelper;
-        }
-
-        /// <summary>
-        /// Gets the command line arguments.
-        /// </summary>
-        protected abstract string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding);
-
-        /// <summary>
-        /// Gets the output file extension.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        /// <returns>System.String.</returns>
-        protected virtual string GetOutputFileExtension(StreamState state)
-        {
-            return Path.GetExtension(state.RequestedUrl);
-        }
-
-        /// <summary>
-        /// Gets the output file path.
-        /// </summary>
-        private string GetOutputFilePath(StreamState state, EncodingOptions encodingOptions, string outputFileExtension)
-        {
-            var data = $"{state.MediaPath}-{state.UserAgent}-{state.Request.DeviceId}-{state.Request.PlaySessionId}";
-
-            var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
-            var ext = outputFileExtension?.ToLowerInvariant();
-            var folder = ServerConfigurationManager.GetTranscodePath();
-
-            return EnableOutputInSubFolder
-                ? Path.Combine(folder, filename, filename + ext)
-                : Path.Combine(folder, filename + ext);
-        }
-
-        protected virtual string GetDefaultEncoderPreset()
-        {
-            return "superfast";
-        }
-
-        private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
-        {
-            if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
-            {
-                state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
-            }
-
-            if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId))
-            {
-                var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
-                {
-                    OpenToken = state.MediaSource.OpenToken
-                }, cancellationTokenSource.Token).ConfigureAwait(false);
-
-                EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.RequestedUrl);
-
-                if (state.VideoRequest != null)
-                {
-                    EncodingHelper.TryStreamCopy(state);
-                }
-            }
-
-            if (state.MediaSource.BufferMs.HasValue)
-            {
-                await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false);
-            }
-        }
-
-        /// <summary>
-        /// Starts the FFMPEG.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        /// <param name="outputPath">The output path.</param>
-        /// <param name="cancellationTokenSource">The cancellation token source.</param>
-        /// <param name="workingDirectory">The working directory.</param>
-        /// <returns>Task.</returns>
-        protected async Task<TranscodingJob> StartFfMpeg(
-            StreamState state,
-            string outputPath,
-            CancellationTokenSource cancellationTokenSource,
-            string workingDirectory = null)
-        {
-            Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
-
-            await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
-
-            if (state.VideoRequest != null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
-            {
-                var auth = AuthorizationContext.GetAuthorizationInfo(Request);
-                if (auth.User != null && !auth.User.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
-                {
-                    ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
-
-                    throw new ArgumentException("User does not have access to video transcoding");
-                }
-            }
-
-            var encodingOptions = ServerConfigurationManager.GetEncodingOptions();
-
-            var process = new Process()
-            {
-                StartInfo = new ProcessStartInfo()
-                {
-                    WindowStyle = ProcessWindowStyle.Hidden,
-                    CreateNoWindow = true,
-                    UseShellExecute = false,
-
-                    // Must consume both stdout and stderr or deadlocks may occur
-                    // RedirectStandardOutput = true,
-                    RedirectStandardError = true,
-                    RedirectStandardInput = true,
-
-                    FileName = MediaEncoder.EncoderPath,
-                    Arguments = GetCommandLineArguments(outputPath, encodingOptions, state, true),
-                    WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? null : workingDirectory,
-
-                    ErrorDialog = false
-                },
-                EnableRaisingEvents = true
-            };
-
-            var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
-                state.Request.PlaySessionId,
-                state.MediaSource.LiveStreamId,
-                Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
-                TranscodingJobType,
-                process,
-                state.Request.DeviceId,
-                state,
-                cancellationTokenSource);
-
-            var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
-            Logger.LogInformation(commandLineLogMessage);
-
-            var logFilePrefix = "ffmpeg-transcode";
-            if (state.VideoRequest != null
-                && EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
-            {
-                logFilePrefix = EncodingHelper.IsCopyCodec(state.OutputAudioCodec)
-                    ? "ffmpeg-remux" : "ffmpeg-directstream";
-            }
-
-            var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
-
-            // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
-            Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
-
-            var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(Request.AbsoluteUri + Environment.NewLine + Environment.NewLine + JsonSerializer.SerializeToString(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
-            await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
-
-            process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
-
-            try
-            {
-                process.Start();
-            }
-            catch (Exception ex)
-            {
-                Logger.LogError(ex, "Error starting ffmpeg");
-
-                ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
-
-                throw;
-            }
-
-            Logger.LogDebug("Launched ffmpeg process");
-            state.TranscodingJob = transcodingJob;
-
-            // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
-            _ = new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream);
-
-            // Wait for the file to exist before proceeeding
-            var ffmpegTargetFile = state.WaitForPath ?? outputPath;
-            Logger.LogDebug("Waiting for the creation of {0}", ffmpegTargetFile);
-            while (!File.Exists(ffmpegTargetFile) && !transcodingJob.HasExited)
-            {
-                await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
-            }
-
-            Logger.LogDebug("File {0} created or transcoding has finished", ffmpegTargetFile);
-
-            if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited)
-            {
-                await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false);
-
-                if (state.ReadInputAtNativeFramerate && !transcodingJob.HasExited)
-                {
-                    await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
-                }
-            }
-
-            if (!transcodingJob.HasExited)
-            {
-                StartThrottler(state, transcodingJob);
-            }
-
-            Logger.LogDebug("StartFfMpeg() finished successfully");
-
-            return transcodingJob;
-        }
-
-        private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
-        {
-            if (EnableThrottling(state))
-            {
-                transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager, FileSystem);
-                state.TranscodingThrottler.Start();
-            }
-        }
-
-        private bool EnableThrottling(StreamState state)
-        {
-            var encodingOptions = ServerConfigurationManager.GetEncodingOptions();
-
-            // enable throttling when NOT using hardware acceleration
-            if (string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
-            {
-                return state.InputProtocol == MediaProtocol.File &&
-                       state.RunTimeTicks.HasValue &&
-                       state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
-                       state.IsInputVideo &&
-                       state.VideoType == VideoType.VideoFile &&
-                       !EncodingHelper.IsCopyCodec(state.OutputVideoCodec);
-            }
-
-            return false;
-        }
-
-        /// <summary>
-        /// Processes the exited.
-        /// </summary>
-        /// <param name="process">The process.</param>
-        /// <param name="job">The job.</param>
-        /// <param name="state">The state.</param>
-        private void OnFfMpegProcessExited(Process process, TranscodingJob job, StreamState state)
-        {
-            if (job != null)
-            {
-                job.HasExited = true;
-            }
-
-            Logger.LogDebug("Disposing stream resources");
-            state.Dispose();
-
-            if (process.ExitCode == 0)
-            {
-                Logger.LogInformation("FFMpeg exited with code 0");
-            }
-            else
-            {
-                Logger.LogError("FFMpeg exited with code {0}", process.ExitCode);
-            }
-
-            process.Dispose();
-        }
-
-        /// <summary>
-        /// Parses the parameters.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        private void ParseParams(StreamRequest request)
-        {
-            var vals = request.Params.Split(';');
-
-            var videoRequest = request as VideoStreamRequest;
-
-            for (var i = 0; i < vals.Length; i++)
-            {
-                var val = vals[i];
-
-                if (string.IsNullOrWhiteSpace(val))
-                {
-                    continue;
-                }
-
-                switch (i)
-                {
-                    case 0:
-                        request.DeviceProfileId = val;
-                        break;
-                    case 1:
-                        request.DeviceId = val;
-                        break;
-                    case 2:
-                        request.MediaSourceId = val;
-                        break;
-                    case 3:
-                        request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                        break;
-                    case 4:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.VideoCodec = val;
-                        }
-
-                        break;
-                    case 5:
-                        request.AudioCodec = val;
-                        break;
-                    case 6:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
-                        }
-
-                        break;
-                    case 7:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
-                        }
-
-                        break;
-                    case 8:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
-                        }
-
-                        break;
-                    case 9:
-                        request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
-                        break;
-                    case 10:
-                        request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
-                        break;
-                    case 11:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
-                        }
-
-                        break;
-                    case 12:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
-                        }
-
-                        break;
-                    case 13:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
-                        }
-
-                        break;
-                    case 14:
-                        request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
-                        break;
-                    case 15:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.Level = val;
-                        }
-
-                        break;
-                    case 16:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
-                        }
-
-                        break;
-                    case 17:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
-                        }
-
-                        break;
-                    case 18:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.Profile = val;
-                        }
-
-                        break;
-                    case 19:
-                        // cabac no longer used
-                        break;
-                    case 20:
-                        request.PlaySessionId = val;
-                        break;
-                    case 21:
-                        // api_key
-                        break;
-                    case 22:
-                        request.LiveStreamId = val;
-                        break;
-                    case 23:
-                        // Duplicating ItemId because of MediaMonkey
-                        break;
-                    case 24:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                        }
-
-                        break;
-                    case 25:
-                        if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
-                        {
-                            if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
-                            {
-                                videoRequest.SubtitleMethod = method;
-                            }
-                        }
-
-                        break;
-                    case 26:
-                        request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
-                        break;
-                    case 27:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                        }
-
-                        break;
-                    case 28:
-                        request.Tag = val;
-                        break;
-                    case 29:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                        }
-
-                        break;
-                    case 30:
-                        request.SubtitleCodec = val;
-                        break;
-                    case 31:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                        }
-
-                        break;
-                    case 32:
-                        if (videoRequest != null)
-                        {
-                            videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                        }
-
-                        break;
-                    case 33:
-                        request.TranscodeReasons = val;
-                        break;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Parses query parameters as StreamOptions.
-        /// </summary>
-        /// <param name="request">The stream request.</param>
-        private void ParseStreamOptions(StreamRequest request)
-        {
-            foreach (var param in Request.QueryString)
-            {
-                if (char.IsLower(param.Key[0]))
-                {
-                    // This was probably not parsed initially and should be a StreamOptions
-                    // TODO: This should be incorporated either in the lower framework for parsing requests
-                    // or the generated URL should correctly serialize it
-                    request.StreamOptions[param.Key] = param.Value;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Parses the dlna headers.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        private void ParseDlnaHeaders(StreamRequest request)
-        {
-            if (!request.StartTimeTicks.HasValue)
-            {
-                var timeSeek = GetHeader("TimeSeekRange.dlna.org");
-
-                request.StartTimeTicks = ParseTimeSeekHeader(timeSeek);
-            }
-        }
-
-        /// <summary>
-        /// Parses the time seek header.
-        /// </summary>
-        private long? ParseTimeSeekHeader(string value)
-        {
-            if (string.IsNullOrWhiteSpace(value))
-            {
-                return null;
-            }
-
-            const string Npt = "npt=";
-            if (!value.StartsWith(Npt, StringComparison.OrdinalIgnoreCase))
-            {
-                throw new ArgumentException("Invalid timeseek header");
-            }
-
-            int index = value.IndexOf('-');
-            value = index == -1
-                ? value.Substring(Npt.Length)
-                : value.Substring(Npt.Length, index - Npt.Length);
-
-            if (value.IndexOf(':') == -1)
-            {
-                // Parses npt times in the format of '417.33'
-                if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds))
-                {
-                    return TimeSpan.FromSeconds(seconds).Ticks;
-                }
-
-                throw new ArgumentException("Invalid timeseek header");
-            }
-
-            // Parses npt times in the format of '10:19:25.7'
-            var tokens = value.Split(new[] { ':' }, 3);
-            double secondsSum = 0;
-            var timeFactor = 3600;
-
-            foreach (var time in tokens)
-            {
-                if (double.TryParse(time, NumberStyles.Any, CultureInfo.InvariantCulture, out var digit))
-                {
-                    secondsSum += digit * timeFactor;
-                }
-                else
-                {
-                    throw new ArgumentException("Invalid timeseek header");
-                }
-
-                timeFactor /= 60;
-            }
-
-            return TimeSpan.FromSeconds(secondsSum).Ticks;
-        }
-
-        /// <summary>
-        /// Gets the state.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>StreamState.</returns>
-        protected async Task<StreamState> GetState(StreamRequest request, CancellationToken cancellationToken)
-        {
-            ParseDlnaHeaders(request);
-
-            if (!string.IsNullOrWhiteSpace(request.Params))
-            {
-                ParseParams(request);
-            }
-
-            ParseStreamOptions(request);
-
-            var url = Request.PathInfo;
-
-            if (string.IsNullOrEmpty(request.AudioCodec))
-            {
-                request.AudioCodec = EncodingHelper.InferAudioCodec(url);
-            }
-
-            var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) ||
-                                    string.Equals(GetHeader("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase);
-
-            var state = new StreamState(MediaSourceManager, TranscodingJobType)
-            {
-                Request = request,
-                RequestedUrl = url,
-                UserAgent = Request.UserAgent,
-                EnableDlnaHeaders = enableDlnaHeaders
-            };
-
-            var auth = AuthorizationContext.GetAuthorizationInfo(Request);
-            if (!auth.UserId.Equals(Guid.Empty))
-            {
-                state.User = UserManager.GetUserById(auth.UserId);
-            }
-
-            // if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
-            //    (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
-            //    (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
-            //{
-            //    state.SegmentLength = 6;
-            //}
-
-            if (state.VideoRequest != null && !string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec))
-            {
-                state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
-                state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
-            }
-
-            if (!string.IsNullOrWhiteSpace(request.AudioCodec))
-            {
-                state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
-                state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i))
-                    ?? state.SupportedAudioCodecs.FirstOrDefault();
-            }
-
-            if (!string.IsNullOrWhiteSpace(request.SubtitleCodec))
-            {
-                state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
-                state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToSubtitleCodec(i))
-                    ?? state.SupportedSubtitleCodecs.FirstOrDefault();
-            }
-
-            var item = LibraryManager.GetItemById(request.Id);
-
-            state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
-
-            // var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ??
-            //             item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null);
-            // if (primaryImage != null)
-            //{
-            //    state.AlbumCoverPath = primaryImage.Path;
-            //}
-
-            MediaSourceInfo mediaSource = null;
-            if (string.IsNullOrWhiteSpace(request.LiveStreamId))
-            {
-                var currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ?
-                    ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId)
-                    : null;
-
-                if (currentJob != null)
-                {
-                    mediaSource = currentJob.MediaSource;
-                }
-
-                if (mediaSource == null)
-                {
-                    var mediaSources = await MediaSourceManager.GetPlaybackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false);
-
-                    mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
-                       ? mediaSources[0]
-                       : mediaSources.Find(i => string.Equals(i.Id, request.MediaSourceId));
-
-                    if (mediaSource == null && Guid.Parse(request.MediaSourceId) == request.Id)
-                    {
-                        mediaSource = mediaSources[0];
-                    }
-                }
-            }
-            else
-            {
-                var liveStreamInfo = await MediaSourceManager.GetLiveStreamWithDirectStreamProvider(request.LiveStreamId, cancellationToken).ConfigureAwait(false);
-                mediaSource = liveStreamInfo.Item1;
-                state.DirectStreamProvider = liveStreamInfo.Item2;
-            }
-
-            var videoRequest = request as VideoStreamRequest;
-
-            EncodingHelper.AttachMediaSourceInfo(state, mediaSource, url);
-
-            var container = Path.GetExtension(state.RequestedUrl);
-
-            if (string.IsNullOrEmpty(container))
-            {
-                container = request.Container;
-            }
-
-            if (string.IsNullOrEmpty(container))
-            {
-                container = request.Static ?
-                    StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, state.MediaPath, null, DlnaProfileType.Audio) :
-                    GetOutputFileExtension(state);
-            }
-
-            state.OutputContainer = (container ?? string.Empty).TrimStart('.');
-
-            state.OutputAudioBitrate = EncodingHelper.GetAudioBitrateParam(state.Request, state.AudioStream);
-
-            state.OutputAudioCodec = state.Request.AudioCodec;
-
-            state.OutputAudioChannels = EncodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec);
-
-            if (videoRequest != null)
-            {
-                state.OutputVideoCodec = state.VideoRequest.VideoCodec;
-                state.OutputVideoBitrate = EncodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
-
-                if (videoRequest != null)
-                {
-                    EncodingHelper.TryStreamCopy(state);
-                }
-
-                if (state.OutputVideoBitrate.HasValue && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
-                {
-                    var resolution = ResolutionNormalizer.Normalize(
-                        state.VideoStream?.BitRate,
-                        state.VideoStream?.Width,
-                        state.VideoStream?.Height,
-                        state.OutputVideoBitrate.Value,
-                        state.VideoStream?.Codec,
-                        state.OutputVideoCodec,
-                        videoRequest.MaxWidth,
-                        videoRequest.MaxHeight);
-
-                    videoRequest.MaxWidth = resolution.MaxWidth;
-                    videoRequest.MaxHeight = resolution.MaxHeight;
-                }
-            }
-
-            ApplyDeviceProfileSettings(state);
-
-            var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
-                ? GetOutputFileExtension(state)
-                : ('.' + state.OutputContainer);
-
-            var encodingOptions = ServerConfigurationManager.GetEncodingOptions();
-
-            state.OutputFilePath = GetOutputFilePath(state, encodingOptions, ext);
-
-            return state;
-        }
-
-        private void ApplyDeviceProfileSettings(StreamState state)
-        {
-            var headers = Request.Headers;
-
-            if (!string.IsNullOrWhiteSpace(state.Request.DeviceProfileId))
-            {
-                state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId);
-            }
-            else if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
-            {
-                var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
-
-                state.DeviceProfile = caps == null ? DlnaManager.GetProfile(headers) : caps.DeviceProfile;
-            }
-
-            var profile = state.DeviceProfile;
-
-            if (profile == null)
-            {
-                // Don't use settings from the default profile.
-                // Only use a specific profile if it was requested.
-                return;
-            }
-
-            var audioCodec = state.ActualOutputAudioCodec;
-            var videoCodec = state.ActualOutputVideoCodec;
-
-            var mediaProfile = state.VideoRequest == null ?
-                profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate, state.OutputAudioSampleRate, state.OutputAudioBitDepth) :
-                profile.GetVideoMediaProfile(state.OutputContainer,
-                audioCodec,
-                videoCodec,
-                state.OutputWidth,
-                state.OutputHeight,
-                state.TargetVideoBitDepth,
-                state.OutputVideoBitrate,
-                state.TargetVideoProfile,
-                state.TargetVideoLevel,
-                state.TargetFramerate,
-                state.TargetPacketLength,
-                state.TargetTimestamp,
-                state.IsTargetAnamorphic,
-                state.IsTargetInterlaced,
-                state.TargetRefFrames,
-                state.TargetVideoStreamCount,
-                state.TargetAudioStreamCount,
-                state.TargetVideoCodecTag,
-                state.IsTargetAVC);
-
-            if (mediaProfile != null)
-            {
-                state.MimeType = mediaProfile.MimeType;
-            }
-
-            if (!state.Request.Static)
-            {
-                var transcodingProfile = state.VideoRequest == null ?
-                    profile.GetAudioTranscodingProfile(state.OutputContainer, audioCodec) :
-                    profile.GetVideoTranscodingProfile(state.OutputContainer, audioCodec, videoCodec);
-
-                if (transcodingProfile != null)
-                {
-                    state.EstimateContentLength = transcodingProfile.EstimateContentLength;
-                    // state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
-                    state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
-
-                    if (state.VideoRequest != null)
-                    {
-                        state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
-                        state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Adds the dlna headers.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        /// <param name="responseHeaders">The response headers.</param>
-        /// <param name="isStaticallyStreamed">if set to <c>true</c> [is statically streamed].</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        protected void AddDlnaHeaders(StreamState state, IDictionary<string, string> responseHeaders, bool isStaticallyStreamed)
-        {
-            if (!state.EnableDlnaHeaders)
-            {
-                return;
-            }
-
-            var profile = state.DeviceProfile;
-
-            var transferMode = GetHeader("transferMode.dlna.org");
-            responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
-            responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*";
-
-            if (state.RunTimeTicks.HasValue)
-            {
-                if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase))
-                {
-                    var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds;
-                    responseHeaders["MediaInfo.sec"] = string.Format(
-                        CultureInfo.InvariantCulture,
-                        "SEC_Duration={0};",
-                        Convert.ToInt32(ms));
-                }
-
-                if (!isStaticallyStreamed && profile != null)
-                {
-                    AddTimeSeekResponseHeaders(state, responseHeaders);
-                }
-            }
-
-            if (profile == null)
-            {
-                profile = DlnaManager.GetDefaultProfile();
-            }
-
-            var audioCodec = state.ActualOutputAudioCodec;
-
-            if (state.VideoRequest == null)
-            {
-                responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile).BuildAudioHeader(
-                    state.OutputContainer,
-                    audioCodec,
-                    state.OutputAudioBitrate,
-                    state.OutputAudioSampleRate,
-                    state.OutputAudioChannels,
-                    state.OutputAudioBitDepth,
-                    isStaticallyStreamed,
-                    state.RunTimeTicks,
-                    state.TranscodeSeekInfo);
-            }
-            else
-            {
-                var videoCodec = state.ActualOutputVideoCodec;
-
-                responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile).BuildVideoHeader(
-                    state.OutputContainer,
-                    videoCodec,
-                    audioCodec,
-                    state.OutputWidth,
-                    state.OutputHeight,
-                    state.TargetVideoBitDepth,
-                    state.OutputVideoBitrate,
-                    state.TargetTimestamp,
-                    isStaticallyStreamed,
-                    state.RunTimeTicks,
-                    state.TargetVideoProfile,
-                    state.TargetVideoLevel,
-                    state.TargetFramerate,
-                    state.TargetPacketLength,
-                    state.TranscodeSeekInfo,
-                    state.IsTargetAnamorphic,
-                    state.IsTargetInterlaced,
-                    state.TargetRefFrames,
-                    state.TargetVideoStreamCount,
-                    state.TargetAudioStreamCount,
-                    state.TargetVideoCodecTag,
-                    state.IsTargetAVC).FirstOrDefault() ?? string.Empty;
-            }
-        }
-
-        private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
-        {
-            var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture);
-            var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture);
-
-            responseHeaders["TimeSeekRange.dlna.org"] = string.Format(
-                CultureInfo.InvariantCulture,
-                "npt={0}-{1}/{1}",
-                startSeconds,
-                runtimeSeconds);
-            responseHeaders["X-AvailableSeekRange"] = string.Format(
-                CultureInfo.InvariantCulture,
-                "1 npt={0}-{1}",
-                startSeconds,
-                runtimeSeconds);
-        }
-    }
-}

+ 0 - 344
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -1,344 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Serialization;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.Playback.Hls
-{
-    /// <summary>
-    /// Class BaseHlsService.
-    /// </summary>
-    public abstract class BaseHlsService : BaseStreamingService
-    {
-        public BaseHlsService(
-            ILogger<BaseHlsService> logger,
-            IServerConfigurationManager serverConfigurationManager,
-            IHttpResultFactory httpResultFactory,
-            IUserManager userManager,
-            ILibraryManager libraryManager,
-            IIsoManager isoManager,
-            IMediaEncoder mediaEncoder,
-            IFileSystem fileSystem,
-            IDlnaManager dlnaManager,
-            IDeviceManager deviceManager,
-            IMediaSourceManager mediaSourceManager,
-            IJsonSerializer jsonSerializer,
-            IAuthorizationContext authorizationContext,
-            EncodingHelper encodingHelper)
-            : base(
-                logger,
-                serverConfigurationManager,
-                httpResultFactory,
-                userManager,
-                libraryManager,
-                isoManager,
-                mediaEncoder,
-                fileSystem,
-                dlnaManager,
-                deviceManager,
-                mediaSourceManager,
-                jsonSerializer,
-                authorizationContext,
-                encodingHelper)
-        {
-        }
-
-        /// <summary>
-        /// Gets the audio arguments.
-        /// </summary>
-        protected abstract string GetAudioArguments(StreamState state, EncodingOptions encodingOptions);
-
-        /// <summary>
-        /// Gets the video arguments.
-        /// </summary>
-        protected abstract string GetVideoArguments(StreamState state, EncodingOptions encodingOptions);
-
-        /// <summary>
-        /// Gets the segment file extension.
-        /// </summary>
-        protected string GetSegmentFileExtension(StreamRequest request)
-        {
-            var segmentContainer = request.SegmentContainer;
-            if (!string.IsNullOrWhiteSpace(segmentContainer))
-            {
-                return "." + segmentContainer;
-            }
-
-            return ".ts";
-        }
-
-        /// <summary>
-        /// Gets the type of the transcoding job.
-        /// </summary>
-        /// <value>The type of the transcoding job.</value>
-        protected override TranscodingJobType TranscodingJobType => TranscodingJobType.Hls;
-
-        /// <summary>
-        /// Processes the request async.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <param name="isLive">if set to <c>true</c> [is live].</param>
-        /// <returns>Task{System.Object}.</returns>
-        /// <exception cref="ArgumentException">A video bitrate is required
-        /// or
-        /// An audio bitrate is required</exception>
-        protected async Task<object> ProcessRequestAsync(StreamRequest request, bool isLive)
-        {
-            var cancellationTokenSource = new CancellationTokenSource();
-
-            var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
-
-            TranscodingJob job = null;
-            var playlist = state.OutputFilePath;
-
-            if (!File.Exists(playlist))
-            {
-                var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlist);
-                await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
-                try
-                {
-                    if (!File.Exists(playlist))
-                    {
-                        // If the playlist doesn't already exist, startup ffmpeg
-                        try
-                        {
-                            job = await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false);
-                            job.IsLiveOutput = isLive;
-                        }
-                        catch
-                        {
-                            state.Dispose();
-                            throw;
-                        }
-
-                        var minSegments = state.MinSegments;
-                        if (minSegments > 0)
-                        {
-                            await WaitForMinimumSegmentCount(playlist, minSegments, cancellationTokenSource.Token).ConfigureAwait(false);
-                        }
-                    }
-                }
-                finally
-                {
-                    transcodingLock.Release();
-                }
-            }
-
-            if (isLive)
-            {
-                job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
-
-                if (job != null)
-                {
-                    ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
-                }
-
-                return ResultFactory.GetResult(GetLivePlaylistText(playlist, state.SegmentLength), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
-            }
-
-            var audioBitrate = state.OutputAudioBitrate ?? 0;
-            var videoBitrate = state.OutputVideoBitrate ?? 0;
-
-            var baselineStreamBitrate = 64000;
-
-            var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate);
-
-            job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
-
-            if (job != null)
-            {
-                ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
-            }
-
-            return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
-        }
-
-        private string GetLivePlaylistText(string path, int segmentLength)
-        {
-            using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
-            using var reader = new StreamReader(stream);
-
-            var text = reader.ReadToEnd();
-
-            text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
-
-            var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
-
-            text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
-            // text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
-
-            return text;
-        }
-
-        private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate)
-        {
-            var builder = new StringBuilder();
-
-            builder.AppendLine("#EXTM3U");
-
-            // Pad a little to satisfy the apple hls validator
-            var paddedBitrate = Convert.ToInt32(bitrate * 1.15);
-
-            // Main stream
-            builder.Append("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=")
-                .AppendLine(paddedBitrate.ToString(CultureInfo.InvariantCulture));
-            var playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "/stream.m3u8");
-            builder.AppendLine(playlistUrl);
-
-            return builder.ToString();
-        }
-
-        protected virtual async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken)
-        {
-            Logger.LogDebug("Waiting for {0} segments in {1}", segmentCount, playlist);
-
-            while (!cancellationToken.IsCancellationRequested)
-            {
-                try
-                {
-                    // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
-                    var fileStream = GetPlaylistFileStream(playlist);
-                    await using (fileStream.ConfigureAwait(false))
-                    {
-                        using var reader = new StreamReader(fileStream);
-                        var count = 0;
-
-                        while (!reader.EndOfStream)
-                        {
-                            var line = await reader.ReadLineAsync().ConfigureAwait(false);
-
-                            if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
-                            {
-                                count++;
-                                if (count >= segmentCount)
-                                {
-                                    Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
-                                    return;
-                                }
-                            }
-                        }
-                    }
-
-                    await Task.Delay(100, cancellationToken).ConfigureAwait(false);
-                }
-                catch (IOException)
-                {
-                    // May get an error if the file is locked
-                }
-
-                await Task.Delay(50, cancellationToken).ConfigureAwait(false);
-            }
-        }
-
-        protected Stream GetPlaylistFileStream(string path)
-        {
-            return new FileStream(
-                path,
-                FileMode.Open,
-                FileAccess.Read,
-                FileShare.ReadWrite,
-                IODefaults.FileStreamBufferSize,
-                FileOptions.SequentialScan);
-        }
-
-        protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
-        {
-            var itsOffsetMs = 0;
-
-            var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(CultureInfo.InvariantCulture));
-
-            var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
-
-            var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, videoCodec);
-
-            var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
-
-            // If isEncoding is true we're actually starting ffmpeg
-            var startNumberParam = isEncoding ? GetStartNumber(state).ToString(CultureInfo.InvariantCulture) : "0";
-
-            var baseUrlParam = string.Empty;
-
-            if (state.Request is GetLiveHlsStream)
-            {
-                baseUrlParam = string.Format(" -hls_base_url \"{0}/\"",
-                    "hls/" + Path.GetFileNameWithoutExtension(outputPath));
-            }
-
-            var useGenericSegmenter = true;
-            if (useGenericSegmenter)
-            {
-                var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
-
-                var timeDeltaParam = string.Empty;
-
-                var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.');
-                if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
-                {
-                    segmentFormat = "mpegts";
-                }
-
-                baseUrlParam = string.Format("\"{0}/\"", "hls/" + Path.GetFileNameWithoutExtension(outputPath));
-
-                return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_entry_prefix {12} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
-                    inputModifier,
-                    EncodingHelper.GetInputArgument(state, encodingOptions),
-                    threads,
-                    EncodingHelper.GetMapArgs(state),
-                    GetVideoArguments(state, encodingOptions),
-                    GetAudioArguments(state, encodingOptions),
-                    state.SegmentLength.ToString(CultureInfo.InvariantCulture),
-                    startNumberParam,
-                    outputPath,
-                    outputTsArg,
-                    timeDeltaParam,
-                    segmentFormat,
-                    baseUrlParam
-                ).Trim();
-            }
-
-            // add when stream copying?
-            // -avoid_negative_ts make_zero -fflags +genpts
-
-            var args = string.Format("{0} {1} {2} -map_metadata -1 -map_chapters -1 -threads {3} {4} {5} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero {6} -hls_time {7} -individual_header_trailer 0 -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
-                itsOffset,
-                inputModifier,
-                EncodingHelper.GetInputArgument(state, encodingOptions),
-                threads,
-                EncodingHelper.GetMapArgs(state),
-                GetVideoArguments(state, encodingOptions),
-                GetAudioArguments(state, encodingOptions),
-                state.SegmentLength.ToString(CultureInfo.InvariantCulture),
-                startNumberParam,
-                state.HlsListSize.ToString(CultureInfo.InvariantCulture),
-                baseUrlParam,
-                outputPath
-                ).Trim();
-
-            return args;
-        }
-
-        protected override string GetDefaultEncoderPreset()
-        {
-            return "veryfast";
-        }
-
-        protected virtual int GetStartNumber(StreamState state)
-        {
-            return 0;
-        }
-    }
-}

+ 0 - 1226
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -1,1226 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
-
-namespace MediaBrowser.Api.Playback.Hls
-{
-    /// <summary>
-    /// Options is needed for chromecast. Threw Head in there since it's related
-    /// </summary>
-    public class GetMasterHlsVideoPlaylist : VideoStreamRequest, IMasterHlsRequest
-    {
-        public bool EnableAdaptiveBitrateStreaming { get; set; }
-
-        public GetMasterHlsVideoPlaylist()
-        {
-            EnableAdaptiveBitrateStreaming = true;
-        }
-    }
-
-    public class GetMasterHlsAudioPlaylist : StreamRequest, IMasterHlsRequest
-    {
-        public bool EnableAdaptiveBitrateStreaming { get; set; }
-
-        public GetMasterHlsAudioPlaylist()
-        {
-            EnableAdaptiveBitrateStreaming = true;
-        }
-    }
-
-    public interface IMasterHlsRequest
-    {
-        bool EnableAdaptiveBitrateStreaming { get; set; }
-    }
-
-    public class GetVariantHlsVideoPlaylist : VideoStreamRequest
-    {
-    }
-
-    public class GetVariantHlsAudioPlaylist : StreamRequest
-    {
-    }
-
-    public class GetHlsVideoSegment : VideoStreamRequest
-    {
-        public string PlaylistId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the segment id.
-        /// </summary>
-        /// <value>The segment id.</value>
-        public string SegmentId { get; set; }
-    }
-
-    public class GetHlsAudioSegment : StreamRequest
-    {
-        public string PlaylistId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the segment id.
-        /// </summary>
-        /// <value>The segment id.</value>
-        public string SegmentId { get; set; }
-    }
-
-    [Authenticated]
-    public class DynamicHlsService : BaseHlsService
-    {
-        public DynamicHlsService(
-            ILogger<DynamicHlsService> logger,
-            IServerConfigurationManager serverConfigurationManager,
-            IHttpResultFactory httpResultFactory,
-            IUserManager userManager,
-            ILibraryManager libraryManager,
-            IIsoManager isoManager,
-            IMediaEncoder mediaEncoder,
-            IFileSystem fileSystem,
-            IDlnaManager dlnaManager,
-            IDeviceManager deviceManager,
-            IMediaSourceManager mediaSourceManager,
-            IJsonSerializer jsonSerializer,
-            IAuthorizationContext authorizationContext,
-            INetworkManager networkManager,
-            EncodingHelper encodingHelper)
-            : base(
-                logger,
-                serverConfigurationManager,
-                httpResultFactory,
-                userManager,
-                libraryManager,
-                isoManager,
-                mediaEncoder,
-                fileSystem,
-                dlnaManager,
-                deviceManager,
-                mediaSourceManager,
-                jsonSerializer,
-                authorizationContext,
-                encodingHelper)
-        {
-            NetworkManager = networkManager;
-        }
-
-        protected INetworkManager NetworkManager { get; private set; }
-
-        public Task<object> Get(GetMasterHlsVideoPlaylist request)
-        {
-            return GetMasterPlaylistInternal(request, "GET");
-        }
-
-        public Task<object> Head(GetMasterHlsVideoPlaylist request)
-        {
-            return GetMasterPlaylistInternal(request, "HEAD");
-        }
-
-        public Task<object> Get(GetMasterHlsAudioPlaylist request)
-        {
-            return GetMasterPlaylistInternal(request, "GET");
-        }
-
-        public Task<object> Head(GetMasterHlsAudioPlaylist request)
-        {
-            return GetMasterPlaylistInternal(request, "HEAD");
-        }
-
-        public Task<object> Get(GetVariantHlsVideoPlaylist request)
-        {
-            return GetVariantPlaylistInternal(request, true, "main");
-        }
-
-        public Task<object> Get(GetVariantHlsAudioPlaylist request)
-        {
-            return GetVariantPlaylistInternal(request, false, "main");
-        }
-
-        public Task<object> Get(GetHlsVideoSegment request)
-        {
-            return GetDynamicSegment(request, request.SegmentId);
-        }
-
-        public Task<object> Get(GetHlsAudioSegment request)
-        {
-            return GetDynamicSegment(request, request.SegmentId);
-        }
-
-        private async Task<object> GetDynamicSegment(StreamRequest request, string segmentId)
-        {
-            if ((request.StartTimeTicks ?? 0) > 0)
-            {
-                throw new ArgumentException("StartTimeTicks is not allowed.");
-            }
-
-            var cancellationTokenSource = new CancellationTokenSource();
-            var cancellationToken = cancellationTokenSource.Token;
-
-            var requestedIndex = int.Parse(segmentId, NumberStyles.Integer, CultureInfo.InvariantCulture);
-
-            var state = await GetState(request, cancellationToken).ConfigureAwait(false);
-
-            var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
-
-            var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex);
-
-            var segmentExtension = GetSegmentFileExtension(state.Request);
-
-            TranscodingJob job = null;
-
-            if (File.Exists(segmentPath))
-            {
-                job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
-                Logger.LogDebug("returning {0} [it exists, try 1]", segmentPath);
-                return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
-            }
-
-            var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlistPath);
-            await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
-            var released = false;
-            var startTranscoding = false;
-
-            try
-            {
-                if (File.Exists(segmentPath))
-                {
-                    job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
-                    transcodingLock.Release();
-                    released = true;
-                    Logger.LogDebug("returning {0} [it exists, try 2]", segmentPath);
-                    return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
-                }
-                else
-                {
-                    var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
-                    var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength;
-
-                    if (currentTranscodingIndex == null)
-                    {
-                        Logger.LogDebug("Starting transcoding because currentTranscodingIndex=null");
-                        startTranscoding = true;
-                    }
-                    else if (requestedIndex < currentTranscodingIndex.Value)
-                    {
-                        Logger.LogDebug("Starting transcoding because requestedIndex={0} and currentTranscodingIndex={1}", requestedIndex, currentTranscodingIndex);
-                        startTranscoding = true;
-                    }
-                    else if (requestedIndex - currentTranscodingIndex.Value > segmentGapRequiringTranscodingChange)
-                    {
-                        Logger.LogDebug("Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}", requestedIndex - currentTranscodingIndex.Value, segmentGapRequiringTranscodingChange, requestedIndex);
-                        startTranscoding = true;
-                    }
-
-                    if (startTranscoding)
-                    {
-                        // If the playlist doesn't already exist, startup ffmpeg
-                        try
-                        {
-                            await ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
-
-                            if (currentTranscodingIndex.HasValue)
-                            {
-                                DeleteLastFile(playlistPath, segmentExtension, 0);
-                            }
-
-                            request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex);
-
-                            state.WaitForPath = segmentPath;
-                            job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
-                        }
-                        catch
-                        {
-                            state.Dispose();
-                            throw;
-                        }
-
-                        // await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
-                    }
-                    else
-                    {
-                        job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
-                        if (job.TranscodingThrottler != null)
-                        {
-                            await job.TranscodingThrottler.UnpauseTranscoding();
-                        }
-                    }
-                }
-            }
-            finally
-            {
-                if (!released)
-                {
-                    transcodingLock.Release();
-                }
-            }
-
-            // Logger.LogInformation("waiting for {0}", segmentPath);
-            // while (!File.Exists(segmentPath))
-            //{
-            //    await Task.Delay(50, cancellationToken).ConfigureAwait(false);
-            //}
-
-            Logger.LogDebug("returning {0} [general case]", segmentPath);
-            job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
-            return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
-        }
-
-        private const int BufferSize = 81920;
-
-        private long GetStartPositionTicks(StreamState state, int requestedIndex)
-        {
-            double startSeconds = 0;
-            var lengths = GetSegmentLengths(state);
-
-            if (requestedIndex >= lengths.Length)
-            {
-                var msg = string.Format("Invalid segment index requested: {0} - Segment count: {1}", requestedIndex, lengths.Length);
-                throw new ArgumentException(msg);
-            }
-
-            for (var i = 0; i < requestedIndex; i++)
-            {
-                startSeconds += lengths[i];
-            }
-
-            var position = TimeSpan.FromSeconds(startSeconds).Ticks;
-            return position;
-        }
-
-        private long GetEndPositionTicks(StreamState state, int requestedIndex)
-        {
-            double startSeconds = 0;
-            var lengths = GetSegmentLengths(state);
-
-            if (requestedIndex >= lengths.Length)
-            {
-                var msg = string.Format("Invalid segment index requested: {0} - Segment count: {1}", requestedIndex, lengths.Length);
-                throw new ArgumentException(msg);
-            }
-
-            for (var i = 0; i <= requestedIndex; i++)
-            {
-                startSeconds += lengths[i];
-            }
-
-            var position = TimeSpan.FromSeconds(startSeconds).Ticks;
-            return position;
-        }
-
-        private double[] GetSegmentLengths(StreamState state)
-        {
-            var result = new List<double>();
-
-            var ticks = state.RunTimeTicks ?? 0;
-
-            var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks;
-
-            while (ticks > 0)
-            {
-                var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks;
-
-                result.Add(TimeSpan.FromTicks(length).TotalSeconds);
-
-                ticks -= length;
-            }
-
-            return result.ToArray();
-        }
-
-        public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
-        {
-            var job = ApiEntryPoint.Instance.GetTranscodingJob(playlist, TranscodingJobType);
-
-            if (job == null || job.HasExited)
-            {
-                return null;
-            }
-
-            var file = GetLastTranscodingFile(playlist, segmentExtension, FileSystem);
-
-            if (file == null)
-            {
-                return null;
-            }
-
-            var playlistFilename = Path.GetFileNameWithoutExtension(playlist);
-
-            var indexString = Path.GetFileNameWithoutExtension(file.Name).AsSpan().Slice(playlistFilename.Length);
-
-            return int.Parse(indexString, NumberStyles.Integer, CultureInfo.InvariantCulture);
-        }
-
-        private void DeleteLastFile(string playlistPath, string segmentExtension, int retryCount)
-        {
-            var file = GetLastTranscodingFile(playlistPath, segmentExtension, FileSystem);
-
-            if (file != null)
-            {
-                DeleteFile(file.FullName, retryCount);
-            }
-        }
-
-        private void DeleteFile(string path, int retryCount)
-        {
-            if (retryCount >= 5)
-            {
-                return;
-            }
-
-            Logger.LogDebug("Deleting partial HLS file {path}", path);
-
-            try
-            {
-                FileSystem.DeleteFile(path);
-            }
-            catch (IOException ex)
-            {
-                Logger.LogError(ex, "Error deleting partial stream file(s) {path}", path);
-
-                var task = Task.Delay(100);
-                Task.WaitAll(task);
-                DeleteFile(path, retryCount + 1);
-            }
-            catch (Exception ex)
-            {
-                Logger.LogError(ex, "Error deleting partial stream file(s) {path}", path);
-            }
-        }
-
-        private static FileSystemMetadata GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem)
-        {
-            var folder = Path.GetDirectoryName(playlist);
-
-            var filePrefix = Path.GetFileNameWithoutExtension(playlist) ?? string.Empty;
-
-            try
-            {
-                return fileSystem.GetFiles(folder, new[] { segmentExtension }, true, false)
-                    .Where(i => Path.GetFileNameWithoutExtension(i.Name).StartsWith(filePrefix, StringComparison.OrdinalIgnoreCase))
-                    .OrderByDescending(fileSystem.GetLastWriteTimeUtc)
-                    .FirstOrDefault();
-            }
-            catch (IOException)
-            {
-                return null;
-            }
-        }
-
-        protected override int GetStartNumber(StreamState state)
-        {
-            return GetStartNumber(state.VideoRequest);
-        }
-
-        private int GetStartNumber(VideoStreamRequest request)
-        {
-            var segmentId = "0";
-
-            if (request is GetHlsVideoSegment segmentRequest)
-            {
-                segmentId = segmentRequest.SegmentId;
-            }
-
-            return int.Parse(segmentId, NumberStyles.Integer, CultureInfo.InvariantCulture);
-        }
-
-        private string GetSegmentPath(StreamState state, string playlist, int index)
-        {
-            var folder = Path.GetDirectoryName(playlist);
-
-            var filename = Path.GetFileNameWithoutExtension(playlist);
-
-            return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + GetSegmentFileExtension(state.Request));
-        }
-
-        private async Task<object> GetSegmentResult(StreamState state,
-            string playlistPath,
-            string segmentPath,
-            string segmentExtension,
-            int segmentIndex,
-            TranscodingJob transcodingJob,
-            CancellationToken cancellationToken)
-        {
-            var segmentExists = File.Exists(segmentPath);
-            if (segmentExists)
-            {
-                if (transcodingJob != null && transcodingJob.HasExited)
-                {
-                    // Transcoding job is over, so assume all existing files are ready
-                    Logger.LogDebug("serving up {0} as transcode is over", segmentPath);
-                    return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
-                }
-
-                var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
-
-                // If requested segment is less than transcoding position, we can't transcode backwards, so assume it's ready
-                if (segmentIndex < currentTranscodingIndex)
-                {
-                    Logger.LogDebug("serving up {0} as transcode index {1} is past requested point {2}", segmentPath, currentTranscodingIndex, segmentIndex);
-                    return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
-                }
-            }
-
-            var nextSegmentPath = GetSegmentPath(state, playlistPath, segmentIndex + 1);
-            if (transcodingJob != null)
-            {
-                while (!cancellationToken.IsCancellationRequested && !transcodingJob.HasExited)
-                {
-                    // To be considered ready, the segment file has to exist AND
-                    // either the transcoding job should be done or next segment should also exist
-                    if (segmentExists)
-                    {
-                        if (transcodingJob.HasExited || File.Exists(nextSegmentPath))
-                        {
-                            Logger.LogDebug("serving up {0} as it deemed ready", segmentPath);
-                            return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
-                        }
-                    }
-                    else
-                    {
-                        segmentExists = File.Exists(segmentPath);
-                        if (segmentExists)
-                        {
-                            continue; // avoid unnecessary waiting if segment just became available
-                        }
-                    }
-
-                    await Task.Delay(100, cancellationToken).ConfigureAwait(false);
-                }
-
-                if (!File.Exists(segmentPath))
-                {
-                    Logger.LogWarning("cannot serve {0} as transcoding quit before we got there", segmentPath);
-                }
-                else
-                {
-                    Logger.LogDebug("serving {0} as it's on disk and transcoding stopped", segmentPath);
-                }
-
-                cancellationToken.ThrowIfCancellationRequested();
-            }
-            else
-            {
-                Logger.LogWarning("cannot serve {0} as it doesn't exist and no transcode is running", segmentPath);
-            }
-
-            return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
-        }
-
-        private Task<object> GetSegmentResult(StreamState state, string segmentPath, int index, TranscodingJob transcodingJob)
-        {
-            var segmentEndingPositionTicks = GetEndPositionTicks(state, index);
-
-            return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
-            {
-                Path = segmentPath,
-                FileShare = FileShare.ReadWrite,
-                OnComplete = () =>
-                {
-                    Logger.LogDebug("finished serving {0}", segmentPath);
-                    if (transcodingJob != null)
-                    {
-                        transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
-                        ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
-                    }
-                }
-            });
-        }
-
-        private async Task<object> GetMasterPlaylistInternal(StreamRequest request, string method)
-        {
-            var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
-
-            if (string.IsNullOrEmpty(request.MediaSourceId))
-            {
-                throw new ArgumentException("MediaSourceId is required");
-            }
-
-            var playlistText = string.Empty;
-
-            if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase))
-            {
-                var audioBitrate = state.OutputAudioBitrate ?? 0;
-                var videoBitrate = state.OutputVideoBitrate ?? 0;
-
-                playlistText = GetMasterPlaylistFileText(state, videoBitrate + audioBitrate);
-            }
-
-            return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
-        }
-
-        private string GetMasterPlaylistFileText(StreamState state, int totalBitrate)
-        {
-            var builder = new StringBuilder();
-
-            builder.AppendLine("#EXTM3U");
-
-            var isLiveStream = state.IsSegmentedLiveStream;
-
-            var queryStringIndex = Request.RawUrl.IndexOf('?');
-            var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
-
-            // from universal audio service
-            if (queryString.IndexOf("SegmentContainer", StringComparison.OrdinalIgnoreCase) == -1 && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer))
-            {
-                queryString += "&SegmentContainer=" + state.Request.SegmentContainer;
-            }
-            // from universal audio service
-            if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) && queryString.IndexOf("TranscodeReasons=", StringComparison.OrdinalIgnoreCase) == -1)
-            {
-                queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons;
-            }
-
-            // Main stream
-            var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8";
-
-            playlistUrl += queryString;
-
-            var request = state.Request;
-
-            var subtitleStreams = state.MediaSource
-                .MediaStreams
-                .Where(i => i.IsTextSubtitleStream)
-                .ToList();
-
-            var subtitleGroup = subtitleStreams.Count > 0 &&
-                request is GetMasterHlsVideoPlaylist &&
-                (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Hls || state.VideoRequest.EnableSubtitlesInManifest) ?
-                "subs" :
-                null;
-
-            // If we're burning in subtitles then don't add additional subs to the manifest
-            if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
-            {
-                subtitleGroup = null;
-            }
-
-            if (!string.IsNullOrWhiteSpace(subtitleGroup))
-            {
-                AddSubtitles(state, subtitleStreams, builder);
-            }
-
-            AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);
-
-            if (EnableAdaptiveBitrateStreaming(state, isLiveStream))
-            {
-                var requestedVideoBitrate = state.VideoRequest == null ? 0 : state.VideoRequest.VideoBitRate ?? 0;
-
-                // By default, vary by just 200k
-                var variation = GetBitrateVariation(totalBitrate);
-
-                var newBitrate = totalBitrate - variation;
-                var variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
-                AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
-
-                variation *= 2;
-                newBitrate = totalBitrate - variation;
-                variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
-                AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
-            }
-
-            return builder.ToString();
-        }
-
-        private string ReplaceBitrate(string url, int oldValue, int newValue)
-        {
-            return url.Replace(
-                "videobitrate=" + oldValue.ToString(CultureInfo.InvariantCulture),
-                "videobitrate=" + newValue.ToString(CultureInfo.InvariantCulture),
-                StringComparison.OrdinalIgnoreCase);
-        }
-
-        private void AddSubtitles(StreamState state, IEnumerable<MediaStream> subtitles, StringBuilder builder)
-        {
-            var selectedIndex = state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Hls ? (int?)null : state.SubtitleStream.Index;
-
-            foreach (var stream in subtitles)
-            {
-                const string format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},AUTOSELECT=YES,URI=\"{3}\",LANGUAGE=\"{4}\"";
-
-                var name = stream.DisplayTitle;
-
-                var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index;
-                var isForced = stream.IsForced;
-
-                var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&api_key={3}",
-                    state.Request.MediaSourceId,
-                    stream.Index.ToString(CultureInfo.InvariantCulture),
-                    30.ToString(CultureInfo.InvariantCulture),
-                    AuthorizationContext.GetAuthorizationInfo(Request).Token);
-
-                var line = string.Format(format,
-                    name,
-                    isDefault ? "YES" : "NO",
-                    isForced ? "YES" : "NO",
-                    url,
-                    stream.Language ?? "Unknown");
-
-                builder.AppendLine(line);
-            }
-        }
-
-        private bool EnableAdaptiveBitrateStreaming(StreamState state, bool isLiveStream)
-        {
-            // Within the local network this will likely do more harm than good.
-            if (Request.IsLocal || NetworkManager.IsInLocalNetwork(Request.RemoteIp))
-            {
-                return false;
-            }
-
-            if (state.Request is IMasterHlsRequest request && !request.EnableAdaptiveBitrateStreaming)
-            {
-                return false;
-            }
-
-            if (isLiveStream || string.IsNullOrWhiteSpace(state.MediaPath))
-            {
-                // Opening live streams is so slow it's not even worth it
-                return false;
-            }
-
-            if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
-            {
-                return false;
-            }
-
-            if (EncodingHelper.IsCopyCodec(state.OutputAudioCodec))
-            {
-                return false;
-            }
-
-            if (!state.IsOutputVideo)
-            {
-                return false;
-            }
-
-            // Having problems in android
-            return false;
-            // return state.VideoRequest.VideoBitRate.HasValue;
-        }
-
-        /// <summary>
-        /// Get the H.26X level of the output video stream.
-        /// </summary>
-        /// <param name="state">StreamState of the current stream.</param>
-        /// <returns>H.26X level of the output video stream.</returns>
-        private int? GetOutputVideoCodecLevel(StreamState state)
-        {
-            string levelString;
-            if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
-                && state.VideoStream.Level.HasValue)
-            {
-                levelString = state.VideoStream?.Level.ToString();
-            }
-            else
-            {
-                levelString = state.GetRequestedLevel(state.ActualOutputVideoCodec);
-            }
-
-            if (int.TryParse(levelString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedLevel))
-            {
-                return parsedLevel;
-            }
-
-            return null;
-        }
-
-        /// <summary>
-        /// Gets a formatted string of the output audio codec, for use in the CODECS field.
-        /// </summary>
-        /// <seealso cref="AppendPlaylistCodecsField(StringBuilder, StreamState)"/>
-        /// <seealso cref="GetPlaylistVideoCodecs(StreamState, string, int)"/>
-        /// <param name="state">StreamState of the current stream.</param>
-        /// <returns>Formatted audio codec string.</returns>
-        private string GetPlaylistAudioCodecs(StreamState state)
-        {
-
-            if (string.Equals(state.ActualOutputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase))
-            {
-                string profile = state.GetRequestedProfiles("aac").FirstOrDefault();
-
-                return HlsCodecStringFactory.GetAACString(profile);
-            }
-            else if (string.Equals(state.ActualOutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
-            {
-                return HlsCodecStringFactory.GetMP3String();
-            }
-            else if (string.Equals(state.ActualOutputAudioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
-            {
-                return HlsCodecStringFactory.GetAC3String();
-            }
-            else if (string.Equals(state.ActualOutputAudioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
-            {
-                return HlsCodecStringFactory.GetEAC3String();
-            }
-
-            return string.Empty;
-        }
-
-        /// <summary>
-        /// Gets a formatted string of the output video codec, for use in the CODECS field.
-        /// </summary>
-        /// <seealso cref="AppendPlaylistCodecsField(StringBuilder, StreamState)"/>
-        /// <seealso cref="GetPlaylistAudioCodecs(StreamState)"/>
-        /// <param name="state">StreamState of the current stream.</param>
-        /// <returns>Formatted video codec string.</returns>
-        private string GetPlaylistVideoCodecs(StreamState state, string codec, int level)
-        {
-            if (level == 0)
-            {
-                // This is 0 when there's no requested H.26X level in the device profile
-                // and the source is not encoded in H.26X
-                Logger.LogError("Got invalid H.26X level when building CODECS field for HLS master playlist");
-                return string.Empty;
-            }
-
-            if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
-            {
-                string profile = state.GetRequestedProfiles("h264").FirstOrDefault();
-
-                return HlsCodecStringFactory.GetH264String(profile, level);
-            }
-            else if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
-                    || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
-            {
-                string profile = state.GetRequestedProfiles("h265").FirstOrDefault();
-
-                return HlsCodecStringFactory.GetH265String(profile, level);
-            }
-
-            return string.Empty;
-        }
-
-        /// <summary>
-        /// Appends a CODECS field containing formatted strings of
-        /// the active streams output video and audio codecs.
-        /// </summary>
-        /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/>
-        /// <seealso cref="GetPlaylistVideoCodecs(StreamState, string, int)"/>
-        /// <seealso cref="GetPlaylistAudioCodecs(StreamState)"/>
-        /// <param name="builder">StringBuilder to append the field to.</param>
-        /// <param name="state">StreamState of the current stream.</param>
-        private void AppendPlaylistCodecsField(StringBuilder builder, StreamState state)
-        {
-            // Video
-            string videoCodecs = string.Empty;
-            int? videoCodecLevel = GetOutputVideoCodecLevel(state);
-            if (!string.IsNullOrEmpty(state.ActualOutputVideoCodec) && videoCodecLevel.HasValue)
-            {
-                videoCodecs = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value);
-            }
-
-            // Audio
-            string audioCodecs = string.Empty;
-            if (!string.IsNullOrEmpty(state.ActualOutputAudioCodec))
-            {
-                audioCodecs = GetPlaylistAudioCodecs(state);
-            }
-
-            StringBuilder codecs = new StringBuilder();
-
-            codecs.Append(videoCodecs)
-                .Append(',')
-                .Append(audioCodecs);
-
-            if (codecs.Length > 1)
-            {
-                builder.Append(",CODECS=\"")
-                    .Append(codecs)
-                    .Append('"');
-            }
-        }
-
-        /// <summary>
-        /// Appends a FRAME-RATE field containing the framerate of the output stream.
-        /// </summary>
-        /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/>
-        /// <param name="builder">StringBuilder to append the field to.</param>
-        /// <param name="state">StreamState of the current stream.</param>
-        private void AppendPlaylistFramerateField(StringBuilder builder, StreamState state)
-        {
-            double? framerate = null;
-            if (state.TargetFramerate.HasValue)
-            {
-                framerate = Math.Round(state.TargetFramerate.GetValueOrDefault(), 3);
-            }
-            else if (state.VideoStream?.RealFrameRate != null)
-            {
-                framerate = Math.Round(state.VideoStream.RealFrameRate.GetValueOrDefault(), 3);
-            }
-
-            if (framerate.HasValue)
-            {
-                builder.Append(",FRAME-RATE=")
-                    .Append(framerate.Value);
-            }
-        }
-
-        /// <summary>
-        /// Appends a RESOLUTION field containing the resolution of the output stream.
-        /// </summary>
-        /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/>
-        /// <param name="builder">StringBuilder to append the field to.</param>
-        /// <param name="state">StreamState of the current stream.</param>
-        private void AppendPlaylistResolutionField(StringBuilder builder, StreamState state)
-        {
-            if (state.OutputWidth.HasValue && state.OutputHeight.HasValue)
-            {
-                builder.Append(",RESOLUTION=")
-                    .Append(state.OutputWidth.GetValueOrDefault())
-                    .Append('x')
-                    .Append(state.OutputHeight.GetValueOrDefault());
-            }
-        }
-
-        private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string subtitleGroup)
-        {
-            builder.Append("#EXT-X-STREAM-INF:BANDWIDTH=")
-                .Append(bitrate.ToString(CultureInfo.InvariantCulture))
-                .Append(",AVERAGE-BANDWIDTH=")
-                .Append(bitrate.ToString(CultureInfo.InvariantCulture));
-
-            AppendPlaylistCodecsField(builder, state);
-
-            AppendPlaylistResolutionField(builder, state);
-
-            AppendPlaylistFramerateField(builder, state);
-
-            if (!string.IsNullOrWhiteSpace(subtitleGroup))
-            {
-                builder.Append(",SUBTITLES=\"")
-                    .Append(subtitleGroup)
-                    .Append('"');
-            }
-
-            builder.Append(Environment.NewLine);
-            builder.AppendLine(url);
-        }
-
-        private int GetBitrateVariation(int bitrate)
-        {
-            // By default, vary by just 50k
-            var variation = 50000;
-
-            if (bitrate >= 10000000)
-            {
-                variation = 2000000;
-            }
-            else if (bitrate >= 5000000)
-            {
-                variation = 1500000;
-            }
-            else if (bitrate >= 3000000)
-            {
-                variation = 1000000;
-            }
-            else if (bitrate >= 2000000)
-            {
-                variation = 500000;
-            }
-            else if (bitrate >= 1000000)
-            {
-                variation = 300000;
-            }
-            else if (bitrate >= 600000)
-            {
-                variation = 200000;
-            }
-            else if (bitrate >= 400000)
-            {
-                variation = 100000;
-            }
-
-            return variation;
-        }
-
-        private async Task<object> GetVariantPlaylistInternal(StreamRequest request, bool isOutputVideo, string name)
-        {
-            var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
-
-            var segmentLengths = GetSegmentLengths(state);
-
-            var builder = new StringBuilder();
-
-            builder.AppendLine("#EXTM3U");
-            builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
-            builder.AppendLine("#EXT-X-VERSION:3");
-            builder.Append("#EXT-X-TARGETDURATION:")
-                .AppendLine(Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength).ToString(CultureInfo.InvariantCulture));
-            builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
-
-            var queryStringIndex = Request.RawUrl.IndexOf('?');
-            var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
-
-            // if ((Request.UserAgent ?? string.Empty).IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1)
-            //{
-            //    queryString = string.Empty;
-            //}
-
-            var index = 0;
-
-            foreach (var length in segmentLengths)
-            {
-                builder.Append("#EXTINF:")
-                    .Append(length.ToString("0.0000", CultureInfo.InvariantCulture))
-                    .AppendLine(", nodesc");
-
-                builder.AppendFormat(
-                    CultureInfo.InvariantCulture,
-                    "hls1/{0}/{1}{2}{3}",
-                    name,
-                    index.ToString(CultureInfo.InvariantCulture),
-                    GetSegmentFileExtension(request),
-                    queryString).AppendLine();
-
-                index++;
-            }
-
-            builder.AppendLine("#EXT-X-ENDLIST");
-
-            var playlistText = builder.ToString();
-
-            return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
-        }
-
-        protected override string GetAudioArguments(StreamState state, EncodingOptions encodingOptions)
-        {
-            var audioCodec = EncodingHelper.GetAudioEncoder(state);
-
-            if (!state.IsOutputVideo)
-            {
-                if (EncodingHelper.IsCopyCodec(audioCodec))
-                {
-                    return "-acodec copy";
-                }
-
-                var audioTranscodeParams = new List<string>();
-
-                audioTranscodeParams.Add("-acodec " + audioCodec);
-
-                if (state.OutputAudioBitrate.HasValue)
-                {
-                    audioTranscodeParams.Add("-ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture));
-                }
-
-                if (state.OutputAudioChannels.HasValue)
-                {
-                    audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
-                }
-
-                if (state.OutputAudioSampleRate.HasValue)
-                {
-                    audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture));
-                }
-
-                audioTranscodeParams.Add("-vn");
-                return string.Join(" ", audioTranscodeParams.ToArray());
-            }
-
-            if (EncodingHelper.IsCopyCodec(audioCodec))
-            {
-                var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
-
-                if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec))
-                {
-                    return "-codec:a:0 copy -copypriorss:a:0 0";
-                }
-
-                return "-codec:a:0 copy";
-            }
-
-            var args = "-codec:a:0 " + audioCodec;
-
-            var channels = state.OutputAudioChannels;
-
-            if (channels.HasValue)
-            {
-                args += " -ac " + channels.Value;
-            }
-
-            var bitrate = state.OutputAudioBitrate;
-
-            if (bitrate.HasValue)
-            {
-                args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
-            }
-
-            if (state.OutputAudioSampleRate.HasValue)
-            {
-                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
-            }
-
-            args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true);
-
-            return args;
-        }
-
-        protected override string GetVideoArguments(StreamState state, EncodingOptions encodingOptions)
-        {
-            if (!state.IsOutputVideo)
-            {
-                return string.Empty;
-            }
-
-            var codec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
-
-            var args = "-codec:v:0 " + codec;
-
-            // if (state.EnableMpegtsM2TsMode)
-            // {
-            //     args += " -mpegts_m2ts_mode 1";
-            // }
-
-            // See if we can save come cpu cycles by avoiding encoding
-            if (EncodingHelper.IsCopyCodec(codec))
-            {
-                if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
-                {
-                    string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream);
-                    if (!string.IsNullOrEmpty(bitStreamArgs))
-                    {
-                        args += " " + bitStreamArgs;
-                    }
-                }
-
-                // args += " -flags -global_header";
-            }
-            else
-            {
-                var gopArg = string.Empty;
-                var keyFrameArg = string.Format(
-                    CultureInfo.InvariantCulture,
-                    " -force_key_frames:0 \"expr:gte(t,{0}+n_forced*{1})\"",
-                    GetStartNumber(state) * state.SegmentLength,
-                    state.SegmentLength);
-
-                var framerate = state.VideoStream?.RealFrameRate;
-
-                if (framerate.HasValue)
-                {
-                    // This is to make sure keyframe interval is limited to our segment,
-                    // as forcing keyframes is not enough.
-                    // Example: we encoded half of desired length, then codec detected
-                    // scene cut and inserted a keyframe; next forced keyframe would
-                    // be created outside of segment, which breaks seeking
-                    // -sc_threshold 0 is used to prevent the hardware encoder from post processing to break the set keyframe
-                    gopArg = string.Format(
-                        CultureInfo.InvariantCulture,
-                        " -g {0} -keyint_min {0} -sc_threshold 0",
-                        Math.Ceiling(state.SegmentLength * framerate.Value)
-                    );
-                }
-
-                args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultEncoderPreset());
-
-                // Unable to force key frames using these hw encoders, set key frames by GOP
-                if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
-                    || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
-                    || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase))
-                {
-                    args += " " + gopArg;
-                }
-                else
-                {
-                    args += " " + keyFrameArg + gopArg;
-                }
-
-                // args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
-
-                var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
-
-                // This is for graphical subs
-                if (hasGraphicalSubs)
-                {
-                    args += EncodingHelper.GetGraphicalSubtitleParam(state, encodingOptions, codec);
-                }
-                // Add resolution params, if specified
-                else
-                {
-                    args += EncodingHelper.GetOutputSizeParam(state, encodingOptions, codec);
-                }
-
-                // -start_at_zero is necessary to use with -ss when seeking,
-                // otherwise the target position cannot be determined.
-                if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream))
-                {
-                    args += " -start_at_zero";
-                }
-
-                // args += " -flags -global_header";
-            }
-
-            if (!string.IsNullOrEmpty(state.OutputVideoSync))
-            {
-                args += " -vsync " + state.OutputVideoSync;
-            }
-
-            args += EncodingHelper.GetOutputFFlags(state);
-
-            return args;
-        }
-
-        protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
-        {
-            var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
-
-            var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, videoCodec);
-
-            if (state.BaseRequest.BreakOnNonKeyFrames)
-            {
-                // FIXME: this is actually a workaround, as ideally it really should be the client which decides whether non-keyframe
-                //        breakpoints are supported; but current implementation always uses "ffmpeg input seeking" which is liable
-                //        to produce a missing part of video stream before first keyframe is encountered, which may lead to
-                //        awkward cases like a few starting HLS segments having no video whatsoever, which breaks hls.js
-                Logger.LogInformation("Current HLS implementation doesn't support non-keyframe breaks but one is requested, ignoring that request");
-                state.BaseRequest.BreakOnNonKeyFrames = false;
-            }
-
-            var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
-
-            // If isEncoding is true we're actually starting ffmpeg
-            var startNumber = GetStartNumber(state);
-            var startNumberParam = isEncoding ? startNumber.ToString(CultureInfo.InvariantCulture) : "0";
-
-            var mapArgs = state.IsOutputVideo ? EncodingHelper.GetMapArgs(state) : string.Empty;
-
-            var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
-
-            var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.');
-            if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
-            {
-                segmentFormat = "mpegts";
-            }
-
-            return string.Format(
-                "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
-                inputModifier,
-                EncodingHelper.GetInputArgument(state, encodingOptions),
-                threads,
-                mapArgs,
-                GetVideoArguments(state, encodingOptions),
-                GetAudioArguments(state, encodingOptions),
-                state.SegmentLength.ToString(CultureInfo.InvariantCulture),
-                segmentFormat,
-                startNumberParam,
-                outputTsArg,
-                outputPath
-            ).Trim();
-        }
-    }
-}

+ 0 - 126
MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs

@@ -1,126 +0,0 @@
-using System;
-using System.Text;
-
-
-namespace MediaBrowser.Api.Playback
-{
-    /// <summary>
-    /// Get various codec strings for use in HLS playlists.
-    /// </summary>
-    static class HlsCodecStringFactory
-    {
-
-        /// <summary>
-        /// Gets a MP3 codec string.
-        /// </summary>
-        /// <returns>MP3 codec string.</returns>
-        public static string GetMP3String()
-        {
-            return "mp4a.40.34";
-        }
-
-        /// <summary>
-        /// Gets an AAC codec string.
-        /// </summary>
-        /// <param name="profile">AAC profile.</param>
-        /// <returns>AAC codec string.</returns>
-        public static string GetAACString(string profile)
-        {
-            StringBuilder result = new StringBuilder("mp4a", 9);
-
-            if (string.Equals(profile, "HE", StringComparison.OrdinalIgnoreCase))
-            {
-                result.Append(".40.5");
-            }
-            else
-            {
-                // Default to LC if profile is invalid
-                result.Append(".40.2");
-            }
-
-            return result.ToString();
-        }
-
-        /// <summary>
-        /// Gets a H.264 codec string.
-        /// </summary>
-        /// <param name="profile">H.264 profile.</param>
-        /// <param name="level">H.264 level.</param>
-        /// <returns>H.264 string.</returns>
-        public static string GetH264String(string profile, int level)
-        {
-            StringBuilder result = new StringBuilder("avc1", 11);
-
-            if (string.Equals(profile, "high", StringComparison.OrdinalIgnoreCase))
-            {
-                result.Append(".6400");
-            }
-            else if (string.Equals(profile, "main", StringComparison.OrdinalIgnoreCase))
-            {
-                result.Append(".4D40");
-            }
-            else if (string.Equals(profile, "baseline", StringComparison.OrdinalIgnoreCase))
-            {
-                result.Append(".42E0");
-            }
-            else
-            {
-                // Default to constrained baseline if profile is invalid
-                result.Append(".4240");
-            }
-
-            string levelHex = level.ToString("X2");
-            result.Append(levelHex);
-
-            return result.ToString();
-        }
-
-        /// <summary>
-        /// Gets a H.265 codec string.
-        /// </summary>
-        /// <param name="profile">H.265 profile.</param>
-        /// <param name="level">H.265 level.</param>
-        /// <returns>H.265 string.</returns>
-        public static string GetH265String(string profile, int level)
-        {
-            // The h265 syntax is a bit of a mystery at the time this comment was written.
-            // This is what I've found through various sources:
-            // FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN]
-            StringBuilder result = new StringBuilder("hev1", 16);
-
-            if (string.Equals(profile, "main10", StringComparison.OrdinalIgnoreCase))
-            {
-                result.Append(".2.6");
-            }
-            else
-            {
-                // Default to main if profile is invalid
-                result.Append(".1.6");
-            }
-
-            result.Append(".L")
-                .Append(level * 3)
-                .Append(".B0");
-
-            return result.ToString();
-        }
-
-        /// <summary>
-        /// Gets an AC-3 codec string.
-        /// </summary>
-        /// <returns>AC-3 codec string.</returns>
-        public static string GetAC3String()
-        {
-            return "mp4a.a5";
-        }
-
-        /// <summary>
-        /// Gets an E-AC-3 codec string.
-        /// </summary>
-        /// <returns>E-AC-3 codec string.</returns>
-        public static string GetEAC3String()
-        {
-            return "mp4a.a6";
-        }
-    }
-}

+ 0 - 6
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -1,6 +0,0 @@
-namespace MediaBrowser.Api.Playback.Hls
-{
-    public class GetLiveHlsStream : VideoStreamRequest
-    {
-    }
-}

+ 0 - 442
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -1,442 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-
-namespace MediaBrowser.Api.Playback.Progressive
-{
-    /// <summary>
-    /// Class BaseProgressiveStreamingService.
-    /// </summary>
-    public abstract class BaseProgressiveStreamingService : BaseStreamingService
-    {
-        protected IHttpClient HttpClient { get; private set; }
-
-        public BaseProgressiveStreamingService(
-            ILogger<BaseProgressiveStreamingService> logger,
-            IServerConfigurationManager serverConfigurationManager,
-            IHttpResultFactory httpResultFactory,
-            IHttpClient httpClient,
-            IUserManager userManager,
-            ILibraryManager libraryManager,
-            IIsoManager isoManager,
-            IMediaEncoder mediaEncoder,
-            IFileSystem fileSystem,
-            IDlnaManager dlnaManager,
-            IDeviceManager deviceManager,
-            IMediaSourceManager mediaSourceManager,
-            IJsonSerializer jsonSerializer,
-            IAuthorizationContext authorizationContext,
-            EncodingHelper encodingHelper)
-            : base(
-                logger,
-                serverConfigurationManager,
-                httpResultFactory,
-                userManager,
-                libraryManager,
-                isoManager,
-                mediaEncoder,
-                fileSystem,
-                dlnaManager,
-                deviceManager,
-                mediaSourceManager,
-                jsonSerializer,
-                authorizationContext,
-                encodingHelper)
-        {
-            HttpClient = httpClient;
-        }
-
-        /// <summary>
-        /// Gets the output file extension.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        /// <returns>System.String.</returns>
-        protected override string GetOutputFileExtension(StreamState state)
-        {
-            var ext = base.GetOutputFileExtension(state);
-
-            if (!string.IsNullOrEmpty(ext))
-            {
-                return ext;
-            }
-
-            var isVideoRequest = state.VideoRequest != null;
-
-            // Try to infer based on the desired video codec
-            if (isVideoRequest)
-            {
-                var videoCodec = state.VideoRequest.VideoCodec;
-
-                if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) ||
-                    string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase))
-                {
-                    return ".ts";
-                }
-
-                if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
-                {
-                    return ".ogv";
-                }
-
-                if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
-                {
-                    return ".webm";
-                }
-
-                if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
-                {
-                    return ".asf";
-                }
-            }
-
-            // Try to infer based on the desired audio codec
-            if (!isVideoRequest)
-            {
-                var audioCodec = state.Request.AudioCodec;
-
-                if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
-                {
-                    return ".aac";
-                }
-
-                if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase))
-                {
-                    return ".mp3";
-                }
-
-                if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase))
-                {
-                    return ".ogg";
-                }
-
-                if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase))
-                {
-                    return ".wma";
-                }
-            }
-
-            return null;
-        }
-
-        /// <summary>
-        /// Gets the type of the transcoding job.
-        /// </summary>
-        /// <value>The type of the transcoding job.</value>
-        protected override TranscodingJobType TranscodingJobType => TranscodingJobType.Progressive;
-
-        /// <summary>
-        /// Processes the request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
-        /// <returns>Task.</returns>
-        protected async Task<object> ProcessRequest(StreamRequest request, bool isHeadRequest)
-        {
-            var cancellationTokenSource = new CancellationTokenSource();
-
-            var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
-
-            var responseHeaders = new Dictionary<string, string>();
-
-            if (request.Static && state.DirectStreamProvider != null)
-            {
-                AddDlnaHeaders(state, responseHeaders, true);
-
-                using (state)
-                {
-                    var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
-                    // TODO: Don't hardcode this
-                    outputHeaders[HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file.ts");
-
-                    return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, CancellationToken.None)
-                    {
-                        AllowEndOfFile = false
-                    };
-                }
-            }
-
-            // Static remote stream
-            if (request.Static && state.InputProtocol == MediaProtocol.Http)
-            {
-                AddDlnaHeaders(state, responseHeaders, true);
-
-                using (state)
-                {
-                    return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false);
-                }
-            }
-
-            if (request.Static && state.InputProtocol != MediaProtocol.File)
-            {
-                throw new ArgumentException(string.Format("Input protocol {0} cannot be streamed statically.", state.InputProtocol));
-            }
-
-            var outputPath = state.OutputFilePath;
-            var outputPathExists = File.Exists(outputPath);
-
-            var transcodingJob = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
-            var isTranscodeCached = outputPathExists && transcodingJob != null;
-
-            AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached);
-
-            // Static stream
-            if (request.Static)
-            {
-                var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath);
-
-                using (state)
-                {
-                    if (state.MediaSource.IsInfiniteStream)
-                    {
-                        var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
-                        {
-                            [HeaderNames.ContentType] = contentType
-                        };
-
-
-                        return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, CancellationToken.None)
-                        {
-                            AllowEndOfFile = false
-                        };
-                    }
-
-                    TimeSpan? cacheDuration = null;
-
-                    if (!string.IsNullOrEmpty(request.Tag))
-                    {
-                        cacheDuration = TimeSpan.FromDays(365);
-                    }
-
-                    return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
-                    {
-                        ResponseHeaders = responseHeaders,
-                        ContentType = contentType,
-                        IsHeadRequest = isHeadRequest,
-                        Path = state.MediaPath,
-                        CacheDuration = cacheDuration
-
-                    }).ConfigureAwait(false);
-                }
-            }
-
-            //// Not static but transcode cache file exists
-            // if (isTranscodeCached && state.VideoRequest == null)
-            //{
-            //    var contentType = state.GetMimeType(outputPath);
-
-            //    try
-            //    {
-            //        if (transcodingJob != null)
-            //        {
-            //            ApiEntryPoint.Instance.OnTranscodeBeginRequest(transcodingJob);
-            //        }
-
-            //        return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
-            //        {
-            //            ResponseHeaders = responseHeaders,
-            //            ContentType = contentType,
-            //            IsHeadRequest = isHeadRequest,
-            //            Path = outputPath,
-            //            FileShare = FileShare.ReadWrite,
-            //            OnComplete = () =>
-            //            {
-            //                if (transcodingJob != null)
-            //                {
-            //                    ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
-            //                }
-            //            }
-
-            //        }).ConfigureAwait(false);
-            //    }
-            //    finally
-            //    {
-            //        state.Dispose();
-            //    }
-            //}
-
-            // Need to start ffmpeg
-            try
-            {
-                return await GetStreamResult(request, state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false);
-            }
-            catch
-            {
-                state.Dispose();
-
-                throw;
-            }
-        }
-
-        /// <summary>
-        /// Gets the static remote stream result.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        /// <param name="responseHeaders">The response headers.</param>
-        /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
-        /// <param name="cancellationTokenSource">The cancellation token source.</param>
-        /// <returns>Task{System.Object}.</returns>
-        private async Task<object> GetStaticRemoteStreamResult(
-            StreamState state,
-            Dictionary<string, string> responseHeaders,
-            bool isHeadRequest,
-            CancellationTokenSource cancellationTokenSource)
-        {
-            var options = new HttpRequestOptions
-            {
-                Url = state.MediaPath,
-                BufferContent = false,
-                CancellationToken = cancellationTokenSource.Token
-            };
-
-            if (state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent))
-            {
-                options.UserAgent = useragent;
-            }
-
-            var response = await HttpClient.GetResponse(options).ConfigureAwait(false);
-
-            responseHeaders[HeaderNames.AcceptRanges] = "none";
-
-            // Seeing cases of -1 here
-            if (response.ContentLength.HasValue && response.ContentLength.Value >= 0)
-            {
-                responseHeaders[HeaderNames.ContentLength] = response.ContentLength.Value.ToString(CultureInfo.InvariantCulture);
-            }
-
-            if (isHeadRequest)
-            {
-                using (response)
-                {
-                    return ResultFactory.GetResult(null, Array.Empty<byte>(), response.ContentType, responseHeaders);
-                }
-            }
-
-            var result = new StaticRemoteStreamWriter(response);
-
-            result.Headers[HeaderNames.ContentType] = response.ContentType;
-
-            // Add the response headers to the result object
-            foreach (var header in responseHeaders)
-            {
-                result.Headers[header.Key] = header.Value;
-            }
-
-            return result;
-        }
-
-        /// <summary>
-        /// Gets the stream result.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        /// <param name="responseHeaders">The response headers.</param>
-        /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
-        /// <param name="cancellationTokenSource">The cancellation token source.</param>
-        /// <returns>Task{System.Object}.</returns>
-        private async Task<object> GetStreamResult(StreamRequest request, StreamState state, IDictionary<string, string> responseHeaders, bool isHeadRequest, CancellationTokenSource cancellationTokenSource)
-        {
-            // Use the command line args with a dummy playlist path
-            var outputPath = state.OutputFilePath;
-
-            responseHeaders[HeaderNames.AcceptRanges] = "none";
-
-            var contentType = state.GetMimeType(outputPath);
-
-            // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response
-            var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null;
-
-            if (contentLength.HasValue)
-            {
-                responseHeaders[HeaderNames.ContentLength] = contentLength.Value.ToString(CultureInfo.InvariantCulture);
-            }
-
-            // Headers only
-            if (isHeadRequest)
-            {
-                var streamResult = ResultFactory.GetResult(null, Array.Empty<byte>(), contentType, responseHeaders);
-
-                if (streamResult is IHasHeaders hasHeaders)
-                {
-                    if (contentLength.HasValue)
-                    {
-                        hasHeaders.Headers[HeaderNames.ContentLength] = contentLength.Value.ToString(CultureInfo.InvariantCulture);
-                    }
-                    else
-                    {
-                        hasHeaders.Headers.Remove(HeaderNames.ContentLength);
-                    }
-                }
-
-                return streamResult;
-            }
-
-            var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath);
-            await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
-            try
-            {
-                TranscodingJob job;
-
-                if (!File.Exists(outputPath))
-                {
-                    job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
-                }
-                else
-                {
-                    job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
-                    state.Dispose();
-                }
-
-                var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
-                {
-                    [HeaderNames.ContentType] = contentType
-                };
-
-
-                // Add the response headers to the result object
-                foreach (var item in responseHeaders)
-                {
-                    outputHeaders[item.Key] = item.Value;
-                }
-
-                return new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, CancellationToken.None);
-            }
-            finally
-            {
-                transcodingLock.Release();
-            }
-        }
-
-        /// <summary>
-        /// Gets the length of the estimated content.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        /// <returns>System.Nullable{System.Int64}.</returns>
-        private long? GetEstimatedContentLength(StreamState state)
-        {
-            var totalBitrate = state.TotalOutputBitrate ?? 0;
-
-            if (totalBitrate > 0 && state.RunTimeTicks.HasValue)
-            {
-                return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8);
-            }
-
-            return null;
-        }
-    }
-}

+ 0 - 182
MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs

@@ -1,182 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Services;
-using MediaBrowser.Model.System;
-using Microsoft.Extensions.Logging;
-using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
-
-namespace MediaBrowser.Api.Playback.Progressive
-{
-    public class ProgressiveFileCopier : IAsyncStreamWriter, IHasHeaders
-    {
-        private readonly IFileSystem _fileSystem;
-        private readonly TranscodingJob _job;
-        private readonly ILogger _logger;
-        private readonly string _path;
-        private readonly CancellationToken _cancellationToken;
-        private readonly Dictionary<string, string> _outputHeaders;
-
-        private long _bytesWritten = 0;
-        public long StartPosition { get; set; }
-
-        public bool AllowEndOfFile = true;
-
-        private readonly IDirectStreamProvider _directStreamProvider;
-
-        public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken)
-        {
-            _fileSystem = fileSystem;
-            _path = path;
-            _outputHeaders = outputHeaders;
-            _job = job;
-            _logger = logger;
-            _cancellationToken = cancellationToken;
-        }
-
-        public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken)
-        {
-            _directStreamProvider = directStreamProvider;
-            _outputHeaders = outputHeaders;
-            _job = job;
-            _logger = logger;
-            _cancellationToken = cancellationToken;
-        }
-
-        public IDictionary<string, string> Headers => _outputHeaders;
-
-        private Stream GetInputStream(bool allowAsyncFileRead)
-        {
-            var fileOptions = FileOptions.SequentialScan;
-
-            if (allowAsyncFileRead)
-            {
-                fileOptions |= FileOptions.Asynchronous;
-            }
-
-            return new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions);
-        }
-
-        public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
-        {
-            cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token;
-
-            try
-            {
-                if (_directStreamProvider != null)
-                {
-                    await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false);
-                    return;
-                }
-
-                var eofCount = 0;
-
-                // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
-                var allowAsyncFileRead = OperatingSystem.Id != OperatingSystemId.Windows;
-
-                using (var inputStream = GetInputStream(allowAsyncFileRead))
-                {
-                    if (StartPosition > 0)
-                    {
-                        inputStream.Position = StartPosition;
-                    }
-
-                    while (eofCount < 20 || !AllowEndOfFile)
-                    {
-                        int bytesRead;
-                        if (allowAsyncFileRead)
-                        {
-                            bytesRead = await CopyToInternalAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
-                        }
-                        else
-                        {
-                            bytesRead = await CopyToInternalAsyncWithSyncRead(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
-                        }
-
-                        // var position = fs.Position;
-                        // _logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
-
-                        if (bytesRead == 0)
-                        {
-                            if (_job == null || _job.HasExited)
-                            {
-                                eofCount++;
-                            }
-
-                            await Task.Delay(100, cancellationToken).ConfigureAwait(false);
-                        }
-                        else
-                        {
-                            eofCount = 0;
-                        }
-                    }
-                }
-            }
-            finally
-            {
-                if (_job != null)
-                {
-                    ApiEntryPoint.Instance.OnTranscodeEndRequest(_job);
-                }
-            }
-        }
-
-        private async Task<int> CopyToInternalAsyncWithSyncRead(Stream source, Stream destination, CancellationToken cancellationToken)
-        {
-            var array = new byte[IODefaults.CopyToBufferSize];
-            int bytesRead;
-            int totalBytesRead = 0;
-
-            while ((bytesRead = source.Read(array, 0, array.Length)) != 0)
-            {
-                var bytesToWrite = bytesRead;
-
-                if (bytesToWrite > 0)
-                {
-                    await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
-
-                    _bytesWritten += bytesRead;
-                    totalBytesRead += bytesRead;
-
-                    if (_job != null)
-                    {
-                        _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
-                    }
-                }
-            }
-
-            return totalBytesRead;
-        }
-
-        private async Task<int> CopyToInternalAsync(Stream source, Stream destination, CancellationToken cancellationToken)
-        {
-            var array = new byte[IODefaults.CopyToBufferSize];
-            int bytesRead;
-            int totalBytesRead = 0;
-
-            while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
-            {
-                var bytesToWrite = bytesRead;
-
-                if (bytesToWrite > 0)
-                {
-                    await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
-
-                    _bytesWritten += bytesRead;
-                    totalBytesRead += bytesRead;
-
-                    if (_job != null)
-                    {
-                        _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
-                    }
-                }
-            }
-
-            return totalBytesRead;
-        }
-    }
-}

+ 0 - 88
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -1,88 +0,0 @@
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.Playback.Progressive
-{
-    public class GetVideoStream : VideoStreamRequest
-    {
-    }
-
-    /// <summary>
-    /// Class VideoService.
-    /// </summary>
-    // TODO: In order to autheneticate this in the future, Dlna playback will require updating
-    //[Authenticated]
-    public class VideoService : BaseProgressiveStreamingService
-    {
-        public VideoService(
-            ILogger<VideoService> logger,
-            IServerConfigurationManager serverConfigurationManager,
-            IHttpResultFactory httpResultFactory,
-            IHttpClient httpClient,
-            IUserManager userManager,
-            ILibraryManager libraryManager,
-            IIsoManager isoManager,
-            IMediaEncoder mediaEncoder,
-            IFileSystem fileSystem,
-            IDlnaManager dlnaManager,
-            IDeviceManager deviceManager,
-            IMediaSourceManager mediaSourceManager,
-            IJsonSerializer jsonSerializer,
-            IAuthorizationContext authorizationContext,
-            EncodingHelper encodingHelper)
-            : base(
-                logger,
-                serverConfigurationManager,
-                httpResultFactory,
-                httpClient,
-                userManager,
-                libraryManager,
-                isoManager,
-                mediaEncoder,
-                fileSystem,
-                dlnaManager,
-                deviceManager,
-                mediaSourceManager,
-                jsonSerializer,
-                authorizationContext,
-                encodingHelper)
-        {
-        }
-
-        /// <summary>
-        /// Gets the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>System.Object.</returns>
-        public Task<object> Get(GetVideoStream request)
-        {
-            return ProcessRequest(request, false);
-        }
-
-        /// <summary>
-        /// Heads the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>System.Object.</returns>
-        public Task<object> Head(GetVideoStream request)
-        {
-            return ProcessRequest(request, true);
-        }
-
-        protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
-        {
-            return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultEncoderPreset());
-        }
-    }
-}

+ 0 - 44
MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs

@@ -1,44 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Api.Playback
-{
-    /// <summary>
-    /// Class StaticRemoteStreamWriter.
-    /// </summary>
-    public class StaticRemoteStreamWriter : IAsyncStreamWriter, IHasHeaders
-    {
-        /// <summary>
-        /// The _input stream.
-        /// </summary>
-        private readonly HttpResponseInfo _response;
-
-        /// <summary>
-        /// The _options.
-        /// </summary>
-        private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
-
-        public StaticRemoteStreamWriter(HttpResponseInfo response)
-        {
-            _response = response;
-        }
-
-        /// <summary>
-        /// Gets the options.
-        /// </summary>
-        /// <value>The options.</value>
-        public IDictionary<string, string> Headers => _options;
-
-        public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
-        {
-            using (_response)
-            {
-                await _response.Content.CopyToAsync(responseStream, 81920, cancellationToken).ConfigureAwait(false);
-            }
-        }
-    }
-}

+ 0 - 37
MediaBrowser.Api/Playback/StreamRequest.cs

@@ -1,37 +0,0 @@
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Api.Playback
-{
-    /// <summary>
-    /// Class StreamRequest.
-    /// </summary>
-    public class StreamRequest : BaseEncodingJobOptions
-    {
-        [ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string DeviceProfileId { get; set; }
-
-        public string Params { get; set; }
-
-        public string PlaySessionId { get; set; }
-
-        public string Tag { get; set; }
-
-        public string SegmentContainer { get; set; }
-
-        public int? SegmentLength { get; set; }
-
-        public int? MinSegments { get; set; }
-    }
-
-    public class VideoStreamRequest : StreamRequest
-    {
-        /// <summary>
-        /// Gets a value indicating whether this instance has fixed resolution.
-        /// </summary>
-        /// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
-        public bool HasFixedResolution => Width.HasValue || Height.HasValue;
-
-        public bool EnableSubtitlesInManifest { get; set; }
-    }
-}

+ 0 - 143
MediaBrowser.Api/Playback/StreamState.cs

@@ -1,143 +0,0 @@
-using System;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Dlna;
-
-namespace MediaBrowser.Api.Playback
-{
-    public class StreamState : EncodingJobInfo, IDisposable
-    {
-        private readonly IMediaSourceManager _mediaSourceManager;
-        private bool _disposed = false;
-
-        public string RequestedUrl { get; set; }
-
-        public StreamRequest Request
-        {
-            get => (StreamRequest)BaseRequest;
-            set
-            {
-                BaseRequest = value;
-
-                IsVideoRequest = VideoRequest != null;
-            }
-        }
-
-        public TranscodingThrottler TranscodingThrottler { get; set; }
-
-        public VideoStreamRequest VideoRequest => Request as VideoStreamRequest;
-
-        public IDirectStreamProvider DirectStreamProvider { get; set; }
-
-        public string WaitForPath { get; set; }
-
-        public bool IsOutputVideo => Request is VideoStreamRequest;
-
-        public int SegmentLength
-        {
-            get
-            {
-                if (Request.SegmentLength.HasValue)
-                {
-                    return Request.SegmentLength.Value;
-                }
-
-                if (EncodingHelper.IsCopyCodec(OutputVideoCodec))
-                {
-                    var userAgent = UserAgent ?? string.Empty;
-
-                    if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1 ||
-                        userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1 ||
-                        userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
-                        userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
-                        userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
-                    {
-                        if (IsSegmentedLiveStream)
-                        {
-                            return 6;
-                        }
-
-                        return 6;
-                    }
-
-                    if (IsSegmentedLiveStream)
-                    {
-                        return 3;
-                    }
-
-                    return 6;
-                }
-
-                return 3;
-            }
-        }
-
-        public int MinSegments
-        {
-            get
-            {
-                if (Request.MinSegments.HasValue)
-                {
-                    return Request.MinSegments.Value;
-                }
-
-                return SegmentLength >= 10 ? 2 : 3;
-            }
-        }
-
-        public string UserAgent { get; set; }
-
-        public bool EstimateContentLength { get; set; }
-
-        public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
-
-        public bool EnableDlnaHeaders { get; set; }
-
-        public DeviceProfile DeviceProfile { get; set; }
-
-        public TranscodingJob TranscodingJob { get; set; }
-
-        public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType)
-            : base(transcodingType)
-        {
-            _mediaSourceManager = mediaSourceManager;
-        }
-
-        public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
-        {
-            ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (_disposed)
-            {
-                return;
-            }
-
-            if (disposing)
-            {
-                // REVIEW: Is this the right place for this?
-                if (MediaSource.RequiresClosing
-                    && string.IsNullOrWhiteSpace(Request.LiveStreamId)
-                    && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
-                {
-                    _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult();
-                }
-
-                TranscodingThrottler?.Dispose();
-            }
-
-            TranscodingThrottler = null;
-            TranscodingJob = null;
-
-            _disposed = true;
-        }
-    }
-}

+ 0 - 175
MediaBrowser.Api/Playback/TranscodingThrottler.cs

@@ -1,175 +0,0 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.IO;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.Playback
-{
-    public class TranscodingThrottler : IDisposable
-    {
-        private readonly TranscodingJob _job;
-        private readonly ILogger _logger;
-        private Timer _timer;
-        private bool _isPaused;
-        private readonly IConfigurationManager _config;
-        private readonly IFileSystem _fileSystem;
-
-        public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config, IFileSystem fileSystem)
-        {
-            _job = job;
-            _logger = logger;
-            _config = config;
-            _fileSystem = fileSystem;
-        }
-
-        private EncodingOptions GetOptions()
-        {
-            return _config.GetConfiguration<EncodingOptions>("encoding");
-        }
-
-        public void Start()
-        {
-            _timer = new Timer(TimerCallback, null, 5000, 5000);
-        }
-
-        private async void TimerCallback(object state)
-        {
-            if (_job.HasExited)
-            {
-                DisposeTimer();
-                return;
-            }
-
-            var options = GetOptions();
-
-            if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleDelaySeconds))
-            {
-                await PauseTranscoding();
-            }
-            else
-            {
-                await UnpauseTranscoding();
-            }
-        }
-
-        private async Task PauseTranscoding()
-        {
-            if (!_isPaused)
-            {
-                _logger.LogDebug("Sending pause command to ffmpeg");
-
-                try
-                {
-                    await _job.Process.StandardInput.WriteAsync("c");
-                    _isPaused = true;
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error pausing transcoding");
-                }
-            }
-        }
-
-        public async Task UnpauseTranscoding()
-        {
-            if (_isPaused)
-            {
-                _logger.LogDebug("Sending resume command to ffmpeg");
-
-                try
-                {
-                    await _job.Process.StandardInput.WriteLineAsync();
-                    _isPaused = false;
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error resuming transcoding");
-                }
-            }
-        }
-
-        private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds)
-        {
-            var bytesDownloaded = job.BytesDownloaded ?? 0;
-            var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
-            var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
-
-            var path = job.Path;
-            var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks;
-
-            if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
-            {
-                // HLS - time-based consideration
-
-                var targetGap = gapLengthInTicks;
-                var gap = transcodingPositionTicks - downloadPositionTicks;
-
-                if (gap < targetGap)
-                {
-                    _logger.LogDebug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
-                    return false;
-                }
-
-                _logger.LogDebug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
-                return true;
-            }
-
-            if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
-            {
-                // Progressive Streaming - byte-based consideration
-
-                try
-                {
-                    var bytesTranscoded = job.BytesTranscoded ?? _fileSystem.GetFileInfo(path).Length;
-
-                    // Estimate the bytes the transcoder should be ahead
-                    double gapFactor = gapLengthInTicks;
-                    gapFactor /= transcodingPositionTicks;
-                    var targetGap = bytesTranscoded * gapFactor;
-
-                    var gap = bytesTranscoded - bytesDownloaded;
-
-                    if (gap < targetGap)
-                    {
-                        _logger.LogDebug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
-                        return false;
-                    }
-
-                    _logger.LogDebug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
-                    return true;
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error getting output size");
-                    return false;
-                }
-            }
-
-            _logger.LogDebug("No throttle data for " + path);
-            return false;
-        }
-
-        public async Task Stop()
-        {
-            DisposeTimer();
-            await UnpauseTranscoding();
-        }
-
-        public void Dispose()
-        {
-            DisposeTimer();
-        }
-
-        private void DisposeTimer()
-        {
-            if (_timer != null)
-            {
-                _timer.Dispose();
-                _timer = null;
-            }
-        }
-    }
-}

+ 0 - 23
MediaBrowser.Api/Properties/AssemblyInfo.cs

@@ -1,23 +0,0 @@
-using System.Reflection;
-using System.Resources;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("MediaBrowser.Api")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("Jellyfin Project")]
-[assembly: AssemblyProduct("Jellyfin Server")]
-[assembly: AssemblyCopyright("Copyright ©  2019 Jellyfin Contributors. Code released under the GNU General Public License")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: NeutralResourcesLanguage("en")]
-[assembly: InternalsVisibleTo("Jellyfin.Api.Tests")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components.  If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]

+ 0 - 26
MediaBrowser.Api/TestService.cs

@@ -1,26 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Net;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api
-{
-    /// <summary>
-    /// Service for testing path value.
-    /// </summary>
-    public class TestService : BaseApiService
-    {
-        /// <summary>
-        /// Test service.
-        /// </summary>
-        /// <param name="logger">Instance of the <see cref="ILogger{TestService}"/> interface.</param>
-        /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
-        /// <param name="httpResultFactory">Instance of the <see cref="IHttpResultFactory"/> interface.</param>
-        public TestService(
-            ILogger<TestService> logger,
-            IServerConfigurationManager serverConfigurationManager,
-            IHttpResultFactory httpResultFactory)
-            : base(logger, serverConfigurationManager, httpResultFactory)
-        {
-        }
-    }
-}

+ 0 - 165
MediaBrowser.Api/TranscodingJob.cs

@@ -1,165 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.Threading;
-using MediaBrowser.Api.Playback;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Dto;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api
-{
-    /// <summary>
-    /// Class TranscodingJob.
-    /// </summary>
-    public class TranscodingJob
-    {
-        /// <summary>
-        /// Gets or sets the play session identifier.
-        /// </summary>
-        /// <value>The play session identifier.</value>
-        public string PlaySessionId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the live stream identifier.
-        /// </summary>
-        /// <value>The live stream identifier.</value>
-        public string LiveStreamId { get; set; }
-
-        public bool IsLiveOutput { get; set; }
-
-        /// <summary>
-        /// Gets or sets the path.
-        /// </summary>
-        /// <value>The path.</value>
-        public MediaSourceInfo MediaSource { get; set; }
-
-        public string Path { get; set; }
-        /// <summary>
-        /// Gets or sets the type.
-        /// </summary>
-        /// <value>The type.</value>
-        public TranscodingJobType Type { get; set; }
-        /// <summary>
-        /// Gets or sets the process.
-        /// </summary>
-        /// <value>The process.</value>
-        public Process Process { get; set; }
-
-        public ILogger Logger { get; private set; }
-        /// <summary>
-        /// Gets or sets the active request count.
-        /// </summary>
-        /// <value>The active request count.</value>
-        public int ActiveRequestCount { get; set; }
-        /// <summary>
-        /// Gets or sets the kill timer.
-        /// </summary>
-        /// <value>The kill timer.</value>
-        private Timer KillTimer { get; set; }
-
-        public string DeviceId { get; set; }
-
-        public CancellationTokenSource CancellationTokenSource { get; set; }
-
-        public object ProcessLock = new object();
-
-        public bool HasExited { get; set; }
-
-        public bool IsUserPaused { get; set; }
-
-        public string Id { get; set; }
-
-        public float? Framerate { get; set; }
-
-        public double? CompletionPercentage { get; set; }
-
-        public long? BytesDownloaded { get; set; }
-
-        public long? BytesTranscoded { get; set; }
-
-        public int? BitRate { get; set; }
-
-        public long? TranscodingPositionTicks { get; set; }
-
-        public long? DownloadPositionTicks { get; set; }
-
-        public TranscodingThrottler TranscodingThrottler { get; set; }
-
-        private readonly object _timerLock = new object();
-
-        public DateTime LastPingDate { get; set; }
-
-        public int PingTimeout { get; set; }
-
-        public TranscodingJob(ILogger logger)
-        {
-            Logger = logger;
-        }
-
-        public void StopKillTimer()
-        {
-            lock (_timerLock)
-            {
-                KillTimer?.Change(Timeout.Infinite, Timeout.Infinite);
-            }
-        }
-
-        public void DisposeKillTimer()
-        {
-            lock (_timerLock)
-            {
-                if (KillTimer != null)
-                {
-                    KillTimer.Dispose();
-                    KillTimer = null;
-                }
-            }
-        }
-
-        public void StartKillTimer(Action<object> callback)
-        {
-            StartKillTimer(callback, PingTimeout);
-        }
-
-        public void StartKillTimer(Action<object> callback, int intervalMs)
-        {
-            if (HasExited)
-            {
-                return;
-            }
-
-            lock (_timerLock)
-            {
-                if (KillTimer == null)
-                {
-                    Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
-                    KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
-                }
-                else
-                {
-                    Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
-                    KillTimer.Change(intervalMs, Timeout.Infinite);
-                }
-            }
-        }
-
-        public void ChangeKillTimerIfStarted()
-        {
-            if (HasExited)
-            {
-                return;
-            }
-
-            lock (_timerLock)
-            {
-                if (KillTimer != null)
-                {
-                    var intervalMs = PingTimeout;
-
-                    Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
-                    KillTimer.Change(intervalMs, Timeout.Infinite);
-                }
-            }
-        }
-    }
-}

+ 0 - 6
MediaBrowser.sln

@@ -6,8 +6,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}"
-EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}"
@@ -80,10 +78,6 @@ Global
 		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU
 		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU

+ 0 - 45
tests/Jellyfin.Api.Tests/GetPathValueTests.cs

@@ -1,45 +0,0 @@
-using MediaBrowser.Api;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging.Abstractions;
-using Moq;
-using Xunit;
-
-namespace Jellyfin.Api.Tests
-{
-    public class GetPathValueTests
-    {
-        [Theory]
-        [InlineData("https://localhost:8096/ScheduledTasks/1234/Triggers", "", 1, "1234")]
-        [InlineData("https://localhost:8096/emby/ScheduledTasks/1234/Triggers", "", 1, "1234")]
-        [InlineData("https://localhost:8096/mediabrowser/ScheduledTasks/1234/Triggers", "", 1, "1234")]
-        [InlineData("https://localhost:8096/jellyfin/2/ScheduledTasks/1234/Triggers", "jellyfin/2", 1, "1234")]
-        [InlineData("https://localhost:8096/jellyfin/2/emby/ScheduledTasks/1234/Triggers", "jellyfin/2", 1, "1234")]
-        [InlineData("https://localhost:8096/jellyfin/2/mediabrowser/ScheduledTasks/1234/Triggers", "jellyfin/2", 1, "1234")]
-        [InlineData("https://localhost:8096/JELLYFIN/2/ScheduledTasks/1234/Triggers", "jellyfin/2", 1, "1234")]
-        [InlineData("https://localhost:8096/JELLYFIN/2/Emby/ScheduledTasks/1234/Triggers", "jellyfin/2", 1, "1234")]
-        [InlineData("https://localhost:8096/JELLYFIN/2/MediaBrowser/ScheduledTasks/1234/Triggers", "jellyfin/2", 1, "1234")]
-        public void GetPathValueTest(string path, string baseUrl, int index, string value)
-        {
-            var reqMock = Mock.Of<IRequest>(x => x.PathInfo == path);
-            var conf = new ServerConfiguration()
-            {
-                BaseUrl = baseUrl
-            };
-
-            var confManagerMock = Mock.Of<IServerConfigurationManager>(x => x.Configuration == conf);
-
-            var service = new TestService(
-                new NullLogger<TestService>(),
-                confManagerMock,
-                Mock.Of<IHttpResultFactory>())
-            {
-                Request = reqMock
-            };
-
-            Assert.Equal(value, service.GetPathValue(index).ToString());
-        }
-    }
-}