DynamicHlsController.cs 109 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.Globalization;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using Jellyfin.Api.Attributes;
  12. using Jellyfin.Api.Constants;
  13. using Jellyfin.Api.Helpers;
  14. using Jellyfin.Api.Models.PlaybackDtos;
  15. using Jellyfin.Api.Models.StreamingDtos;
  16. using Jellyfin.MediaEncoding.Hls.Playlist;
  17. using MediaBrowser.Common.Configuration;
  18. using MediaBrowser.Controller.Configuration;
  19. using MediaBrowser.Controller.Devices;
  20. using MediaBrowser.Controller.Dlna;
  21. using MediaBrowser.Controller.Library;
  22. using MediaBrowser.Controller.MediaEncoding;
  23. using MediaBrowser.Model.Configuration;
  24. using MediaBrowser.Model.Dlna;
  25. using MediaBrowser.Model.Entities;
  26. using MediaBrowser.Model.IO;
  27. using MediaBrowser.Model.Net;
  28. using Microsoft.AspNetCore.Authorization;
  29. using Microsoft.AspNetCore.Http;
  30. using Microsoft.AspNetCore.Mvc;
  31. using Microsoft.Extensions.Logging;
  32. namespace Jellyfin.Api.Controllers;
  33. /// <summary>
  34. /// Dynamic hls controller.
  35. /// </summary>
  36. [Route("")]
  37. [Authorize(Policy = Policies.DefaultAuthorization)]
  38. public class DynamicHlsController : BaseJellyfinApiController
  39. {
  40. private const string DefaultVodEncoderPreset = "veryfast";
  41. private const string DefaultEventEncoderPreset = "superfast";
  42. private const TranscodingJobType TranscodingJobType = MediaBrowser.Controller.MediaEncoding.TranscodingJobType.Hls;
  43. private readonly ILibraryManager _libraryManager;
  44. private readonly IUserManager _userManager;
  45. private readonly IDlnaManager _dlnaManager;
  46. private readonly IMediaSourceManager _mediaSourceManager;
  47. private readonly IServerConfigurationManager _serverConfigurationManager;
  48. private readonly IMediaEncoder _mediaEncoder;
  49. private readonly IFileSystem _fileSystem;
  50. private readonly IDeviceManager _deviceManager;
  51. private readonly TranscodingJobHelper _transcodingJobHelper;
  52. private readonly ILogger<DynamicHlsController> _logger;
  53. private readonly EncodingHelper _encodingHelper;
  54. private readonly IDynamicHlsPlaylistGenerator _dynamicHlsPlaylistGenerator;
  55. private readonly DynamicHlsHelper _dynamicHlsHelper;
  56. private readonly EncodingOptions _encodingOptions;
  57. /// <summary>
  58. /// Initializes a new instance of the <see cref="DynamicHlsController"/> class.
  59. /// </summary>
  60. /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
  61. /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
  62. /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
  63. /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
  64. /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
  65. /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
  66. /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
  67. /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
  68. /// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param>
  69. /// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsController}"/> interface.</param>
  70. /// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param>
  71. /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
  72. /// <param name="dynamicHlsPlaylistGenerator">Instance of <see cref="IDynamicHlsPlaylistGenerator"/>.</param>
  73. public DynamicHlsController(
  74. ILibraryManager libraryManager,
  75. IUserManager userManager,
  76. IDlnaManager dlnaManager,
  77. IMediaSourceManager mediaSourceManager,
  78. IServerConfigurationManager serverConfigurationManager,
  79. IMediaEncoder mediaEncoder,
  80. IFileSystem fileSystem,
  81. IDeviceManager deviceManager,
  82. TranscodingJobHelper transcodingJobHelper,
  83. ILogger<DynamicHlsController> logger,
  84. DynamicHlsHelper dynamicHlsHelper,
  85. EncodingHelper encodingHelper,
  86. IDynamicHlsPlaylistGenerator dynamicHlsPlaylistGenerator)
  87. {
  88. _libraryManager = libraryManager;
  89. _userManager = userManager;
  90. _dlnaManager = dlnaManager;
  91. _mediaSourceManager = mediaSourceManager;
  92. _serverConfigurationManager = serverConfigurationManager;
  93. _mediaEncoder = mediaEncoder;
  94. _fileSystem = fileSystem;
  95. _deviceManager = deviceManager;
  96. _transcodingJobHelper = transcodingJobHelper;
  97. _logger = logger;
  98. _dynamicHlsHelper = dynamicHlsHelper;
  99. _encodingHelper = encodingHelper;
  100. _dynamicHlsPlaylistGenerator = dynamicHlsPlaylistGenerator;
  101. _encodingOptions = serverConfigurationManager.GetEncodingOptions();
  102. }
  103. /// <summary>
  104. /// Gets a hls live stream.
  105. /// </summary>
  106. /// <param name="itemId">The item id.</param>
  107. /// <param name="container">The audio container.</param>
  108. /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
  109. /// <param name="params">The streaming parameters.</param>
  110. /// <param name="tag">The tag.</param>
  111. /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
  112. /// <param name="playSessionId">The play session id.</param>
  113. /// <param name="segmentContainer">The segment container.</param>
  114. /// <param name="segmentLength">The segment length.</param>
  115. /// <param name="minSegments">The minimum number of segments.</param>
  116. /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
  117. /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
  118. /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
  119. /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
  120. /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
  121. /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
  122. /// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
  123. /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
  124. /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
  125. /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
  126. /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
  127. /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
  128. /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
  129. /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
  130. /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  131. /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  132. /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
  133. /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
  134. /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
  135. /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
  136. /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
  137. /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
  138. /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
  139. /// <param name="maxRefFrames">Optional.</param>
  140. /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
  141. /// <param name="requireAvc">Optional. Whether to require avc.</param>
  142. /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
  143. /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
  144. /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
  145. /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
  146. /// <param name="liveStreamId">The live stream id.</param>
  147. /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
  148. /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param>
  149. /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
  150. /// <param name="transcodeReasons">Optional. The transcoding reason.</param>
  151. /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
  152. /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
  153. /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
  154. /// <param name="streamOptions">Optional. The streaming options.</param>
  155. /// <param name="maxWidth">Optional. The max width.</param>
  156. /// <param name="maxHeight">Optional. The max height.</param>
  157. /// <param name="enableSubtitlesInManifest">Optional. Whether to enable subtitles in the manifest.</param>
  158. /// <response code="200">Hls live stream retrieved.</response>
  159. /// <returns>A <see cref="FileResult"/> containing the hls file.</returns>
  160. [HttpGet("Videos/{itemId}/live.m3u8")]
  161. [ProducesResponseType(StatusCodes.Status200OK)]
  162. [ProducesPlaylistFile]
  163. public async Task<ActionResult> GetLiveHlsStream(
  164. [FromRoute, Required] Guid itemId,
  165. [FromQuery] string? container,
  166. [FromQuery] bool? @static,
  167. [FromQuery] string? @params,
  168. [FromQuery] string? tag,
  169. [FromQuery] string? deviceProfileId,
  170. [FromQuery] string? playSessionId,
  171. [FromQuery] string? segmentContainer,
  172. [FromQuery] int? segmentLength,
  173. [FromQuery] int? minSegments,
  174. [FromQuery] string? mediaSourceId,
  175. [FromQuery] string? deviceId,
  176. [FromQuery] string? audioCodec,
  177. [FromQuery] bool? enableAutoStreamCopy,
  178. [FromQuery] bool? allowVideoStreamCopy,
  179. [FromQuery] bool? allowAudioStreamCopy,
  180. [FromQuery] bool? breakOnNonKeyFrames,
  181. [FromQuery] int? audioSampleRate,
  182. [FromQuery] int? maxAudioBitDepth,
  183. [FromQuery] int? audioBitRate,
  184. [FromQuery] int? audioChannels,
  185. [FromQuery] int? maxAudioChannels,
  186. [FromQuery] string? profile,
  187. [FromQuery] string? level,
  188. [FromQuery] float? framerate,
  189. [FromQuery] float? maxFramerate,
  190. [FromQuery] bool? copyTimestamps,
  191. [FromQuery] long? startTimeTicks,
  192. [FromQuery] int? width,
  193. [FromQuery] int? height,
  194. [FromQuery] int? videoBitRate,
  195. [FromQuery] int? subtitleStreamIndex,
  196. [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
  197. [FromQuery] int? maxRefFrames,
  198. [FromQuery] int? maxVideoBitDepth,
  199. [FromQuery] bool? requireAvc,
  200. [FromQuery] bool? deInterlace,
  201. [FromQuery] bool? requireNonAnamorphic,
  202. [FromQuery] int? transcodingMaxAudioChannels,
  203. [FromQuery] int? cpuCoreLimit,
  204. [FromQuery] string? liveStreamId,
  205. [FromQuery] bool? enableMpegtsM2TsMode,
  206. [FromQuery] string? videoCodec,
  207. [FromQuery] string? subtitleCodec,
  208. [FromQuery] string? transcodeReasons,
  209. [FromQuery] int? audioStreamIndex,
  210. [FromQuery] int? videoStreamIndex,
  211. [FromQuery] EncodingContext? context,
  212. [FromQuery] Dictionary<string, string> streamOptions,
  213. [FromQuery] int? maxWidth,
  214. [FromQuery] int? maxHeight,
  215. [FromQuery] bool? enableSubtitlesInManifest)
  216. {
  217. VideoRequestDto streamingRequest = new VideoRequestDto
  218. {
  219. Id = itemId,
  220. Container = container,
  221. Static = @static ?? false,
  222. Params = @params,
  223. Tag = tag,
  224. DeviceProfileId = deviceProfileId,
  225. PlaySessionId = playSessionId,
  226. SegmentContainer = segmentContainer,
  227. SegmentLength = segmentLength,
  228. MinSegments = minSegments,
  229. MediaSourceId = mediaSourceId,
  230. DeviceId = deviceId,
  231. AudioCodec = audioCodec,
  232. EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
  233. AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
  234. AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
  235. BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
  236. AudioSampleRate = audioSampleRate,
  237. MaxAudioChannels = maxAudioChannels,
  238. AudioBitRate = audioBitRate,
  239. MaxAudioBitDepth = maxAudioBitDepth,
  240. AudioChannels = audioChannels,
  241. Profile = profile,
  242. Level = level,
  243. Framerate = framerate,
  244. MaxFramerate = maxFramerate,
  245. CopyTimestamps = copyTimestamps ?? false,
  246. StartTimeTicks = startTimeTicks,
  247. Width = width,
  248. Height = height,
  249. VideoBitRate = videoBitRate,
  250. SubtitleStreamIndex = subtitleStreamIndex,
  251. SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
  252. MaxRefFrames = maxRefFrames,
  253. MaxVideoBitDepth = maxVideoBitDepth,
  254. RequireAvc = requireAvc ?? false,
  255. DeInterlace = deInterlace ?? false,
  256. RequireNonAnamorphic = requireNonAnamorphic ?? false,
  257. TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
  258. CpuCoreLimit = cpuCoreLimit,
  259. LiveStreamId = liveStreamId,
  260. EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
  261. VideoCodec = videoCodec,
  262. SubtitleCodec = subtitleCodec,
  263. TranscodeReasons = transcodeReasons,
  264. AudioStreamIndex = audioStreamIndex,
  265. VideoStreamIndex = videoStreamIndex,
  266. Context = context ?? EncodingContext.Streaming,
  267. StreamOptions = streamOptions,
  268. MaxHeight = maxHeight,
  269. MaxWidth = maxWidth,
  270. EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true
  271. };
  272. // CTS lifecycle is managed internally.
  273. var cancellationTokenSource = new CancellationTokenSource();
  274. // Due to CTS.Token calling ThrowIfDisposed (https://github.com/dotnet/runtime/issues/29970) we have to "cache" the token
  275. // since it gets disposed when ffmpeg exits
  276. var cancellationToken = cancellationTokenSource.Token;
  277. var state = await StreamingHelpers.GetStreamingState(
  278. streamingRequest,
  279. HttpContext,
  280. _mediaSourceManager,
  281. _userManager,
  282. _libraryManager,
  283. _serverConfigurationManager,
  284. _mediaEncoder,
  285. _encodingHelper,
  286. _dlnaManager,
  287. _deviceManager,
  288. _transcodingJobHelper,
  289. TranscodingJobType,
  290. cancellationToken)
  291. .ConfigureAwait(false);
  292. TranscodingJobDto? job = null;
  293. var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
  294. if (!System.IO.File.Exists(playlistPath))
  295. {
  296. var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath);
  297. await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
  298. try
  299. {
  300. if (!System.IO.File.Exists(playlistPath))
  301. {
  302. // If the playlist doesn't already exist, startup ffmpeg
  303. try
  304. {
  305. job = await _transcodingJobHelper.StartFfMpeg(
  306. state,
  307. playlistPath,
  308. GetCommandLineArguments(playlistPath, state, true, 0),
  309. Request,
  310. TranscodingJobType,
  311. cancellationTokenSource)
  312. .ConfigureAwait(false);
  313. job.IsLiveOutput = true;
  314. }
  315. catch
  316. {
  317. state.Dispose();
  318. throw;
  319. }
  320. minSegments = state.MinSegments;
  321. if (minSegments > 0)
  322. {
  323. await HlsHelpers.WaitForMinimumSegmentCount(playlistPath, minSegments, _logger, cancellationToken).ConfigureAwait(false);
  324. }
  325. }
  326. }
  327. finally
  328. {
  329. transcodingLock.Release();
  330. }
  331. }
  332. job ??= _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
  333. if (job is not null)
  334. {
  335. _transcodingJobHelper.OnTranscodeEndRequest(job);
  336. }
  337. var playlistText = HlsHelpers.GetLivePlaylistText(playlistPath, state);
  338. return Content(playlistText, MimeTypes.GetMimeType("playlist.m3u8"));
  339. }
  340. /// <summary>
  341. /// Gets a video hls playlist stream.
  342. /// </summary>
  343. /// <param name="itemId">The item id.</param>
  344. /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
  345. /// <param name="params">The streaming parameters.</param>
  346. /// <param name="tag">The tag.</param>
  347. /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
  348. /// <param name="playSessionId">The play session id.</param>
  349. /// <param name="segmentContainer">The segment container.</param>
  350. /// <param name="segmentLength">The segment length.</param>
  351. /// <param name="minSegments">The minimum number of segments.</param>
  352. /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
  353. /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
  354. /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
  355. /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
  356. /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
  357. /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
  358. /// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
  359. /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
  360. /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
  361. /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
  362. /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
  363. /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
  364. /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
  365. /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
  366. /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  367. /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  368. /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
  369. /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
  370. /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
  371. /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
  372. /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
  373. /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
  374. /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
  375. /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
  376. /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
  377. /// <param name="maxRefFrames">Optional.</param>
  378. /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
  379. /// <param name="requireAvc">Optional. Whether to require avc.</param>
  380. /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
  381. /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
  382. /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
  383. /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
  384. /// <param name="liveStreamId">The live stream id.</param>
  385. /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
  386. /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param>
  387. /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
  388. /// <param name="transcodeReasons">Optional. The transcoding reason.</param>
  389. /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
  390. /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
  391. /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
  392. /// <param name="streamOptions">Optional. The streaming options.</param>
  393. /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
  394. /// <response code="200">Video stream returned.</response>
  395. /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
  396. [HttpGet("Videos/{itemId}/master.m3u8")]
  397. [HttpHead("Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")]
  398. [ProducesResponseType(StatusCodes.Status200OK)]
  399. [ProducesPlaylistFile]
  400. public async Task<ActionResult> GetMasterHlsVideoPlaylist(
  401. [FromRoute, Required] Guid itemId,
  402. [FromQuery] bool? @static,
  403. [FromQuery] string? @params,
  404. [FromQuery] string? tag,
  405. [FromQuery] string? deviceProfileId,
  406. [FromQuery] string? playSessionId,
  407. [FromQuery] string? segmentContainer,
  408. [FromQuery] int? segmentLength,
  409. [FromQuery] int? minSegments,
  410. [FromQuery, Required] string mediaSourceId,
  411. [FromQuery] string? deviceId,
  412. [FromQuery] string? audioCodec,
  413. [FromQuery] bool? enableAutoStreamCopy,
  414. [FromQuery] bool? allowVideoStreamCopy,
  415. [FromQuery] bool? allowAudioStreamCopy,
  416. [FromQuery] bool? breakOnNonKeyFrames,
  417. [FromQuery] int? audioSampleRate,
  418. [FromQuery] int? maxAudioBitDepth,
  419. [FromQuery] int? audioBitRate,
  420. [FromQuery] int? audioChannels,
  421. [FromQuery] int? maxAudioChannels,
  422. [FromQuery] string? profile,
  423. [FromQuery] string? level,
  424. [FromQuery] float? framerate,
  425. [FromQuery] float? maxFramerate,
  426. [FromQuery] bool? copyTimestamps,
  427. [FromQuery] long? startTimeTicks,
  428. [FromQuery] int? width,
  429. [FromQuery] int? height,
  430. [FromQuery] int? maxWidth,
  431. [FromQuery] int? maxHeight,
  432. [FromQuery] int? videoBitRate,
  433. [FromQuery] int? subtitleStreamIndex,
  434. [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
  435. [FromQuery] int? maxRefFrames,
  436. [FromQuery] int? maxVideoBitDepth,
  437. [FromQuery] bool? requireAvc,
  438. [FromQuery] bool? deInterlace,
  439. [FromQuery] bool? requireNonAnamorphic,
  440. [FromQuery] int? transcodingMaxAudioChannels,
  441. [FromQuery] int? cpuCoreLimit,
  442. [FromQuery] string? liveStreamId,
  443. [FromQuery] bool? enableMpegtsM2TsMode,
  444. [FromQuery] string? videoCodec,
  445. [FromQuery] string? subtitleCodec,
  446. [FromQuery] string? transcodeReasons,
  447. [FromQuery] int? audioStreamIndex,
  448. [FromQuery] int? videoStreamIndex,
  449. [FromQuery] EncodingContext? context,
  450. [FromQuery] Dictionary<string, string> streamOptions,
  451. [FromQuery] bool enableAdaptiveBitrateStreaming = true)
  452. {
  453. var streamingRequest = new HlsVideoRequestDto
  454. {
  455. Id = itemId,
  456. Static = @static ?? false,
  457. Params = @params,
  458. Tag = tag,
  459. DeviceProfileId = deviceProfileId,
  460. PlaySessionId = playSessionId,
  461. SegmentContainer = segmentContainer,
  462. SegmentLength = segmentLength,
  463. MinSegments = minSegments,
  464. MediaSourceId = mediaSourceId,
  465. DeviceId = deviceId,
  466. AudioCodec = audioCodec,
  467. EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
  468. AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
  469. AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
  470. BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
  471. AudioSampleRate = audioSampleRate,
  472. MaxAudioChannels = maxAudioChannels,
  473. AudioBitRate = audioBitRate,
  474. MaxAudioBitDepth = maxAudioBitDepth,
  475. AudioChannels = audioChannels,
  476. Profile = profile,
  477. Level = level,
  478. Framerate = framerate,
  479. MaxFramerate = maxFramerate,
  480. CopyTimestamps = copyTimestamps ?? false,
  481. StartTimeTicks = startTimeTicks,
  482. Width = width,
  483. Height = height,
  484. MaxWidth = maxWidth,
  485. MaxHeight = maxHeight,
  486. VideoBitRate = videoBitRate,
  487. SubtitleStreamIndex = subtitleStreamIndex,
  488. SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
  489. MaxRefFrames = maxRefFrames,
  490. MaxVideoBitDepth = maxVideoBitDepth,
  491. RequireAvc = requireAvc ?? false,
  492. DeInterlace = deInterlace ?? false,
  493. RequireNonAnamorphic = requireNonAnamorphic ?? false,
  494. TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
  495. CpuCoreLimit = cpuCoreLimit,
  496. LiveStreamId = liveStreamId,
  497. EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
  498. VideoCodec = videoCodec,
  499. SubtitleCodec = subtitleCodec,
  500. TranscodeReasons = transcodeReasons,
  501. AudioStreamIndex = audioStreamIndex,
  502. VideoStreamIndex = videoStreamIndex,
  503. Context = context ?? EncodingContext.Streaming,
  504. StreamOptions = streamOptions,
  505. EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
  506. };
  507. return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false);
  508. }
  509. /// <summary>
  510. /// Gets an audio hls playlist stream.
  511. /// </summary>
  512. /// <param name="itemId">The item id.</param>
  513. /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
  514. /// <param name="params">The streaming parameters.</param>
  515. /// <param name="tag">The tag.</param>
  516. /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
  517. /// <param name="playSessionId">The play session id.</param>
  518. /// <param name="segmentContainer">The segment container.</param>
  519. /// <param name="segmentLength">The segment length.</param>
  520. /// <param name="minSegments">The minimum number of segments.</param>
  521. /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
  522. /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
  523. /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
  524. /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
  525. /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
  526. /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
  527. /// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
  528. /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
  529. /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
  530. /// <param name="maxStreamingBitrate">Optional. The maximum streaming bitrate.</param>
  531. /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
  532. /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
  533. /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
  534. /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
  535. /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
  536. /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  537. /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  538. /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
  539. /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
  540. /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
  541. /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
  542. /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
  543. /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
  544. /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
  545. /// <param name="maxRefFrames">Optional.</param>
  546. /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
  547. /// <param name="requireAvc">Optional. Whether to require avc.</param>
  548. /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
  549. /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
  550. /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
  551. /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
  552. /// <param name="liveStreamId">The live stream id.</param>
  553. /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
  554. /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param>
  555. /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
  556. /// <param name="transcodeReasons">Optional. The transcoding reason.</param>
  557. /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
  558. /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
  559. /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
  560. /// <param name="streamOptions">Optional. The streaming options.</param>
  561. /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
  562. /// <response code="200">Audio stream returned.</response>
  563. /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
  564. [HttpGet("Audio/{itemId}/master.m3u8")]
  565. [HttpHead("Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")]
  566. [ProducesResponseType(StatusCodes.Status200OK)]
  567. [ProducesPlaylistFile]
  568. public async Task<ActionResult> GetMasterHlsAudioPlaylist(
  569. [FromRoute, Required] Guid itemId,
  570. [FromQuery] bool? @static,
  571. [FromQuery] string? @params,
  572. [FromQuery] string? tag,
  573. [FromQuery] string? deviceProfileId,
  574. [FromQuery] string? playSessionId,
  575. [FromQuery] string? segmentContainer,
  576. [FromQuery] int? segmentLength,
  577. [FromQuery] int? minSegments,
  578. [FromQuery, Required] string mediaSourceId,
  579. [FromQuery] string? deviceId,
  580. [FromQuery] string? audioCodec,
  581. [FromQuery] bool? enableAutoStreamCopy,
  582. [FromQuery] bool? allowVideoStreamCopy,
  583. [FromQuery] bool? allowAudioStreamCopy,
  584. [FromQuery] bool? breakOnNonKeyFrames,
  585. [FromQuery] int? audioSampleRate,
  586. [FromQuery] int? maxAudioBitDepth,
  587. [FromQuery] int? maxStreamingBitrate,
  588. [FromQuery] int? audioBitRate,
  589. [FromQuery] int? audioChannels,
  590. [FromQuery] int? maxAudioChannels,
  591. [FromQuery] string? profile,
  592. [FromQuery] string? level,
  593. [FromQuery] float? framerate,
  594. [FromQuery] float? maxFramerate,
  595. [FromQuery] bool? copyTimestamps,
  596. [FromQuery] long? startTimeTicks,
  597. [FromQuery] int? width,
  598. [FromQuery] int? height,
  599. [FromQuery] int? videoBitRate,
  600. [FromQuery] int? subtitleStreamIndex,
  601. [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
  602. [FromQuery] int? maxRefFrames,
  603. [FromQuery] int? maxVideoBitDepth,
  604. [FromQuery] bool? requireAvc,
  605. [FromQuery] bool? deInterlace,
  606. [FromQuery] bool? requireNonAnamorphic,
  607. [FromQuery] int? transcodingMaxAudioChannels,
  608. [FromQuery] int? cpuCoreLimit,
  609. [FromQuery] string? liveStreamId,
  610. [FromQuery] bool? enableMpegtsM2TsMode,
  611. [FromQuery] string? videoCodec,
  612. [FromQuery] string? subtitleCodec,
  613. [FromQuery] string? transcodeReasons,
  614. [FromQuery] int? audioStreamIndex,
  615. [FromQuery] int? videoStreamIndex,
  616. [FromQuery] EncodingContext? context,
  617. [FromQuery] Dictionary<string, string> streamOptions,
  618. [FromQuery] bool enableAdaptiveBitrateStreaming = true)
  619. {
  620. var streamingRequest = new HlsAudioRequestDto
  621. {
  622. Id = itemId,
  623. Static = @static ?? false,
  624. Params = @params,
  625. Tag = tag,
  626. DeviceProfileId = deviceProfileId,
  627. PlaySessionId = playSessionId,
  628. SegmentContainer = segmentContainer,
  629. SegmentLength = segmentLength,
  630. MinSegments = minSegments,
  631. MediaSourceId = mediaSourceId,
  632. DeviceId = deviceId,
  633. AudioCodec = audioCodec,
  634. EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
  635. AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
  636. AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
  637. BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
  638. AudioSampleRate = audioSampleRate,
  639. MaxAudioChannels = maxAudioChannels,
  640. AudioBitRate = audioBitRate ?? maxStreamingBitrate,
  641. MaxAudioBitDepth = maxAudioBitDepth,
  642. AudioChannels = audioChannels,
  643. Profile = profile,
  644. Level = level,
  645. Framerate = framerate,
  646. MaxFramerate = maxFramerate,
  647. CopyTimestamps = copyTimestamps ?? false,
  648. StartTimeTicks = startTimeTicks,
  649. Width = width,
  650. Height = height,
  651. VideoBitRate = videoBitRate,
  652. SubtitleStreamIndex = subtitleStreamIndex,
  653. SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
  654. MaxRefFrames = maxRefFrames,
  655. MaxVideoBitDepth = maxVideoBitDepth,
  656. RequireAvc = requireAvc ?? false,
  657. DeInterlace = deInterlace ?? false,
  658. RequireNonAnamorphic = requireNonAnamorphic ?? false,
  659. TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
  660. CpuCoreLimit = cpuCoreLimit,
  661. LiveStreamId = liveStreamId,
  662. EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
  663. VideoCodec = videoCodec,
  664. SubtitleCodec = subtitleCodec,
  665. TranscodeReasons = transcodeReasons,
  666. AudioStreamIndex = audioStreamIndex,
  667. VideoStreamIndex = videoStreamIndex,
  668. Context = context ?? EncodingContext.Streaming,
  669. StreamOptions = streamOptions,
  670. EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
  671. };
  672. return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false);
  673. }
  674. /// <summary>
  675. /// Gets a video stream using HTTP live streaming.
  676. /// </summary>
  677. /// <param name="itemId">The item id.</param>
  678. /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
  679. /// <param name="params">The streaming parameters.</param>
  680. /// <param name="tag">The tag.</param>
  681. /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
  682. /// <param name="playSessionId">The play session id.</param>
  683. /// <param name="segmentContainer">The segment container.</param>
  684. /// <param name="segmentLength">The segment length.</param>
  685. /// <param name="minSegments">The minimum number of segments.</param>
  686. /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
  687. /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
  688. /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
  689. /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
  690. /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
  691. /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
  692. /// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
  693. /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
  694. /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
  695. /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
  696. /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
  697. /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
  698. /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
  699. /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
  700. /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  701. /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  702. /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
  703. /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
  704. /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
  705. /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
  706. /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
  707. /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
  708. /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
  709. /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
  710. /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
  711. /// <param name="maxRefFrames">Optional.</param>
  712. /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
  713. /// <param name="requireAvc">Optional. Whether to require avc.</param>
  714. /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
  715. /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
  716. /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
  717. /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
  718. /// <param name="liveStreamId">The live stream id.</param>
  719. /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
  720. /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param>
  721. /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
  722. /// <param name="transcodeReasons">Optional. The transcoding reason.</param>
  723. /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
  724. /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
  725. /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
  726. /// <param name="streamOptions">Optional. The streaming options.</param>
  727. /// <response code="200">Video stream returned.</response>
  728. /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
  729. [HttpGet("Videos/{itemId}/main.m3u8")]
  730. [ProducesResponseType(StatusCodes.Status200OK)]
  731. [ProducesPlaylistFile]
  732. public async Task<ActionResult> GetVariantHlsVideoPlaylist(
  733. [FromRoute, Required] Guid itemId,
  734. [FromQuery] bool? @static,
  735. [FromQuery] string? @params,
  736. [FromQuery] string? tag,
  737. [FromQuery] string? deviceProfileId,
  738. [FromQuery] string? playSessionId,
  739. [FromQuery] string? segmentContainer,
  740. [FromQuery] int? segmentLength,
  741. [FromQuery] int? minSegments,
  742. [FromQuery] string? mediaSourceId,
  743. [FromQuery] string? deviceId,
  744. [FromQuery] string? audioCodec,
  745. [FromQuery] bool? enableAutoStreamCopy,
  746. [FromQuery] bool? allowVideoStreamCopy,
  747. [FromQuery] bool? allowAudioStreamCopy,
  748. [FromQuery] bool? breakOnNonKeyFrames,
  749. [FromQuery] int? audioSampleRate,
  750. [FromQuery] int? maxAudioBitDepth,
  751. [FromQuery] int? audioBitRate,
  752. [FromQuery] int? audioChannels,
  753. [FromQuery] int? maxAudioChannels,
  754. [FromQuery] string? profile,
  755. [FromQuery] string? level,
  756. [FromQuery] float? framerate,
  757. [FromQuery] float? maxFramerate,
  758. [FromQuery] bool? copyTimestamps,
  759. [FromQuery] long? startTimeTicks,
  760. [FromQuery] int? width,
  761. [FromQuery] int? height,
  762. [FromQuery] int? maxWidth,
  763. [FromQuery] int? maxHeight,
  764. [FromQuery] int? videoBitRate,
  765. [FromQuery] int? subtitleStreamIndex,
  766. [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
  767. [FromQuery] int? maxRefFrames,
  768. [FromQuery] int? maxVideoBitDepth,
  769. [FromQuery] bool? requireAvc,
  770. [FromQuery] bool? deInterlace,
  771. [FromQuery] bool? requireNonAnamorphic,
  772. [FromQuery] int? transcodingMaxAudioChannels,
  773. [FromQuery] int? cpuCoreLimit,
  774. [FromQuery] string? liveStreamId,
  775. [FromQuery] bool? enableMpegtsM2TsMode,
  776. [FromQuery] string? videoCodec,
  777. [FromQuery] string? subtitleCodec,
  778. [FromQuery] string? transcodeReasons,
  779. [FromQuery] int? audioStreamIndex,
  780. [FromQuery] int? videoStreamIndex,
  781. [FromQuery] EncodingContext? context,
  782. [FromQuery] Dictionary<string, string> streamOptions)
  783. {
  784. using var cancellationTokenSource = new CancellationTokenSource();
  785. var streamingRequest = new VideoRequestDto
  786. {
  787. Id = itemId,
  788. Static = @static ?? false,
  789. Params = @params,
  790. Tag = tag,
  791. DeviceProfileId = deviceProfileId,
  792. PlaySessionId = playSessionId,
  793. SegmentContainer = segmentContainer,
  794. SegmentLength = segmentLength,
  795. MinSegments = minSegments,
  796. MediaSourceId = mediaSourceId,
  797. DeviceId = deviceId,
  798. AudioCodec = audioCodec,
  799. EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
  800. AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
  801. AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
  802. BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
  803. AudioSampleRate = audioSampleRate,
  804. MaxAudioChannels = maxAudioChannels,
  805. AudioBitRate = audioBitRate,
  806. MaxAudioBitDepth = maxAudioBitDepth,
  807. AudioChannels = audioChannels,
  808. Profile = profile,
  809. Level = level,
  810. Framerate = framerate,
  811. MaxFramerate = maxFramerate,
  812. CopyTimestamps = copyTimestamps ?? false,
  813. StartTimeTicks = startTimeTicks,
  814. Width = width,
  815. Height = height,
  816. MaxWidth = maxWidth,
  817. MaxHeight = maxHeight,
  818. VideoBitRate = videoBitRate,
  819. SubtitleStreamIndex = subtitleStreamIndex,
  820. SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
  821. MaxRefFrames = maxRefFrames,
  822. MaxVideoBitDepth = maxVideoBitDepth,
  823. RequireAvc = requireAvc ?? false,
  824. DeInterlace = deInterlace ?? false,
  825. RequireNonAnamorphic = requireNonAnamorphic ?? false,
  826. TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
  827. CpuCoreLimit = cpuCoreLimit,
  828. LiveStreamId = liveStreamId,
  829. EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
  830. VideoCodec = videoCodec,
  831. SubtitleCodec = subtitleCodec,
  832. TranscodeReasons = transcodeReasons,
  833. AudioStreamIndex = audioStreamIndex,
  834. VideoStreamIndex = videoStreamIndex,
  835. Context = context ?? EncodingContext.Streaming,
  836. StreamOptions = streamOptions
  837. };
  838. return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource)
  839. .ConfigureAwait(false);
  840. }
  841. /// <summary>
  842. /// Gets an audio stream using HTTP live streaming.
  843. /// </summary>
  844. /// <param name="itemId">The item id.</param>
  845. /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
  846. /// <param name="params">The streaming parameters.</param>
  847. /// <param name="tag">The tag.</param>
  848. /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
  849. /// <param name="playSessionId">The play session id.</param>
  850. /// <param name="segmentContainer">The segment container.</param>
  851. /// <param name="segmentLength">The segment length.</param>
  852. /// <param name="minSegments">The minimum number of segments.</param>
  853. /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
  854. /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
  855. /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
  856. /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
  857. /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
  858. /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
  859. /// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
  860. /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
  861. /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
  862. /// <param name="maxStreamingBitrate">Optional. The maximum streaming bitrate.</param>
  863. /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
  864. /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
  865. /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
  866. /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
  867. /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
  868. /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  869. /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  870. /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
  871. /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
  872. /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
  873. /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
  874. /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
  875. /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
  876. /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
  877. /// <param name="maxRefFrames">Optional.</param>
  878. /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
  879. /// <param name="requireAvc">Optional. Whether to require avc.</param>
  880. /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
  881. /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
  882. /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
  883. /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
  884. /// <param name="liveStreamId">The live stream id.</param>
  885. /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
  886. /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.</param>
  887. /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
  888. /// <param name="transcodeReasons">Optional. The transcoding reason.</param>
  889. /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
  890. /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
  891. /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
  892. /// <param name="streamOptions">Optional. The streaming options.</param>
  893. /// <response code="200">Audio stream returned.</response>
  894. /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
  895. [HttpGet("Audio/{itemId}/main.m3u8")]
  896. [ProducesResponseType(StatusCodes.Status200OK)]
  897. [ProducesPlaylistFile]
  898. public async Task<ActionResult> GetVariantHlsAudioPlaylist(
  899. [FromRoute, Required] Guid itemId,
  900. [FromQuery] bool? @static,
  901. [FromQuery] string? @params,
  902. [FromQuery] string? tag,
  903. [FromQuery] string? deviceProfileId,
  904. [FromQuery] string? playSessionId,
  905. [FromQuery] string? segmentContainer,
  906. [FromQuery] int? segmentLength,
  907. [FromQuery] int? minSegments,
  908. [FromQuery] string? mediaSourceId,
  909. [FromQuery] string? deviceId,
  910. [FromQuery] string? audioCodec,
  911. [FromQuery] bool? enableAutoStreamCopy,
  912. [FromQuery] bool? allowVideoStreamCopy,
  913. [FromQuery] bool? allowAudioStreamCopy,
  914. [FromQuery] bool? breakOnNonKeyFrames,
  915. [FromQuery] int? audioSampleRate,
  916. [FromQuery] int? maxAudioBitDepth,
  917. [FromQuery] int? maxStreamingBitrate,
  918. [FromQuery] int? audioBitRate,
  919. [FromQuery] int? audioChannels,
  920. [FromQuery] int? maxAudioChannels,
  921. [FromQuery] string? profile,
  922. [FromQuery] string? level,
  923. [FromQuery] float? framerate,
  924. [FromQuery] float? maxFramerate,
  925. [FromQuery] bool? copyTimestamps,
  926. [FromQuery] long? startTimeTicks,
  927. [FromQuery] int? width,
  928. [FromQuery] int? height,
  929. [FromQuery] int? videoBitRate,
  930. [FromQuery] int? subtitleStreamIndex,
  931. [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
  932. [FromQuery] int? maxRefFrames,
  933. [FromQuery] int? maxVideoBitDepth,
  934. [FromQuery] bool? requireAvc,
  935. [FromQuery] bool? deInterlace,
  936. [FromQuery] bool? requireNonAnamorphic,
  937. [FromQuery] int? transcodingMaxAudioChannels,
  938. [FromQuery] int? cpuCoreLimit,
  939. [FromQuery] string? liveStreamId,
  940. [FromQuery] bool? enableMpegtsM2TsMode,
  941. [FromQuery] string? videoCodec,
  942. [FromQuery] string? subtitleCodec,
  943. [FromQuery] string? transcodeReasons,
  944. [FromQuery] int? audioStreamIndex,
  945. [FromQuery] int? videoStreamIndex,
  946. [FromQuery] EncodingContext? context,
  947. [FromQuery] Dictionary<string, string> streamOptions)
  948. {
  949. using var cancellationTokenSource = new CancellationTokenSource();
  950. var streamingRequest = new StreamingRequestDto
  951. {
  952. Id = itemId,
  953. Static = @static ?? false,
  954. Params = @params,
  955. Tag = tag,
  956. DeviceProfileId = deviceProfileId,
  957. PlaySessionId = playSessionId,
  958. SegmentContainer = segmentContainer,
  959. SegmentLength = segmentLength,
  960. MinSegments = minSegments,
  961. MediaSourceId = mediaSourceId,
  962. DeviceId = deviceId,
  963. AudioCodec = audioCodec,
  964. EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
  965. AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
  966. AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
  967. BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
  968. AudioSampleRate = audioSampleRate,
  969. MaxAudioChannels = maxAudioChannels,
  970. AudioBitRate = audioBitRate ?? maxStreamingBitrate,
  971. MaxAudioBitDepth = maxAudioBitDepth,
  972. AudioChannels = audioChannels,
  973. Profile = profile,
  974. Level = level,
  975. Framerate = framerate,
  976. MaxFramerate = maxFramerate,
  977. CopyTimestamps = copyTimestamps ?? false,
  978. StartTimeTicks = startTimeTicks,
  979. Width = width,
  980. Height = height,
  981. VideoBitRate = videoBitRate,
  982. SubtitleStreamIndex = subtitleStreamIndex,
  983. SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
  984. MaxRefFrames = maxRefFrames,
  985. MaxVideoBitDepth = maxVideoBitDepth,
  986. RequireAvc = requireAvc ?? false,
  987. DeInterlace = deInterlace ?? false,
  988. RequireNonAnamorphic = requireNonAnamorphic ?? false,
  989. TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
  990. CpuCoreLimit = cpuCoreLimit,
  991. LiveStreamId = liveStreamId,
  992. EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
  993. VideoCodec = videoCodec,
  994. SubtitleCodec = subtitleCodec,
  995. TranscodeReasons = transcodeReasons,
  996. AudioStreamIndex = audioStreamIndex,
  997. VideoStreamIndex = videoStreamIndex,
  998. Context = context ?? EncodingContext.Streaming,
  999. StreamOptions = streamOptions
  1000. };
  1001. return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource)
  1002. .ConfigureAwait(false);
  1003. }
  1004. /// <summary>
  1005. /// Gets a video stream using HTTP live streaming.
  1006. /// </summary>
  1007. /// <param name="itemId">The item id.</param>
  1008. /// <param name="playlistId">The playlist id.</param>
  1009. /// <param name="segmentId">The segment id.</param>
  1010. /// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
  1011. /// <param name="runtimeTicks">The position of the requested segment in ticks.</param>
  1012. /// <param name="actualSegmentLengthTicks">The length of the requested segment in ticks.</param>
  1013. /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
  1014. /// <param name="params">The streaming parameters.</param>
  1015. /// <param name="tag">The tag.</param>
  1016. /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
  1017. /// <param name="playSessionId">The play session id.</param>
  1018. /// <param name="segmentContainer">The segment container.</param>
  1019. /// <param name="segmentLength">The desired segment length.</param>
  1020. /// <param name="minSegments">The minimum number of segments.</param>
  1021. /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
  1022. /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
  1023. /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
  1024. /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
  1025. /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
  1026. /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
  1027. /// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
  1028. /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
  1029. /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
  1030. /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
  1031. /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
  1032. /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
  1033. /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
  1034. /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
  1035. /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  1036. /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  1037. /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
  1038. /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
  1039. /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
  1040. /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
  1041. /// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
  1042. /// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
  1043. /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
  1044. /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
  1045. /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
  1046. /// <param name="maxRefFrames">Optional.</param>
  1047. /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
  1048. /// <param name="requireAvc">Optional. Whether to require avc.</param>
  1049. /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
  1050. /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
  1051. /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
  1052. /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
  1053. /// <param name="liveStreamId">The live stream id.</param>
  1054. /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
  1055. /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param>
  1056. /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
  1057. /// <param name="transcodeReasons">Optional. The transcoding reason.</param>
  1058. /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
  1059. /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
  1060. /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
  1061. /// <param name="streamOptions">Optional. The streaming options.</param>
  1062. /// <response code="200">Video stream returned.</response>
  1063. /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
  1064. [HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
  1065. [ProducesResponseType(StatusCodes.Status200OK)]
  1066. [ProducesVideoFile]
  1067. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
  1068. public async Task<ActionResult> GetHlsVideoSegment(
  1069. [FromRoute, Required] Guid itemId,
  1070. [FromRoute, Required] string playlistId,
  1071. [FromRoute, Required] int segmentId,
  1072. [FromRoute, Required] string container,
  1073. [FromQuery, Required] long runtimeTicks,
  1074. [FromQuery, Required] long actualSegmentLengthTicks,
  1075. [FromQuery] bool? @static,
  1076. [FromQuery] string? @params,
  1077. [FromQuery] string? tag,
  1078. [FromQuery] string? deviceProfileId,
  1079. [FromQuery] string? playSessionId,
  1080. [FromQuery] string? segmentContainer,
  1081. [FromQuery] int? segmentLength,
  1082. [FromQuery] int? minSegments,
  1083. [FromQuery] string? mediaSourceId,
  1084. [FromQuery] string? deviceId,
  1085. [FromQuery] string? audioCodec,
  1086. [FromQuery] bool? enableAutoStreamCopy,
  1087. [FromQuery] bool? allowVideoStreamCopy,
  1088. [FromQuery] bool? allowAudioStreamCopy,
  1089. [FromQuery] bool? breakOnNonKeyFrames,
  1090. [FromQuery] int? audioSampleRate,
  1091. [FromQuery] int? maxAudioBitDepth,
  1092. [FromQuery] int? audioBitRate,
  1093. [FromQuery] int? audioChannels,
  1094. [FromQuery] int? maxAudioChannels,
  1095. [FromQuery] string? profile,
  1096. [FromQuery] string? level,
  1097. [FromQuery] float? framerate,
  1098. [FromQuery] float? maxFramerate,
  1099. [FromQuery] bool? copyTimestamps,
  1100. [FromQuery] long? startTimeTicks,
  1101. [FromQuery] int? width,
  1102. [FromQuery] int? height,
  1103. [FromQuery] int? maxWidth,
  1104. [FromQuery] int? maxHeight,
  1105. [FromQuery] int? videoBitRate,
  1106. [FromQuery] int? subtitleStreamIndex,
  1107. [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
  1108. [FromQuery] int? maxRefFrames,
  1109. [FromQuery] int? maxVideoBitDepth,
  1110. [FromQuery] bool? requireAvc,
  1111. [FromQuery] bool? deInterlace,
  1112. [FromQuery] bool? requireNonAnamorphic,
  1113. [FromQuery] int? transcodingMaxAudioChannels,
  1114. [FromQuery] int? cpuCoreLimit,
  1115. [FromQuery] string? liveStreamId,
  1116. [FromQuery] bool? enableMpegtsM2TsMode,
  1117. [FromQuery] string? videoCodec,
  1118. [FromQuery] string? subtitleCodec,
  1119. [FromQuery] string? transcodeReasons,
  1120. [FromQuery] int? audioStreamIndex,
  1121. [FromQuery] int? videoStreamIndex,
  1122. [FromQuery] EncodingContext? context,
  1123. [FromQuery] Dictionary<string, string> streamOptions)
  1124. {
  1125. var streamingRequest = new VideoRequestDto
  1126. {
  1127. Id = itemId,
  1128. CurrentRuntimeTicks = runtimeTicks,
  1129. ActualSegmentLengthTicks = actualSegmentLengthTicks,
  1130. Container = container,
  1131. Static = @static ?? false,
  1132. Params = @params,
  1133. Tag = tag,
  1134. DeviceProfileId = deviceProfileId,
  1135. PlaySessionId = playSessionId,
  1136. SegmentContainer = segmentContainer,
  1137. SegmentLength = segmentLength,
  1138. MinSegments = minSegments,
  1139. MediaSourceId = mediaSourceId,
  1140. DeviceId = deviceId,
  1141. AudioCodec = audioCodec,
  1142. EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
  1143. AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
  1144. AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
  1145. BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
  1146. AudioSampleRate = audioSampleRate,
  1147. MaxAudioChannels = maxAudioChannels,
  1148. AudioBitRate = audioBitRate,
  1149. MaxAudioBitDepth = maxAudioBitDepth,
  1150. AudioChannels = audioChannels,
  1151. Profile = profile,
  1152. Level = level,
  1153. Framerate = framerate,
  1154. MaxFramerate = maxFramerate,
  1155. CopyTimestamps = copyTimestamps ?? false,
  1156. StartTimeTicks = startTimeTicks,
  1157. Width = width,
  1158. Height = height,
  1159. MaxWidth = maxWidth,
  1160. MaxHeight = maxHeight,
  1161. VideoBitRate = videoBitRate,
  1162. SubtitleStreamIndex = subtitleStreamIndex,
  1163. SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
  1164. MaxRefFrames = maxRefFrames,
  1165. MaxVideoBitDepth = maxVideoBitDepth,
  1166. RequireAvc = requireAvc ?? false,
  1167. DeInterlace = deInterlace ?? false,
  1168. RequireNonAnamorphic = requireNonAnamorphic ?? false,
  1169. TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
  1170. CpuCoreLimit = cpuCoreLimit,
  1171. LiveStreamId = liveStreamId,
  1172. EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
  1173. VideoCodec = videoCodec,
  1174. SubtitleCodec = subtitleCodec,
  1175. TranscodeReasons = transcodeReasons,
  1176. AudioStreamIndex = audioStreamIndex,
  1177. VideoStreamIndex = videoStreamIndex,
  1178. Context = context ?? EncodingContext.Streaming,
  1179. StreamOptions = streamOptions
  1180. };
  1181. return await GetDynamicSegment(streamingRequest, segmentId)
  1182. .ConfigureAwait(false);
  1183. }
  1184. /// <summary>
  1185. /// Gets a video stream using HTTP live streaming.
  1186. /// </summary>
  1187. /// <param name="itemId">The item id.</param>
  1188. /// <param name="playlistId">The playlist id.</param>
  1189. /// <param name="segmentId">The segment id.</param>
  1190. /// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
  1191. /// <param name="runtimeTicks">The position of the requested segment in ticks.</param>
  1192. /// <param name="actualSegmentLengthTicks">The length of the requested segment in ticks.</param>
  1193. /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
  1194. /// <param name="params">The streaming parameters.</param>
  1195. /// <param name="tag">The tag.</param>
  1196. /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
  1197. /// <param name="playSessionId">The play session id.</param>
  1198. /// <param name="segmentContainer">The segment container.</param>
  1199. /// <param name="segmentLength">The segment length.</param>
  1200. /// <param name="minSegments">The minimum number of segments.</param>
  1201. /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
  1202. /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
  1203. /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
  1204. /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
  1205. /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
  1206. /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
  1207. /// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
  1208. /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
  1209. /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
  1210. /// <param name="maxStreamingBitrate">Optional. The maximum streaming bitrate.</param>
  1211. /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
  1212. /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
  1213. /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
  1214. /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
  1215. /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
  1216. /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  1217. /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
  1218. /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
  1219. /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
  1220. /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
  1221. /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
  1222. /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
  1223. /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
  1224. /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
  1225. /// <param name="maxRefFrames">Optional.</param>
  1226. /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
  1227. /// <param name="requireAvc">Optional. Whether to require avc.</param>
  1228. /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
  1229. /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
  1230. /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
  1231. /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
  1232. /// <param name="liveStreamId">The live stream id.</param>
  1233. /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
  1234. /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.</param>
  1235. /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
  1236. /// <param name="transcodeReasons">Optional. The transcoding reason.</param>
  1237. /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
  1238. /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
  1239. /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
  1240. /// <param name="streamOptions">Optional. The streaming options.</param>
  1241. /// <response code="200">Video stream returned.</response>
  1242. /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
  1243. [HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
  1244. [ProducesResponseType(StatusCodes.Status200OK)]
  1245. [ProducesAudioFile]
  1246. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
  1247. public async Task<ActionResult> GetHlsAudioSegment(
  1248. [FromRoute, Required] Guid itemId,
  1249. [FromRoute, Required] string playlistId,
  1250. [FromRoute, Required] int segmentId,
  1251. [FromRoute, Required] string container,
  1252. [FromQuery, Required] long runtimeTicks,
  1253. [FromQuery, Required] long actualSegmentLengthTicks,
  1254. [FromQuery] bool? @static,
  1255. [FromQuery] string? @params,
  1256. [FromQuery] string? tag,
  1257. [FromQuery] string? deviceProfileId,
  1258. [FromQuery] string? playSessionId,
  1259. [FromQuery] string? segmentContainer,
  1260. [FromQuery] int? segmentLength,
  1261. [FromQuery] int? minSegments,
  1262. [FromQuery] string? mediaSourceId,
  1263. [FromQuery] string? deviceId,
  1264. [FromQuery] string? audioCodec,
  1265. [FromQuery] bool? enableAutoStreamCopy,
  1266. [FromQuery] bool? allowVideoStreamCopy,
  1267. [FromQuery] bool? allowAudioStreamCopy,
  1268. [FromQuery] bool? breakOnNonKeyFrames,
  1269. [FromQuery] int? audioSampleRate,
  1270. [FromQuery] int? maxAudioBitDepth,
  1271. [FromQuery] int? maxStreamingBitrate,
  1272. [FromQuery] int? audioBitRate,
  1273. [FromQuery] int? audioChannels,
  1274. [FromQuery] int? maxAudioChannels,
  1275. [FromQuery] string? profile,
  1276. [FromQuery] string? level,
  1277. [FromQuery] float? framerate,
  1278. [FromQuery] float? maxFramerate,
  1279. [FromQuery] bool? copyTimestamps,
  1280. [FromQuery] long? startTimeTicks,
  1281. [FromQuery] int? width,
  1282. [FromQuery] int? height,
  1283. [FromQuery] int? videoBitRate,
  1284. [FromQuery] int? subtitleStreamIndex,
  1285. [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
  1286. [FromQuery] int? maxRefFrames,
  1287. [FromQuery] int? maxVideoBitDepth,
  1288. [FromQuery] bool? requireAvc,
  1289. [FromQuery] bool? deInterlace,
  1290. [FromQuery] bool? requireNonAnamorphic,
  1291. [FromQuery] int? transcodingMaxAudioChannels,
  1292. [FromQuery] int? cpuCoreLimit,
  1293. [FromQuery] string? liveStreamId,
  1294. [FromQuery] bool? enableMpegtsM2TsMode,
  1295. [FromQuery] string? videoCodec,
  1296. [FromQuery] string? subtitleCodec,
  1297. [FromQuery] string? transcodeReasons,
  1298. [FromQuery] int? audioStreamIndex,
  1299. [FromQuery] int? videoStreamIndex,
  1300. [FromQuery] EncodingContext? context,
  1301. [FromQuery] Dictionary<string, string> streamOptions)
  1302. {
  1303. var streamingRequest = new StreamingRequestDto
  1304. {
  1305. Id = itemId,
  1306. Container = container,
  1307. CurrentRuntimeTicks = runtimeTicks,
  1308. ActualSegmentLengthTicks = actualSegmentLengthTicks,
  1309. Static = @static ?? false,
  1310. Params = @params,
  1311. Tag = tag,
  1312. DeviceProfileId = deviceProfileId,
  1313. PlaySessionId = playSessionId,
  1314. SegmentContainer = segmentContainer,
  1315. SegmentLength = segmentLength,
  1316. MinSegments = minSegments,
  1317. MediaSourceId = mediaSourceId,
  1318. DeviceId = deviceId,
  1319. AudioCodec = audioCodec,
  1320. EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
  1321. AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
  1322. AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
  1323. BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
  1324. AudioSampleRate = audioSampleRate,
  1325. MaxAudioChannels = maxAudioChannels,
  1326. AudioBitRate = audioBitRate ?? maxStreamingBitrate,
  1327. MaxAudioBitDepth = maxAudioBitDepth,
  1328. AudioChannels = audioChannels,
  1329. Profile = profile,
  1330. Level = level,
  1331. Framerate = framerate,
  1332. MaxFramerate = maxFramerate,
  1333. CopyTimestamps = copyTimestamps ?? false,
  1334. StartTimeTicks = startTimeTicks,
  1335. Width = width,
  1336. Height = height,
  1337. VideoBitRate = videoBitRate,
  1338. SubtitleStreamIndex = subtitleStreamIndex,
  1339. SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
  1340. MaxRefFrames = maxRefFrames,
  1341. MaxVideoBitDepth = maxVideoBitDepth,
  1342. RequireAvc = requireAvc ?? false,
  1343. DeInterlace = deInterlace ?? false,
  1344. RequireNonAnamorphic = requireNonAnamorphic ?? false,
  1345. TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
  1346. CpuCoreLimit = cpuCoreLimit,
  1347. LiveStreamId = liveStreamId,
  1348. EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
  1349. VideoCodec = videoCodec,
  1350. SubtitleCodec = subtitleCodec,
  1351. TranscodeReasons = transcodeReasons,
  1352. AudioStreamIndex = audioStreamIndex,
  1353. VideoStreamIndex = videoStreamIndex,
  1354. Context = context ?? EncodingContext.Streaming,
  1355. StreamOptions = streamOptions
  1356. };
  1357. return await GetDynamicSegment(streamingRequest, segmentId)
  1358. .ConfigureAwait(false);
  1359. }
  1360. private async Task<ActionResult> GetVariantPlaylistInternal(StreamingRequestDto streamingRequest, CancellationTokenSource cancellationTokenSource)
  1361. {
  1362. using var state = await StreamingHelpers.GetStreamingState(
  1363. streamingRequest,
  1364. HttpContext,
  1365. _mediaSourceManager,
  1366. _userManager,
  1367. _libraryManager,
  1368. _serverConfigurationManager,
  1369. _mediaEncoder,
  1370. _encodingHelper,
  1371. _dlnaManager,
  1372. _deviceManager,
  1373. _transcodingJobHelper,
  1374. TranscodingJobType,
  1375. cancellationTokenSource.Token)
  1376. .ConfigureAwait(false);
  1377. var request = new CreateMainPlaylistRequest(
  1378. state.MediaPath,
  1379. state.SegmentLength * 1000,
  1380. state.RunTimeTicks ?? 0,
  1381. state.Request.SegmentContainer ?? string.Empty,
  1382. "hls1/main/",
  1383. Request.QueryString.ToString(),
  1384. EncodingHelper.IsCopyCodec(state.OutputVideoCodec));
  1385. var playlist = _dynamicHlsPlaylistGenerator.CreateMainPlaylist(request);
  1386. return new FileContentResult(Encoding.UTF8.GetBytes(playlist), MimeTypes.GetMimeType("playlist.m3u8"));
  1387. }
  1388. private async Task<ActionResult> GetDynamicSegment(StreamingRequestDto streamingRequest, int segmentId)
  1389. {
  1390. if ((streamingRequest.StartTimeTicks ?? 0) > 0)
  1391. {
  1392. throw new ArgumentException("StartTimeTicks is not allowed.");
  1393. }
  1394. // CTS lifecycle is managed internally.
  1395. var cancellationTokenSource = new CancellationTokenSource();
  1396. var cancellationToken = cancellationTokenSource.Token;
  1397. var state = await StreamingHelpers.GetStreamingState(
  1398. streamingRequest,
  1399. HttpContext,
  1400. _mediaSourceManager,
  1401. _userManager,
  1402. _libraryManager,
  1403. _serverConfigurationManager,
  1404. _mediaEncoder,
  1405. _encodingHelper,
  1406. _dlnaManager,
  1407. _deviceManager,
  1408. _transcodingJobHelper,
  1409. TranscodingJobType,
  1410. cancellationToken)
  1411. .ConfigureAwait(false);
  1412. var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
  1413. var segmentPath = GetSegmentPath(state, playlistPath, segmentId);
  1414. var segmentExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer);
  1415. TranscodingJobDto? job;
  1416. if (System.IO.File.Exists(segmentPath))
  1417. {
  1418. job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
  1419. _logger.LogDebug("returning {0} [it exists, try 1]", segmentPath);
  1420. return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
  1421. }
  1422. var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath);
  1423. await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
  1424. var released = false;
  1425. var startTranscoding = false;
  1426. try
  1427. {
  1428. if (System.IO.File.Exists(segmentPath))
  1429. {
  1430. job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
  1431. transcodingLock.Release();
  1432. released = true;
  1433. _logger.LogDebug("returning {0} [it exists, try 2]", segmentPath);
  1434. return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
  1435. }
  1436. else
  1437. {
  1438. var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
  1439. var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength;
  1440. if (segmentId == -1)
  1441. {
  1442. _logger.LogDebug("Starting transcoding because fmp4 init file is being requested");
  1443. startTranscoding = true;
  1444. segmentId = 0;
  1445. }
  1446. else if (currentTranscodingIndex is null)
  1447. {
  1448. _logger.LogDebug("Starting transcoding because currentTranscodingIndex=null");
  1449. startTranscoding = true;
  1450. }
  1451. else if (segmentId < currentTranscodingIndex.Value)
  1452. {
  1453. _logger.LogDebug("Starting transcoding because requestedIndex={0} and currentTranscodingIndex={1}", segmentId, currentTranscodingIndex);
  1454. startTranscoding = true;
  1455. }
  1456. else if (segmentId - currentTranscodingIndex.Value > segmentGapRequiringTranscodingChange)
  1457. {
  1458. _logger.LogDebug("Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}", segmentId - currentTranscodingIndex.Value, segmentGapRequiringTranscodingChange, segmentId);
  1459. startTranscoding = true;
  1460. }
  1461. if (startTranscoding)
  1462. {
  1463. // If the playlist doesn't already exist, startup ffmpeg
  1464. try
  1465. {
  1466. await _transcodingJobHelper.KillTranscodingJobs(streamingRequest.DeviceId, streamingRequest.PlaySessionId, p => false)
  1467. .ConfigureAwait(false);
  1468. if (currentTranscodingIndex.HasValue)
  1469. {
  1470. DeleteLastFile(playlistPath, segmentExtension, 0);
  1471. }
  1472. streamingRequest.StartTimeTicks = streamingRequest.CurrentRuntimeTicks;
  1473. state.WaitForPath = segmentPath;
  1474. job = await _transcodingJobHelper.StartFfMpeg(
  1475. state,
  1476. playlistPath,
  1477. GetCommandLineArguments(playlistPath, state, false, segmentId),
  1478. Request,
  1479. TranscodingJobType,
  1480. cancellationTokenSource).ConfigureAwait(false);
  1481. }
  1482. catch
  1483. {
  1484. state.Dispose();
  1485. throw;
  1486. }
  1487. // await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
  1488. }
  1489. else
  1490. {
  1491. job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
  1492. if (job?.TranscodingThrottler is not null)
  1493. {
  1494. await job.TranscodingThrottler.UnpauseTranscoding().ConfigureAwait(false);
  1495. }
  1496. }
  1497. }
  1498. }
  1499. finally
  1500. {
  1501. if (!released)
  1502. {
  1503. transcodingLock.Release();
  1504. }
  1505. }
  1506. _logger.LogDebug("returning {0} [general case]", segmentPath);
  1507. job ??= _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
  1508. return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
  1509. }
  1510. private static double[] GetSegmentLengths(StreamState state)
  1511. => GetSegmentLengthsInternal(state.RunTimeTicks ?? 0, state.SegmentLength);
  1512. internal static double[] GetSegmentLengthsInternal(long runtimeTicks, int segmentlength)
  1513. {
  1514. var segmentLengthTicks = TimeSpan.FromSeconds(segmentlength).Ticks;
  1515. var wholeSegments = runtimeTicks / segmentLengthTicks;
  1516. var remainingTicks = runtimeTicks % segmentLengthTicks;
  1517. var segmentsLen = wholeSegments + (remainingTicks == 0 ? 0 : 1);
  1518. var segments = new double[segmentsLen];
  1519. for (int i = 0; i < wholeSegments; i++)
  1520. {
  1521. segments[i] = segmentlength;
  1522. }
  1523. if (remainingTicks != 0)
  1524. {
  1525. segments[^1] = TimeSpan.FromTicks(remainingTicks).TotalSeconds;
  1526. }
  1527. return segments;
  1528. }
  1529. private string GetCommandLineArguments(string outputPath, StreamState state, bool isEventPlaylist, int startNumber)
  1530. {
  1531. var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
  1532. var threads = EncodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec);
  1533. if (state.BaseRequest.BreakOnNonKeyFrames)
  1534. {
  1535. // FIXME: this is actually a workaround, as ideally it really should be the client which decides whether non-keyframe
  1536. // breakpoints are supported; but current implementation always uses "ffmpeg input seeking" which is liable
  1537. // to produce a missing part of video stream before first keyframe is encountered, which may lead to
  1538. // awkward cases like a few starting HLS segments having no video whatsoever, which breaks hls.js
  1539. _logger.LogInformation("Current HLS implementation doesn't support non-keyframe breaks but one is requested, ignoring that request");
  1540. state.BaseRequest.BreakOnNonKeyFrames = false;
  1541. }
  1542. var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty;
  1543. var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
  1544. var outputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputPath);
  1545. var outputPrefix = Path.Combine(directory, outputFileNameWithoutExtension);
  1546. var outputExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer);
  1547. var outputTsArg = outputPrefix + "%d" + outputExtension;
  1548. var segmentFormat = string.Empty;
  1549. var segmentContainer = outputExtension.TrimStart('.');
  1550. var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions, segmentContainer);
  1551. if (string.Equals(segmentContainer, "ts", StringComparison.OrdinalIgnoreCase))
  1552. {
  1553. segmentFormat = "mpegts";
  1554. }
  1555. else if (string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase))
  1556. {
  1557. var outputFmp4HeaderArg = OperatingSystem.IsWindows() switch
  1558. {
  1559. // on Windows, the path of fmp4 header file needs to be configured
  1560. true => " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"",
  1561. // on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder
  1562. false => " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\""
  1563. };
  1564. segmentFormat = "fmp4" + outputFmp4HeaderArg;
  1565. }
  1566. else
  1567. {
  1568. _logger.LogError("Invalid HLS segment container: {SegmentContainer}, default to mpegts", segmentContainer);
  1569. segmentFormat = "mpegts";
  1570. }
  1571. var maxMuxingQueueSize = _encodingOptions.MaxMuxingQueueSize > 128
  1572. ? _encodingOptions.MaxMuxingQueueSize.ToString(CultureInfo.InvariantCulture)
  1573. : "128";
  1574. var baseUrlParam = string.Empty;
  1575. if (isEventPlaylist)
  1576. {
  1577. baseUrlParam = string.Format(
  1578. CultureInfo.InvariantCulture,
  1579. " -hls_base_url \"hls/{0}/\"",
  1580. Path.GetFileNameWithoutExtension(outputPath));
  1581. }
  1582. return string.Format(
  1583. CultureInfo.InvariantCulture,
  1584. "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9}{10} -hls_segment_filename \"{12}\" -hls_playlist_type {11} -hls_list_size 0 -y \"{13}\"",
  1585. inputModifier,
  1586. _encodingHelper.GetInputArgument(state, _encodingOptions, segmentContainer),
  1587. threads,
  1588. mapArgs,
  1589. GetVideoArguments(state, startNumber, isEventPlaylist),
  1590. GetAudioArguments(state),
  1591. maxMuxingQueueSize,
  1592. state.SegmentLength.ToString(CultureInfo.InvariantCulture),
  1593. segmentFormat,
  1594. startNumber.ToString(CultureInfo.InvariantCulture),
  1595. baseUrlParam,
  1596. isEventPlaylist ? "event" : "vod",
  1597. outputTsArg,
  1598. outputPath).Trim();
  1599. }
  1600. /// <summary>
  1601. /// Gets the audio arguments for transcoding.
  1602. /// </summary>
  1603. /// <param name="state">The <see cref="StreamState"/>.</param>
  1604. /// <returns>The command line arguments for audio transcoding.</returns>
  1605. private string GetAudioArguments(StreamState state)
  1606. {
  1607. if (state.AudioStream is null)
  1608. {
  1609. return string.Empty;
  1610. }
  1611. var audioCodec = _encodingHelper.GetAudioEncoder(state);
  1612. if (!state.IsOutputVideo)
  1613. {
  1614. if (EncodingHelper.IsCopyCodec(audioCodec))
  1615. {
  1616. var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
  1617. return "-acodec copy -strict -2" + bitStreamArgs;
  1618. }
  1619. var audioTranscodeParams = string.Empty;
  1620. audioTranscodeParams += "-acodec " + audioCodec;
  1621. if (state.OutputAudioBitrate.HasValue)
  1622. {
  1623. audioTranscodeParams += " -ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture);
  1624. }
  1625. if (state.OutputAudioChannels.HasValue)
  1626. {
  1627. audioTranscodeParams += " -ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture);
  1628. }
  1629. if (state.OutputAudioSampleRate.HasValue)
  1630. {
  1631. audioTranscodeParams += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
  1632. }
  1633. audioTranscodeParams += " -vn";
  1634. return audioTranscodeParams;
  1635. }
  1636. // dts, flac, opus and truehd are experimental in mp4 muxer
  1637. var strictArgs = string.Empty;
  1638. if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
  1639. || string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)
  1640. || string.Equals(state.ActualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase)
  1641. || string.Equals(state.ActualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase))
  1642. {
  1643. strictArgs = " -strict -2";
  1644. }
  1645. if (EncodingHelper.IsCopyCodec(audioCodec))
  1646. {
  1647. var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
  1648. var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
  1649. var copyArgs = "-codec:a:0 copy" + bitStreamArgs + strictArgs;
  1650. if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec))
  1651. {
  1652. return copyArgs + " -copypriorss:a:0 0";
  1653. }
  1654. return copyArgs;
  1655. }
  1656. var args = "-codec:a:0 " + audioCodec + strictArgs;
  1657. var channels = state.OutputAudioChannels;
  1658. if (channels.HasValue
  1659. && (channels.Value != 2
  1660. || (state.AudioStream is not null
  1661. && state.AudioStream.Channels.HasValue
  1662. && state.AudioStream.Channels.Value > 5
  1663. && _encodingOptions.DownMixStereoAlgorithm == DownMixStereoAlgorithms.None)))
  1664. {
  1665. args += " -ac " + channels.Value;
  1666. }
  1667. var bitrate = state.OutputAudioBitrate;
  1668. if (bitrate.HasValue)
  1669. {
  1670. args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
  1671. }
  1672. if (state.OutputAudioSampleRate.HasValue)
  1673. {
  1674. args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
  1675. }
  1676. args += _encodingHelper.GetAudioFilterParam(state, _encodingOptions);
  1677. return args;
  1678. }
  1679. /// <summary>
  1680. /// Gets the video arguments for transcoding.
  1681. /// </summary>
  1682. /// <param name="state">The <see cref="StreamState"/>.</param>
  1683. /// <param name="startNumber">The first number in the hls sequence.</param>
  1684. /// <param name="isEventPlaylist">Whether the playlist is EVENT or VOD.</param>
  1685. /// <returns>The command line arguments for video transcoding.</returns>
  1686. private string GetVideoArguments(StreamState state, int startNumber, bool isEventPlaylist)
  1687. {
  1688. if (state.VideoStream is null)
  1689. {
  1690. return string.Empty;
  1691. }
  1692. if (!state.IsOutputVideo)
  1693. {
  1694. return string.Empty;
  1695. }
  1696. var codec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
  1697. var args = "-codec:v:0 " + codec;
  1698. if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
  1699. || string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
  1700. || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
  1701. || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
  1702. {
  1703. if (EncodingHelper.IsCopyCodec(codec)
  1704. && (string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase)
  1705. || string.Equals(state.VideoStream.CodecTag, "dovi", StringComparison.OrdinalIgnoreCase)
  1706. || string.Equals(state.VideoStream.CodecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
  1707. || string.Equals(state.VideoStream.CodecTag, "dvhe", StringComparison.OrdinalIgnoreCase)))
  1708. {
  1709. // Prefer dvh1 to dvhe
  1710. args += " -tag:v:0 dvh1 -strict -2";
  1711. }
  1712. else
  1713. {
  1714. // Prefer hvc1 to hev1
  1715. args += " -tag:v:0 hvc1";
  1716. }
  1717. }
  1718. // if (state.EnableMpegtsM2TsMode)
  1719. // {
  1720. // args += " -mpegts_m2ts_mode 1";
  1721. // }
  1722. // See if we can save come cpu cycles by avoiding encoding.
  1723. if (EncodingHelper.IsCopyCodec(codec))
  1724. {
  1725. // If h264_mp4toannexb is ever added, do not use it for live tv.
  1726. if (state.VideoStream is not null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
  1727. {
  1728. string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream);
  1729. if (!string.IsNullOrEmpty(bitStreamArgs))
  1730. {
  1731. args += " " + bitStreamArgs;
  1732. }
  1733. }
  1734. args += " -start_at_zero";
  1735. }
  1736. else
  1737. {
  1738. args += _encodingHelper.GetVideoQualityParam(state, codec, _encodingOptions, isEventPlaylist ? DefaultEventEncoderPreset : DefaultVodEncoderPreset);
  1739. // Set the key frame params for video encoding to match the hls segment time.
  1740. args += _encodingHelper.GetHlsVideoKeyFrameArguments(state, codec, state.SegmentLength, isEventPlaylist, startNumber);
  1741. // Currently b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now.
  1742. if (string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase))
  1743. {
  1744. args += " -bf 0";
  1745. }
  1746. // args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
  1747. // video processing filters.
  1748. args += _encodingHelper.GetVideoProcessingFilterParam(state, _encodingOptions, codec);
  1749. // -start_at_zero is necessary to use with -ss when seeking,
  1750. // otherwise the target position cannot be determined.
  1751. if (state.SubtitleStream is not null)
  1752. {
  1753. // Disable start_at_zero for external graphical subs
  1754. if (!(state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream))
  1755. {
  1756. args += " -start_at_zero";
  1757. }
  1758. }
  1759. }
  1760. // TODO why was this not enabled for VOD?
  1761. if (isEventPlaylist)
  1762. {
  1763. args += " -flags -global_header";
  1764. }
  1765. if (!string.IsNullOrEmpty(state.OutputVideoSync))
  1766. {
  1767. args += " -vsync " + state.OutputVideoSync;
  1768. }
  1769. args += _encodingHelper.GetOutputFFlags(state);
  1770. return args;
  1771. }
  1772. private string GetSegmentPath(StreamState state, string playlist, int index)
  1773. {
  1774. var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException($"Provided path ({playlist}) is not valid.", nameof(playlist));
  1775. var filename = Path.GetFileNameWithoutExtension(playlist);
  1776. return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer));
  1777. }
  1778. private async Task<ActionResult> GetSegmentResult(
  1779. StreamState state,
  1780. string playlistPath,
  1781. string segmentPath,
  1782. string segmentExtension,
  1783. int segmentIndex,
  1784. TranscodingJobDto? transcodingJob,
  1785. CancellationToken cancellationToken)
  1786. {
  1787. var segmentExists = System.IO.File.Exists(segmentPath);
  1788. if (segmentExists)
  1789. {
  1790. if (transcodingJob is not null && transcodingJob.HasExited)
  1791. {
  1792. // Transcoding job is over, so assume all existing files are ready
  1793. _logger.LogDebug("serving up {0} as transcode is over", segmentPath);
  1794. return GetSegmentResult(state, segmentPath, transcodingJob);
  1795. }
  1796. var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
  1797. // If requested segment is less than transcoding position, we can't transcode backwards, so assume it's ready
  1798. if (segmentIndex < currentTranscodingIndex)
  1799. {
  1800. _logger.LogDebug("serving up {0} as transcode index {1} is past requested point {2}", segmentPath, currentTranscodingIndex, segmentIndex);
  1801. return GetSegmentResult(state, segmentPath, transcodingJob);
  1802. }
  1803. }
  1804. var nextSegmentPath = GetSegmentPath(state, playlistPath, segmentIndex + 1);
  1805. if (transcodingJob is not null)
  1806. {
  1807. while (!cancellationToken.IsCancellationRequested && !transcodingJob.HasExited)
  1808. {
  1809. // To be considered ready, the segment file has to exist AND
  1810. // either the transcoding job should be done or next segment should also exist
  1811. if (segmentExists)
  1812. {
  1813. if (transcodingJob.HasExited || System.IO.File.Exists(nextSegmentPath))
  1814. {
  1815. _logger.LogDebug("Serving up {SegmentPath} as it deemed ready", segmentPath);
  1816. return GetSegmentResult(state, segmentPath, transcodingJob);
  1817. }
  1818. }
  1819. else
  1820. {
  1821. segmentExists = System.IO.File.Exists(segmentPath);
  1822. if (segmentExists)
  1823. {
  1824. continue; // avoid unnecessary waiting if segment just became available
  1825. }
  1826. }
  1827. await Task.Delay(100, cancellationToken).ConfigureAwait(false);
  1828. }
  1829. if (!System.IO.File.Exists(segmentPath))
  1830. {
  1831. _logger.LogWarning("cannot serve {0} as transcoding quit before we got there", segmentPath);
  1832. }
  1833. else
  1834. {
  1835. _logger.LogDebug("serving {0} as it's on disk and transcoding stopped", segmentPath);
  1836. }
  1837. cancellationToken.ThrowIfCancellationRequested();
  1838. }
  1839. else
  1840. {
  1841. _logger.LogWarning("cannot serve {0} as it doesn't exist and no transcode is running", segmentPath);
  1842. }
  1843. return GetSegmentResult(state, segmentPath, transcodingJob);
  1844. }
  1845. private ActionResult GetSegmentResult(StreamState state, string segmentPath, TranscodingJobDto? transcodingJob)
  1846. {
  1847. var segmentEndingPositionTicks = state.Request.CurrentRuntimeTicks + state.Request.ActualSegmentLengthTicks;
  1848. Response.OnCompleted(() =>
  1849. {
  1850. _logger.LogDebug("Finished serving {SegmentPath}", segmentPath);
  1851. if (transcodingJob is not null)
  1852. {
  1853. transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
  1854. _transcodingJobHelper.OnTranscodeEndRequest(transcodingJob);
  1855. }
  1856. return Task.CompletedTask;
  1857. });
  1858. return FileStreamResponseHelpers.GetStaticFileResult(segmentPath, MimeTypes.GetMimeType(segmentPath));
  1859. }
  1860. private int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
  1861. {
  1862. var job = _transcodingJobHelper.GetTranscodingJob(playlist, TranscodingJobType);
  1863. if (job is null || job.HasExited)
  1864. {
  1865. return null;
  1866. }
  1867. var file = GetLastTranscodingFile(playlist, segmentExtension, _fileSystem);
  1868. if (file is null)
  1869. {
  1870. return null;
  1871. }
  1872. var playlistFilename = Path.GetFileNameWithoutExtension(playlist);
  1873. var indexString = Path.GetFileNameWithoutExtension(file.Name).Substring(playlistFilename.Length);
  1874. return int.Parse(indexString, NumberStyles.Integer, CultureInfo.InvariantCulture);
  1875. }
  1876. private static FileSystemMetadata? GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem)
  1877. {
  1878. var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException("Path can't be a root directory.", nameof(playlist));
  1879. var filePrefix = Path.GetFileNameWithoutExtension(playlist);
  1880. try
  1881. {
  1882. return fileSystem.GetFiles(folder, new[] { segmentExtension }, true, false)
  1883. .Where(i => Path.GetFileNameWithoutExtension(i.Name).StartsWith(filePrefix, StringComparison.OrdinalIgnoreCase))
  1884. .OrderByDescending(fileSystem.GetLastWriteTimeUtc)
  1885. .FirstOrDefault();
  1886. }
  1887. catch (IOException)
  1888. {
  1889. return null;
  1890. }
  1891. }
  1892. private void DeleteLastFile(string playlistPath, string segmentExtension, int retryCount)
  1893. {
  1894. var file = GetLastTranscodingFile(playlistPath, segmentExtension, _fileSystem);
  1895. if (file is not null)
  1896. {
  1897. DeleteFile(file.FullName, retryCount);
  1898. }
  1899. }
  1900. private void DeleteFile(string path, int retryCount)
  1901. {
  1902. if (retryCount >= 5)
  1903. {
  1904. return;
  1905. }
  1906. _logger.LogDebug("Deleting partial HLS file {Path}", path);
  1907. try
  1908. {
  1909. _fileSystem.DeleteFile(path);
  1910. }
  1911. catch (IOException ex)
  1912. {
  1913. _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
  1914. var task = Task.Delay(100);
  1915. task.Wait();
  1916. DeleteFile(path, retryCount + 1);
  1917. }
  1918. catch (Exception ex)
  1919. {
  1920. _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
  1921. }
  1922. }
  1923. }