DynamicHlsController.cs 112 KB

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