| 
					
				 | 
			
			
				@@ -0,0 +1,153 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System.Diagnostics.CodeAnalysis; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System.IO; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System.Linq; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using System.Threading.Tasks; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using Jellyfin.Api.Constants; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using Jellyfin.Api.Helpers; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using MediaBrowser.Common.Configuration; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using MediaBrowser.Controller.Configuration; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using MediaBrowser.Controller.MediaEncoding; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using MediaBrowser.Model.IO; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using MediaBrowser.Model.Net; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using Microsoft.AspNetCore.Authorization; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using Microsoft.AspNetCore.Http; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using Microsoft.AspNetCore.Mvc; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+namespace Jellyfin.Api.Controllers 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /// <summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /// The hls segment controller. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /// </summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public class HlsSegmentController : BaseJellyfinApiController 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly IFileSystem _fileSystem; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly IServerConfigurationManager _serverConfigurationManager; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private readonly TranscodingJobHelper _transcodingJobHelper; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// Initializes a new instance of the <see cref="HlsSegmentController"/> class. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// </summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="transcodingJobHelper">Initialized instance of the <see cref="TranscodingJobHelper"/>.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        public HlsSegmentController( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            IFileSystem fileSystem, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            IServerConfigurationManager serverConfigurationManager, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            TranscodingJobHelper transcodingJobHelper) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _fileSystem = fileSystem; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _serverConfigurationManager = serverConfigurationManager; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _transcodingJobHelper = transcodingJobHelper; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// Gets the specified audio segment for an audio item. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// </summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="itemId">The item id.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="segmentId">The segment id.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <response code="200">Hls audio segment returned.</response> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <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")] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [ProducesResponseType(StatusCodes.Status200OK)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        public ActionResult GetHlsAudioSegmentLegacy([FromRoute] string itemId, [FromRoute] string segmentId) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            // TODO: Deprecate with new iOS app 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var file = segmentId + Path.GetExtension(Request.Path); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)!, false, this); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// Gets a hls video playlist. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// </summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="itemId">The video id.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="playlistId">The playlist id.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <response code="200">Hls video playlist returned.</response> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <returns>A <see cref="FileStreamResult"/> containing the playlist.</returns> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [HttpGet("/Videos/{itemId}/hls/{playlistId}/stream.m3u8")] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [Authorize(Policy = Policies.DefaultAuthorization)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [ProducesResponseType(StatusCodes.Status200OK)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        public ActionResult GetHlsPlaylistLegacy([FromRoute] string itemId, [FromRoute] string playlistId) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var file = playlistId + Path.GetExtension(Request.Path); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return GetFileResult(file, file); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// Stops an active encoding. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// </summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="playSessionId">The play session id.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <response code="204">Encoding stopped successfully.</response> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [HttpDelete("/Videos/ActiveEncodings")] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [Authorize(Policy = Policies.DefaultAuthorization)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [ProducesResponseType(StatusCodes.Status204NoContent)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        public ActionResult StopEncodingProcess([FromQuery] string deviceId, [FromQuery] string playSessionId) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _transcodingJobHelper.KillTranscodingJobs(deviceId, playSessionId, path => true); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return NoContent(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// Gets a hls video segment. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// </summary> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="itemId">The item id.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="playlistId">The playlist id.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="segmentId">The segment id.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <param name="segmentContainer">The segment container.</param> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <response code="200">Hls video segment returned.</response> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /// <returns>A <see cref="FileStreamResult"/> containing the video segment.</returns> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // Can't require authentication just yet due to seeing some requests come from Chrome without full query string 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // [Authenticated] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [HttpGet("/Videos/{itemId}/hls/{playlistId}/{segmentId}.{segmentContainer}")] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [ProducesResponseType(StatusCodes.Status200OK)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        public ActionResult GetHlsVideoSegmentLegacy( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            [FromRoute] string itemId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            [FromRoute] string playlistId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            [FromRoute] string segmentId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            [FromRoute] string segmentContainer) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var file = segmentId + Path.GetExtension(Request.Path); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            file = Path.Combine(transcodeFolderPath, file); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var normalizedPlaylistId = playlistId; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var playlistPath = _fileSystem.GetFilePaths(transcodeFolderPath) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                .FirstOrDefault(i => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return GetFileResult(file, playlistPath); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private ActionResult GetFileResult(string path, string playlistPath) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var transcodingJob = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Response.OnCompleted(() => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (transcodingJob != null) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    _transcodingJobHelper.OnTranscodeEndRequest(transcodingJob); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return Task.CompletedTask; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return FileStreamResponseHelpers.GetStaticFileResult(path, MimeTypes.GetMimeType(path)!, false, this); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 |