DynamicHlsController.cs 101 KB

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