FileStreamResponseHelpers.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. using System;
  2. using System.IO;
  3. using System.Net.Http;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using Jellyfin.Api.Models.StreamingDtos;
  7. using MediaBrowser.Controller.MediaEncoding;
  8. using MediaBrowser.Model.IO;
  9. using Microsoft.AspNetCore.Http;
  10. using Microsoft.AspNetCore.Mvc;
  11. using Microsoft.Net.Http.Headers;
  12. namespace Jellyfin.Api.Helpers
  13. {
  14. /// <summary>
  15. /// The stream response helpers.
  16. /// </summary>
  17. public static class FileStreamResponseHelpers
  18. {
  19. /// <summary>
  20. /// Returns a static file from a remote source.
  21. /// </summary>
  22. /// <param name="state">The current <see cref="StreamState"/>.</param>
  23. /// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param>
  24. /// <param name="controller">The <see cref="ControllerBase"/> managing the response.</param>
  25. /// <returns>A <see cref="Task{ActionResult}"/> containing the API response.</returns>
  26. public static async Task<ActionResult> GetStaticRemoteStreamResult(
  27. StreamState state,
  28. bool isHeadRequest,
  29. ControllerBase controller)
  30. {
  31. HttpClient httpClient = new HttpClient();
  32. if (state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent))
  33. {
  34. httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, useragent);
  35. }
  36. var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false);
  37. var contentType = response.Content.Headers.ContentType.ToString();
  38. controller.Response.Headers[HeaderNames.AcceptRanges] = "none";
  39. if (isHeadRequest)
  40. {
  41. using (response)
  42. {
  43. return controller.File(Array.Empty<byte>(), contentType);
  44. }
  45. }
  46. return controller.File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), contentType);
  47. }
  48. /// <summary>
  49. /// Returns a static file from the server.
  50. /// </summary>
  51. /// <param name="path">The path to the file.</param>
  52. /// <param name="contentType">The content type of the file.</param>
  53. /// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param>
  54. /// <param name="controller">The <see cref="ControllerBase"/> managing the response.</param>
  55. /// <returns>An <see cref="ActionResult"/> the file.</returns>
  56. public static ActionResult GetStaticFileResult(
  57. string path,
  58. string contentType,
  59. bool isHeadRequest,
  60. ControllerBase controller)
  61. {
  62. controller.Response.ContentType = contentType;
  63. // if the request is a head request, return a NoContent result with the same headers as it would with a GET request
  64. if (isHeadRequest)
  65. {
  66. return controller.NoContent();
  67. }
  68. var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
  69. return controller.File(stream, contentType);
  70. }
  71. /// <summary>
  72. /// Returns a transcoded file from the server.
  73. /// </summary>
  74. /// <param name="state">The current <see cref="StreamState"/>.</param>
  75. /// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param>
  76. /// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
  77. /// <param name="controller">The <see cref="ControllerBase"/> managing the response.</param>
  78. /// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
  79. /// <param name="ffmpegCommandLineArguments">The command line arguments to start ffmpeg.</param>
  80. /// <param name="request">The <see cref="HttpRequest"/> starting the transcoding.</param>
  81. /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
  82. /// <param name="cancellationTokenSource">The <see cref="CancellationTokenSource"/>.</param>
  83. /// <returns>A <see cref="Task{ActionResult}"/> containing the transcoded file.</returns>
  84. public static async Task<ActionResult> GetTranscodedFile(
  85. StreamState state,
  86. bool isHeadRequest,
  87. IStreamHelper streamHelper,
  88. ControllerBase controller,
  89. TranscodingJobHelper transcodingJobHelper,
  90. string ffmpegCommandLineArguments,
  91. HttpRequest request,
  92. TranscodingJobType transcodingJobType,
  93. CancellationTokenSource cancellationTokenSource)
  94. {
  95. // Use the command line args with a dummy playlist path
  96. var outputPath = state.OutputFilePath;
  97. controller.Response.Headers[HeaderNames.AcceptRanges] = "none";
  98. var contentType = state.GetMimeType(outputPath);
  99. // Headers only
  100. if (isHeadRequest)
  101. {
  102. return controller.File(Array.Empty<byte>(), contentType);
  103. }
  104. var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath);
  105. await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
  106. try
  107. {
  108. if (!File.Exists(outputPath))
  109. {
  110. await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false);
  111. }
  112. else
  113. {
  114. transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
  115. state.Dispose();
  116. }
  117. using (var memoryStream = new MemoryStream())
  118. {
  119. await new ProgressiveFileCopier(streamHelper, outputPath).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false);
  120. return controller.File(memoryStream, contentType);
  121. }
  122. }
  123. finally
  124. {
  125. transcodingLock.Release();
  126. }
  127. }
  128. }
  129. }