aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Controllers/HlsSegmentController.cs
blob: 054e586ce3999d45cbf44d59dcaa76d3d0762d84 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
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>
    [Route("")]
    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", Name = "GetHlsAudioSegmentLegacyMp3")]
        [HttpGet("Audio/{itemId}/hls/{segmentId}/stream.aac", Name = "GetHlsAudioSegmentLegacyAac")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesAudioFile]
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
        public ActionResult GetHlsAudioSegmentLegacy([FromRoute, Required] string itemId, [FromRoute, Required] 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, HttpContext);
        }

        /// <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)]
        [ProducesPlaylistFile]
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
        public ActionResult GetHlsPlaylistLegacy([FromRoute, Required] string itemId, [FromRoute, Required] 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)]
        [ProducesVideoFile]
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
        public ActionResult GetHlsVideoSegmentLegacy(
            [FromRoute, Required] string itemId,
            [FromRoute, Required] string playlistId,
            [FromRoute, Required] string segmentId,
            [FromRoute, Required] 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, HttpContext);
        }
    }
}