StreamingHelpers.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using Jellyfin.Api.Models.StreamingDtos;
  9. using MediaBrowser.Common.Configuration;
  10. using MediaBrowser.Common.Extensions;
  11. using MediaBrowser.Controller.Configuration;
  12. using MediaBrowser.Controller.Devices;
  13. using MediaBrowser.Controller.Dlna;
  14. using MediaBrowser.Controller.Library;
  15. using MediaBrowser.Controller.MediaEncoding;
  16. using MediaBrowser.Controller.Net;
  17. using MediaBrowser.Model.Dlna;
  18. using MediaBrowser.Model.Dto;
  19. using MediaBrowser.Model.Entities;
  20. using MediaBrowser.Model.IO;
  21. using Microsoft.AspNetCore.Http;
  22. using Microsoft.Extensions.Configuration;
  23. using Microsoft.Extensions.Primitives;
  24. using Microsoft.Net.Http.Headers;
  25. namespace Jellyfin.Api.Helpers
  26. {
  27. /// <summary>
  28. /// The streaming helpers.
  29. /// </summary>
  30. public static class StreamingHelpers
  31. {
  32. /// <summary>
  33. /// Gets the current streaming state.
  34. /// </summary>
  35. /// <param name="streamingRequest">The <see cref="StreamingRequestDto"/>.</param>
  36. /// <param name="httpRequest">The <see cref="HttpRequest"/>.</param>
  37. /// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
  38. /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
  39. /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
  40. /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
  41. /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
  42. /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
  43. /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
  44. /// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
  45. /// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
  46. /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
  47. /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
  48. /// <param name="transcodingJobHelper">Initialized <see cref="TranscodingJobHelper"/>.</param>
  49. /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
  50. /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
  51. /// <returns>A <see cref="Task"/> containing the current <see cref="StreamState"/>.</returns>
  52. public static async Task<StreamState> GetStreamingState(
  53. StreamingRequestDto streamingRequest,
  54. HttpRequest httpRequest,
  55. IAuthorizationContext authorizationContext,
  56. IMediaSourceManager mediaSourceManager,
  57. IUserManager userManager,
  58. ILibraryManager libraryManager,
  59. IServerConfigurationManager serverConfigurationManager,
  60. IMediaEncoder mediaEncoder,
  61. IFileSystem fileSystem,
  62. ISubtitleEncoder subtitleEncoder,
  63. IConfiguration configuration,
  64. IDlnaManager dlnaManager,
  65. IDeviceManager deviceManager,
  66. TranscodingJobHelper transcodingJobHelper,
  67. TranscodingJobType transcodingJobType,
  68. CancellationToken cancellationToken)
  69. {
  70. EncodingHelper encodingHelper = new EncodingHelper(mediaEncoder, fileSystem, subtitleEncoder, configuration);
  71. // Parse the DLNA time seek header
  72. if (!streamingRequest.StartTimeTicks.HasValue)
  73. {
  74. var timeSeek = httpRequest.Headers["TimeSeekRange.dlna.org"];
  75. streamingRequest.StartTimeTicks = ParseTimeSeekHeader(timeSeek.ToString());
  76. }
  77. if (!string.IsNullOrWhiteSpace(streamingRequest.Params))
  78. {
  79. ParseParams(streamingRequest);
  80. }
  81. streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query);
  82. if (httpRequest.Path.Value == null)
  83. {
  84. throw new ResourceNotFoundException(nameof(httpRequest.Path));
  85. }
  86. var url = httpRequest.Path.Value.Split('.')[^1];
  87. if (string.IsNullOrEmpty(streamingRequest.AudioCodec))
  88. {
  89. streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url);
  90. }
  91. var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) ||
  92. string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase);
  93. var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper)
  94. {
  95. Request = streamingRequest,
  96. RequestedUrl = url,
  97. UserAgent = httpRequest.Headers[HeaderNames.UserAgent],
  98. EnableDlnaHeaders = enableDlnaHeaders
  99. };
  100. var auth = authorizationContext.GetAuthorizationInfo(httpRequest);
  101. if (!auth.UserId.Equals(Guid.Empty))
  102. {
  103. state.User = userManager.GetUserById(auth.UserId);
  104. }
  105. if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.Request.VideoCodec))
  106. {
  107. state.SupportedVideoCodecs = state.Request.VideoCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
  108. state.Request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
  109. }
  110. if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec))
  111. {
  112. state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
  113. state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToAudioCodec(i))
  114. ?? state.SupportedAudioCodecs.FirstOrDefault();
  115. }
  116. if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec))
  117. {
  118. state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
  119. state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToSubtitleCodec(i))
  120. ?? state.SupportedSubtitleCodecs.FirstOrDefault();
  121. }
  122. var item = libraryManager.GetItemById(streamingRequest.Id);
  123. state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
  124. MediaSourceInfo? mediaSource = null;
  125. if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId))
  126. {
  127. var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId)
  128. ? transcodingJobHelper.GetTranscodingJob(streamingRequest.PlaySessionId)
  129. : null;
  130. if (currentJob != null)
  131. {
  132. mediaSource = currentJob.MediaSource;
  133. }
  134. if (mediaSource == null)
  135. {
  136. var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById(streamingRequest.Id), null, false, false, cancellationToken).ConfigureAwait(false);
  137. mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId)
  138. ? mediaSources[0]
  139. : mediaSources.Find(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.InvariantCulture));
  140. if (mediaSource == null && Guid.Parse(streamingRequest.MediaSourceId) == streamingRequest.Id)
  141. {
  142. mediaSource = mediaSources[0];
  143. }
  144. }
  145. }
  146. else
  147. {
  148. var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStreamId, cancellationToken).ConfigureAwait(false);
  149. mediaSource = liveStreamInfo.Item1;
  150. state.DirectStreamProvider = liveStreamInfo.Item2;
  151. }
  152. encodingHelper.AttachMediaSourceInfo(state, mediaSource, url);
  153. string? containerInternal = Path.GetExtension(state.RequestedUrl);
  154. if (!string.IsNullOrEmpty(streamingRequest.Container))
  155. {
  156. containerInternal = streamingRequest.Container;
  157. }
  158. if (string.IsNullOrEmpty(containerInternal))
  159. {
  160. containerInternal = streamingRequest.Static ?
  161. StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, state.MediaPath, null, DlnaProfileType.Audio)
  162. : GetOutputFileExtension(state);
  163. }
  164. state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
  165. state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, state.AudioStream);
  166. state.OutputAudioCodec = streamingRequest.AudioCodec;
  167. state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec);
  168. if (state.VideoRequest != null)
  169. {
  170. state.OutputVideoCodec = state.Request.VideoCodec;
  171. state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
  172. encodingHelper.TryStreamCopy(state);
  173. if (state.OutputVideoBitrate.HasValue && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
  174. {
  175. var resolution = ResolutionNormalizer.Normalize(
  176. state.VideoStream?.BitRate,
  177. state.VideoStream?.Width,
  178. state.VideoStream?.Height,
  179. state.OutputVideoBitrate.Value,
  180. state.VideoStream?.Codec,
  181. state.OutputVideoCodec,
  182. state.VideoRequest.MaxWidth,
  183. state.VideoRequest.MaxHeight);
  184. state.VideoRequest.MaxWidth = resolution.MaxWidth;
  185. state.VideoRequest.MaxHeight = resolution.MaxHeight;
  186. }
  187. }
  188. ApplyDeviceProfileSettings(state, dlnaManager, deviceManager, httpRequest, streamingRequest.DeviceProfileId, streamingRequest.Static);
  189. var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
  190. ? GetOutputFileExtension(state)
  191. : ('.' + state.OutputContainer);
  192. state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId);
  193. return state;
  194. }
  195. /// <summary>
  196. /// Adds the dlna headers.
  197. /// </summary>
  198. /// <param name="state">The state.</param>
  199. /// <param name="responseHeaders">The response headers.</param>
  200. /// <param name="isStaticallyStreamed">if set to <c>true</c> [is statically streamed].</param>
  201. /// <param name="startTimeTicks">The start time in ticks.</param>
  202. /// <param name="request">The <see cref="HttpRequest"/>.</param>
  203. /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
  204. public static void AddDlnaHeaders(
  205. StreamState state,
  206. IHeaderDictionary responseHeaders,
  207. bool isStaticallyStreamed,
  208. long? startTimeTicks,
  209. HttpRequest request,
  210. IDlnaManager dlnaManager)
  211. {
  212. if (!state.EnableDlnaHeaders)
  213. {
  214. return;
  215. }
  216. var profile = state.DeviceProfile;
  217. StringValues transferMode = request.Headers["transferMode.dlna.org"];
  218. responseHeaders.Add("transferMode.dlna.org", string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode.ToString());
  219. responseHeaders.Add("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*");
  220. if (state.RunTimeTicks.HasValue)
  221. {
  222. if (string.Equals(request.Headers["getMediaInfo.sec"], "1", StringComparison.OrdinalIgnoreCase))
  223. {
  224. var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds;
  225. responseHeaders.Add("MediaInfo.sec", string.Format(
  226. CultureInfo.InvariantCulture,
  227. "SEC_Duration={0};",
  228. Convert.ToInt32(ms)));
  229. }
  230. if (!isStaticallyStreamed && profile != null)
  231. {
  232. AddTimeSeekResponseHeaders(state, responseHeaders, startTimeTicks);
  233. }
  234. }
  235. if (profile == null)
  236. {
  237. profile = dlnaManager.GetDefaultProfile();
  238. }
  239. var audioCodec = state.ActualOutputAudioCodec;
  240. if (!state.IsVideoRequest)
  241. {
  242. responseHeaders.Add("contentFeatures.dlna.org", new ContentFeatureBuilder(profile).BuildAudioHeader(
  243. state.OutputContainer,
  244. audioCodec,
  245. state.OutputAudioBitrate,
  246. state.OutputAudioSampleRate,
  247. state.OutputAudioChannels,
  248. state.OutputAudioBitDepth,
  249. isStaticallyStreamed,
  250. state.RunTimeTicks,
  251. state.TranscodeSeekInfo));
  252. }
  253. else
  254. {
  255. var videoCodec = state.ActualOutputVideoCodec;
  256. responseHeaders.Add(
  257. "contentFeatures.dlna.org",
  258. new ContentFeatureBuilder(profile).BuildVideoHeader(state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
  259. }
  260. }
  261. /// <summary>
  262. /// Parses the time seek header.
  263. /// </summary>
  264. /// <param name="value">The time seek header string.</param>
  265. /// <returns>A nullable <see cref="long"/> representing the seek time in ticks.</returns>
  266. private static long? ParseTimeSeekHeader(ReadOnlySpan<char> value)
  267. {
  268. if (value.IsEmpty)
  269. {
  270. return null;
  271. }
  272. const string npt = "npt=";
  273. if (!value.StartsWith(npt, StringComparison.OrdinalIgnoreCase))
  274. {
  275. throw new ArgumentException("Invalid timeseek header");
  276. }
  277. var index = value.IndexOf('-');
  278. value = index == -1
  279. ? value.Slice(npt.Length)
  280. : value.Slice(npt.Length, index - npt.Length);
  281. if (value.IndexOf(':') == -1)
  282. {
  283. // Parses npt times in the format of '417.33'
  284. if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds))
  285. {
  286. return TimeSpan.FromSeconds(seconds).Ticks;
  287. }
  288. throw new ArgumentException("Invalid timeseek header");
  289. }
  290. try
  291. {
  292. // Parses npt times in the format of '10:19:25.7'
  293. return TimeSpan.Parse(value).Ticks;
  294. }
  295. catch
  296. {
  297. throw new ArgumentException("Invalid timeseek header");
  298. }
  299. }
  300. /// <summary>
  301. /// Parses query parameters as StreamOptions.
  302. /// </summary>
  303. /// <param name="queryString">The query string.</param>
  304. /// <returns>A <see cref="Dictionary{String,String}"/> containing the stream options.</returns>
  305. private static Dictionary<string, string> ParseStreamOptions(IQueryCollection queryString)
  306. {
  307. Dictionary<string, string> streamOptions = new Dictionary<string, string>();
  308. foreach (var param in queryString)
  309. {
  310. if (char.IsLower(param.Key[0]))
  311. {
  312. // This was probably not parsed initially and should be a StreamOptions
  313. // or the generated URL should correctly serialize it
  314. // TODO: This should be incorporated either in the lower framework for parsing requests
  315. streamOptions[param.Key] = param.Value;
  316. }
  317. }
  318. return streamOptions;
  319. }
  320. /// <summary>
  321. /// Adds the dlna time seek headers to the response.
  322. /// </summary>
  323. /// <param name="state">The current <see cref="StreamState"/>.</param>
  324. /// <param name="responseHeaders">The <see cref="IHeaderDictionary"/> of the response.</param>
  325. /// <param name="startTimeTicks">The start time in ticks.</param>
  326. private static void AddTimeSeekResponseHeaders(StreamState state, IHeaderDictionary responseHeaders, long? startTimeTicks)
  327. {
  328. var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks!.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture);
  329. var startSeconds = TimeSpan.FromTicks(startTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture);
  330. responseHeaders.Add("TimeSeekRange.dlna.org", string.Format(
  331. CultureInfo.InvariantCulture,
  332. "npt={0}-{1}/{1}",
  333. startSeconds,
  334. runtimeSeconds));
  335. responseHeaders.Add("X-AvailableSeekRange", string.Format(
  336. CultureInfo.InvariantCulture,
  337. "1 npt={0}-{1}",
  338. startSeconds,
  339. runtimeSeconds));
  340. }
  341. /// <summary>
  342. /// Gets the output file extension.
  343. /// </summary>
  344. /// <param name="state">The state.</param>
  345. /// <returns>System.String.</returns>
  346. private static string? GetOutputFileExtension(StreamState state)
  347. {
  348. var ext = Path.GetExtension(state.RequestedUrl);
  349. if (!string.IsNullOrEmpty(ext))
  350. {
  351. return ext;
  352. }
  353. // Try to infer based on the desired video codec
  354. if (state.IsVideoRequest)
  355. {
  356. var videoCodec = state.Request.VideoCodec;
  357. if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) ||
  358. string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase))
  359. {
  360. return ".ts";
  361. }
  362. if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
  363. {
  364. return ".ogv";
  365. }
  366. if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
  367. {
  368. return ".webm";
  369. }
  370. if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
  371. {
  372. return ".asf";
  373. }
  374. }
  375. // Try to infer based on the desired audio codec
  376. if (!state.IsVideoRequest)
  377. {
  378. var audioCodec = state.Request.AudioCodec;
  379. if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
  380. {
  381. return ".aac";
  382. }
  383. if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase))
  384. {
  385. return ".mp3";
  386. }
  387. if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase))
  388. {
  389. return ".ogg";
  390. }
  391. if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase))
  392. {
  393. return ".wma";
  394. }
  395. }
  396. return null;
  397. }
  398. /// <summary>
  399. /// Gets the output file path for transcoding.
  400. /// </summary>
  401. /// <param name="state">The current <see cref="StreamState"/>.</param>
  402. /// <param name="outputFileExtension">The file extension of the output file.</param>
  403. /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
  404. /// <param name="deviceId">The device id.</param>
  405. /// <param name="playSessionId">The play session id.</param>
  406. /// <returns>The complete file path, including the folder, for the transcoding file.</returns>
  407. private static string GetOutputFilePath(StreamState state, string outputFileExtension, IServerConfigurationManager serverConfigurationManager, string? deviceId, string? playSessionId)
  408. {
  409. var data = $"{state.MediaPath}-{state.UserAgent}-{deviceId!}-{playSessionId!}";
  410. var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
  411. var ext = outputFileExtension?.ToLowerInvariant();
  412. var folder = serverConfigurationManager.GetTranscodePath();
  413. return Path.Combine(folder, filename + ext);
  414. }
  415. private static void ApplyDeviceProfileSettings(StreamState state, IDlnaManager dlnaManager, IDeviceManager deviceManager, HttpRequest request, string? deviceProfileId, bool? @static)
  416. {
  417. var headers = request.Headers;
  418. if (!string.IsNullOrWhiteSpace(deviceProfileId))
  419. {
  420. state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId);
  421. }
  422. else if (!string.IsNullOrWhiteSpace(deviceProfileId))
  423. {
  424. var caps = deviceManager.GetCapabilities(deviceProfileId);
  425. state.DeviceProfile = caps == null ? dlnaManager.GetProfile(headers) : caps.DeviceProfile;
  426. }
  427. var profile = state.DeviceProfile;
  428. if (profile == null)
  429. {
  430. // Don't use settings from the default profile.
  431. // Only use a specific profile if it was requested.
  432. return;
  433. }
  434. var audioCodec = state.ActualOutputAudioCodec;
  435. var videoCodec = state.ActualOutputVideoCodec;
  436. var mediaProfile = !state.IsVideoRequest
  437. ? profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate, state.OutputAudioSampleRate, state.OutputAudioBitDepth)
  438. : profile.GetVideoMediaProfile(
  439. state.OutputContainer,
  440. audioCodec,
  441. videoCodec,
  442. state.OutputWidth,
  443. state.OutputHeight,
  444. state.TargetVideoBitDepth,
  445. state.OutputVideoBitrate,
  446. state.TargetVideoProfile,
  447. state.TargetVideoLevel,
  448. state.TargetFramerate,
  449. state.TargetPacketLength,
  450. state.TargetTimestamp,
  451. state.IsTargetAnamorphic,
  452. state.IsTargetInterlaced,
  453. state.TargetRefFrames,
  454. state.TargetVideoStreamCount,
  455. state.TargetAudioStreamCount,
  456. state.TargetVideoCodecTag,
  457. state.IsTargetAVC);
  458. if (mediaProfile != null)
  459. {
  460. state.MimeType = mediaProfile.MimeType;
  461. }
  462. if (!(@static.HasValue && @static.Value))
  463. {
  464. var transcodingProfile = !state.IsVideoRequest ? profile.GetAudioTranscodingProfile(state.OutputContainer, audioCodec) : profile.GetVideoTranscodingProfile(state.OutputContainer, audioCodec, videoCodec);
  465. if (transcodingProfile != null)
  466. {
  467. state.EstimateContentLength = transcodingProfile.EstimateContentLength;
  468. // state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
  469. state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
  470. if (state.VideoRequest != null)
  471. {
  472. state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
  473. state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
  474. }
  475. }
  476. }
  477. }
  478. /// <summary>
  479. /// Parses the parameters.
  480. /// </summary>
  481. /// <param name="request">The request.</param>
  482. private static void ParseParams(StreamingRequestDto request)
  483. {
  484. if (string.IsNullOrEmpty(request.Params))
  485. {
  486. return;
  487. }
  488. var vals = request.Params.Split(';');
  489. var videoRequest = request as VideoRequestDto;
  490. for (var i = 0; i < vals.Length; i++)
  491. {
  492. var val = vals[i];
  493. if (string.IsNullOrWhiteSpace(val))
  494. {
  495. continue;
  496. }
  497. switch (i)
  498. {
  499. case 0:
  500. request.DeviceProfileId = val;
  501. break;
  502. case 1:
  503. request.DeviceId = val;
  504. break;
  505. case 2:
  506. request.MediaSourceId = val;
  507. break;
  508. case 3:
  509. request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  510. break;
  511. case 4:
  512. if (videoRequest != null)
  513. {
  514. videoRequest.VideoCodec = val;
  515. }
  516. break;
  517. case 5:
  518. request.AudioCodec = val;
  519. break;
  520. case 6:
  521. if (videoRequest != null)
  522. {
  523. videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
  524. }
  525. break;
  526. case 7:
  527. if (videoRequest != null)
  528. {
  529. videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
  530. }
  531. break;
  532. case 8:
  533. if (videoRequest != null)
  534. {
  535. videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
  536. }
  537. break;
  538. case 9:
  539. request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
  540. break;
  541. case 10:
  542. request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
  543. break;
  544. case 11:
  545. if (videoRequest != null)
  546. {
  547. videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
  548. }
  549. break;
  550. case 12:
  551. if (videoRequest != null)
  552. {
  553. videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
  554. }
  555. break;
  556. case 13:
  557. if (videoRequest != null)
  558. {
  559. videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
  560. }
  561. break;
  562. case 14:
  563. request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
  564. break;
  565. case 15:
  566. if (videoRequest != null)
  567. {
  568. videoRequest.Level = val;
  569. }
  570. break;
  571. case 16:
  572. if (videoRequest != null)
  573. {
  574. videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
  575. }
  576. break;
  577. case 17:
  578. if (videoRequest != null)
  579. {
  580. videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
  581. }
  582. break;
  583. case 18:
  584. if (videoRequest != null)
  585. {
  586. videoRequest.Profile = val;
  587. }
  588. break;
  589. case 19:
  590. // cabac no longer used
  591. break;
  592. case 20:
  593. request.PlaySessionId = val;
  594. break;
  595. case 21:
  596. // api_key
  597. break;
  598. case 22:
  599. request.LiveStreamId = val;
  600. break;
  601. case 23:
  602. // Duplicating ItemId because of MediaMonkey
  603. break;
  604. case 24:
  605. if (videoRequest != null)
  606. {
  607. videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  608. }
  609. break;
  610. case 25:
  611. if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
  612. {
  613. if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
  614. {
  615. videoRequest.SubtitleMethod = method;
  616. }
  617. }
  618. break;
  619. case 26:
  620. request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
  621. break;
  622. case 27:
  623. if (videoRequest != null)
  624. {
  625. videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  626. }
  627. break;
  628. case 28:
  629. request.Tag = val;
  630. break;
  631. case 29:
  632. if (videoRequest != null)
  633. {
  634. videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  635. }
  636. break;
  637. case 30:
  638. request.SubtitleCodec = val;
  639. break;
  640. case 31:
  641. if (videoRequest != null)
  642. {
  643. videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  644. }
  645. break;
  646. case 32:
  647. if (videoRequest != null)
  648. {
  649. videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  650. }
  651. break;
  652. case 33:
  653. request.TranscodeReasons = val;
  654. break;
  655. }
  656. }
  657. }
  658. }
  659. }