MediaInfoService.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. #pragma warning disable CS1591
  2. #pragma warning disable SA1402
  3. #pragma warning disable SA1649
  4. using System;
  5. using System.Buffers;
  6. using System.Globalization;
  7. using System.Linq;
  8. using System.Text.Json;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using Jellyfin.Data.Enums;
  12. using MediaBrowser.Common.Net;
  13. using MediaBrowser.Controller.Configuration;
  14. using MediaBrowser.Controller.Devices;
  15. using MediaBrowser.Controller.Entities;
  16. using MediaBrowser.Controller.Entities.Audio;
  17. using MediaBrowser.Controller.Library;
  18. using MediaBrowser.Controller.MediaEncoding;
  19. using MediaBrowser.Controller.Net;
  20. using MediaBrowser.Model.Dlna;
  21. using MediaBrowser.Model.Dto;
  22. using MediaBrowser.Model.Entities;
  23. using MediaBrowser.Model.MediaInfo;
  24. using MediaBrowser.Model.Services;
  25. using MediaBrowser.Model.Session;
  26. using Microsoft.Extensions.Logging;
  27. namespace MediaBrowser.Api.Playback
  28. {
  29. public class GetPlaybackInfo : IReturn<PlaybackInfoResponse>
  30. {
  31. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  32. public Guid Id { get; set; }
  33. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
  34. public Guid UserId { get; set; }
  35. }
  36. public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
  37. {
  38. }
  39. public class OpenMediaSource : LiveStreamRequest, IReturn<LiveStreamResponse>
  40. {
  41. }
  42. public class CloseMediaSource : IReturnVoid
  43. {
  44. [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
  45. public string LiveStreamId { get; set; }
  46. }
  47. public class GetBitrateTestBytes
  48. {
  49. [ApiMember(Name = "Size", Description = "Size", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")]
  50. public int Size { get; set; }
  51. public GetBitrateTestBytes()
  52. {
  53. // 100k
  54. Size = 102400;
  55. }
  56. }
  57. [Authenticated]
  58. public class MediaInfoService : BaseApiService
  59. {
  60. private readonly IMediaSourceManager _mediaSourceManager;
  61. private readonly IDeviceManager _deviceManager;
  62. private readonly ILibraryManager _libraryManager;
  63. private readonly INetworkManager _networkManager;
  64. private readonly IMediaEncoder _mediaEncoder;
  65. private readonly IUserManager _userManager;
  66. private readonly IAuthorizationContext _authContext;
  67. public MediaInfoService(
  68. ILogger<MediaInfoService> logger,
  69. IServerConfigurationManager serverConfigurationManager,
  70. IHttpResultFactory httpResultFactory,
  71. IMediaSourceManager mediaSourceManager,
  72. IDeviceManager deviceManager,
  73. ILibraryManager libraryManager,
  74. INetworkManager networkManager,
  75. IMediaEncoder mediaEncoder,
  76. IUserManager userManager,
  77. IAuthorizationContext authContext)
  78. : base(logger, serverConfigurationManager, httpResultFactory)
  79. {
  80. _mediaSourceManager = mediaSourceManager;
  81. _deviceManager = deviceManager;
  82. _libraryManager = libraryManager;
  83. _networkManager = networkManager;
  84. _mediaEncoder = mediaEncoder;
  85. _userManager = userManager;
  86. _authContext = authContext;
  87. }
  88. public object Get(GetBitrateTestBytes request)
  89. {
  90. const int MaxSize = 10_000_000;
  91. var size = request.Size;
  92. if (size <= 0)
  93. {
  94. throw new ArgumentException($"The requested size ({size}) is equal to or smaller than 0.", nameof(request));
  95. }
  96. if (size > MaxSize)
  97. {
  98. throw new ArgumentException($"The requested size ({size}) is larger than the max allowed value ({MaxSize}).", nameof(request));
  99. }
  100. byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
  101. try
  102. {
  103. new Random().NextBytes(buffer);
  104. return ResultFactory.GetResult(null, buffer, "application/octet-stream");
  105. }
  106. finally
  107. {
  108. ArrayPool<byte>.Shared.Return(buffer);
  109. }
  110. }
  111. public async Task<object> Get(GetPlaybackInfo request)
  112. {
  113. var result = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }).ConfigureAwait(false);
  114. return ToOptimizedResult(result);
  115. }
  116. public async Task<object> Post(OpenMediaSource request)
  117. {
  118. var result = await OpenMediaSource(request).ConfigureAwait(false);
  119. return ToOptimizedResult(result);
  120. }
  121. private async Task<LiveStreamResponse> OpenMediaSource(OpenMediaSource request)
  122. {
  123. var authInfo = _authContext.GetAuthorizationInfo(Request);
  124. var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false);
  125. var profile = request.DeviceProfile;
  126. if (profile == null)
  127. {
  128. var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
  129. if (caps != null)
  130. {
  131. profile = caps.DeviceProfile;
  132. }
  133. }
  134. if (profile != null)
  135. {
  136. var item = _libraryManager.GetItemById(request.ItemId);
  137. SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
  138. request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
  139. request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, request.EnableDirectPlay, true, request.EnableDirectStream, true, true, true);
  140. }
  141. else
  142. {
  143. if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
  144. {
  145. result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
  146. }
  147. }
  148. if (result.MediaSource != null)
  149. {
  150. NormalizeMediaSourceContainer(result.MediaSource, profile, DlnaProfileType.Video);
  151. }
  152. return result;
  153. }
  154. public void Post(CloseMediaSource request)
  155. {
  156. _mediaSourceManager.CloseLiveStream(request.LiveStreamId).GetAwaiter().GetResult();
  157. }
  158. public async Task<PlaybackInfoResponse> GetPlaybackInfo(GetPostedPlaybackInfo request)
  159. {
  160. var authInfo = _authContext.GetAuthorizationInfo(Request);
  161. var profile = request.DeviceProfile;
  162. Logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile);
  163. if (profile == null)
  164. {
  165. var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
  166. if (caps != null)
  167. {
  168. profile = caps.DeviceProfile;
  169. }
  170. }
  171. var info = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }, request.MediaSourceId, request.LiveStreamId).ConfigureAwait(false);
  172. if (profile != null)
  173. {
  174. var mediaSourceId = request.MediaSourceId;
  175. SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, true, request.EnableDirectStream, request.EnableTranscoding, request.AllowVideoStreamCopy, request.AllowAudioStreamCopy);
  176. }
  177. if (request.AutoOpenLiveStream)
  178. {
  179. var mediaSource = string.IsNullOrWhiteSpace(request.MediaSourceId) ? info.MediaSources.FirstOrDefault() : info.MediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId, StringComparison.Ordinal));
  180. if (mediaSource != null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
  181. {
  182. var openStreamResult = await OpenMediaSource(new OpenMediaSource
  183. {
  184. AudioStreamIndex = request.AudioStreamIndex,
  185. DeviceProfile = request.DeviceProfile,
  186. EnableDirectPlay = request.EnableDirectPlay,
  187. EnableDirectStream = request.EnableDirectStream,
  188. ItemId = request.Id,
  189. MaxAudioChannels = request.MaxAudioChannels,
  190. MaxStreamingBitrate = request.MaxStreamingBitrate,
  191. PlaySessionId = info.PlaySessionId,
  192. StartTimeTicks = request.StartTimeTicks,
  193. SubtitleStreamIndex = request.SubtitleStreamIndex,
  194. UserId = request.UserId,
  195. OpenToken = mediaSource.OpenToken
  196. }).ConfigureAwait(false);
  197. info.MediaSources = new[] { openStreamResult.MediaSource };
  198. }
  199. }
  200. if (info.MediaSources != null)
  201. {
  202. foreach (var mediaSource in info.MediaSources)
  203. {
  204. NormalizeMediaSourceContainer(mediaSource, profile, DlnaProfileType.Video);
  205. }
  206. }
  207. return info;
  208. }
  209. private void NormalizeMediaSourceContainer(MediaSourceInfo mediaSource, DeviceProfile profile, DlnaProfileType type)
  210. {
  211. mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, mediaSource.Path, profile, type);
  212. }
  213. public async Task<object> Post(GetPostedPlaybackInfo request)
  214. {
  215. var result = await GetPlaybackInfo(request).ConfigureAwait(false);
  216. return ToOptimizedResult(result);
  217. }
  218. private async Task<PlaybackInfoResponse> GetPlaybackInfo(Guid id, Guid userId, string[] supportedLiveMediaTypes, string mediaSourceId = null, string liveStreamId = null)
  219. {
  220. var user = _userManager.GetUserById(userId);
  221. var item = _libraryManager.GetItemById(id);
  222. var result = new PlaybackInfoResponse();
  223. MediaSourceInfo[] mediaSources;
  224. if (string.IsNullOrWhiteSpace(liveStreamId))
  225. {
  226. // TODO handle supportedLiveMediaTypes?
  227. var mediaSourcesList = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, true, CancellationToken.None).ConfigureAwait(false);
  228. if (string.IsNullOrWhiteSpace(mediaSourceId))
  229. {
  230. mediaSources = mediaSourcesList.ToArray();
  231. }
  232. else
  233. {
  234. mediaSources = mediaSourcesList
  235. .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
  236. .ToArray();
  237. }
  238. }
  239. else
  240. {
  241. var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
  242. mediaSources = new[] { mediaSource };
  243. }
  244. if (mediaSources.Length == 0)
  245. {
  246. result.MediaSources = Array.Empty<MediaSourceInfo>();
  247. if (!result.ErrorCode.HasValue)
  248. {
  249. result.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
  250. }
  251. }
  252. else
  253. {
  254. // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it
  255. // Should we move this directly into MediaSourceManager?
  256. result.MediaSources = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
  257. result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
  258. }
  259. return result;
  260. }
  261. private void SetDeviceSpecificData(
  262. Guid itemId,
  263. PlaybackInfoResponse result,
  264. DeviceProfile profile,
  265. AuthorizationInfo auth,
  266. long? maxBitrate,
  267. long startTimeTicks,
  268. string mediaSourceId,
  269. int? audioStreamIndex,
  270. int? subtitleStreamIndex,
  271. int? maxAudioChannels,
  272. Guid userId,
  273. bool enableDirectPlay,
  274. bool forceDirectPlayRemoteMediaSource,
  275. bool enableDirectStream,
  276. bool enableTranscoding,
  277. bool allowVideoStreamCopy,
  278. bool allowAudioStreamCopy)
  279. {
  280. var item = _libraryManager.GetItemById(itemId);
  281. foreach (var mediaSource in result.MediaSources)
  282. {
  283. SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId, enableDirectPlay, forceDirectPlayRemoteMediaSource, enableDirectStream, enableTranscoding, allowVideoStreamCopy, allowAudioStreamCopy);
  284. }
  285. SortMediaSources(result, maxBitrate);
  286. }
  287. private void SetDeviceSpecificData(
  288. BaseItem item,
  289. MediaSourceInfo mediaSource,
  290. DeviceProfile profile,
  291. AuthorizationInfo auth,
  292. long? maxBitrate,
  293. long startTimeTicks,
  294. string mediaSourceId,
  295. int? audioStreamIndex,
  296. int? subtitleStreamIndex,
  297. int? maxAudioChannels,
  298. string playSessionId,
  299. Guid userId,
  300. bool enableDirectPlay,
  301. bool forceDirectPlayRemoteMediaSource,
  302. bool enableDirectStream,
  303. bool enableTranscoding,
  304. bool allowVideoStreamCopy,
  305. bool allowAudioStreamCopy)
  306. {
  307. var streamBuilder = new StreamBuilder(_mediaEncoder, Logger);
  308. var options = new VideoOptions
  309. {
  310. MediaSources = new[] { mediaSource },
  311. Context = EncodingContext.Streaming,
  312. DeviceId = auth.DeviceId,
  313. ItemId = item.Id,
  314. Profile = profile,
  315. MaxAudioChannels = maxAudioChannels
  316. };
  317. if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
  318. {
  319. options.MediaSourceId = mediaSourceId;
  320. options.AudioStreamIndex = audioStreamIndex;
  321. options.SubtitleStreamIndex = subtitleStreamIndex;
  322. }
  323. var user = _userManager.GetUserById(userId);
  324. if (!enableDirectPlay)
  325. {
  326. mediaSource.SupportsDirectPlay = false;
  327. }
  328. if (!enableDirectStream)
  329. {
  330. mediaSource.SupportsDirectStream = false;
  331. }
  332. if (!enableTranscoding)
  333. {
  334. mediaSource.SupportsTranscoding = false;
  335. }
  336. if (item is Audio)
  337. {
  338. Logger.LogInformation(
  339. "User policy for {0}. EnableAudioPlaybackTranscoding: {1}",
  340. user.Username,
  341. user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
  342. }
  343. else
  344. {
  345. Logger.LogInformation("User policy for {0}. EnablePlaybackRemuxing: {1} EnableVideoPlaybackTranscoding: {2} EnableAudioPlaybackTranscoding: {3}",
  346. user.Username,
  347. user.HasPermission(PermissionKind.EnablePlaybackRemuxing),
  348. user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding),
  349. user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
  350. }
  351. // Beginning of Playback Determination: Attempt DirectPlay first
  352. if (mediaSource.SupportsDirectPlay)
  353. {
  354. if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
  355. {
  356. mediaSource.SupportsDirectPlay = false;
  357. }
  358. else
  359. {
  360. var supportsDirectStream = mediaSource.SupportsDirectStream;
  361. // Dummy this up to fool StreamBuilder
  362. mediaSource.SupportsDirectStream = true;
  363. options.MaxBitrate = maxBitrate;
  364. if (item is Audio)
  365. {
  366. if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
  367. {
  368. options.ForceDirectPlay = true;
  369. }
  370. }
  371. else if (item is Video)
  372. {
  373. if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
  374. && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
  375. && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
  376. {
  377. options.ForceDirectPlay = true;
  378. }
  379. }
  380. // The MediaSource supports direct stream, now test to see if the client supports it
  381. var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
  382. ? streamBuilder.BuildAudioItem(options)
  383. : streamBuilder.BuildVideoItem(options);
  384. if (streamInfo == null || !streamInfo.IsDirectStream)
  385. {
  386. mediaSource.SupportsDirectPlay = false;
  387. }
  388. // Set this back to what it was
  389. mediaSource.SupportsDirectStream = supportsDirectStream;
  390. if (streamInfo != null)
  391. {
  392. SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
  393. }
  394. }
  395. }
  396. if (mediaSource.SupportsDirectStream)
  397. {
  398. if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
  399. {
  400. mediaSource.SupportsDirectStream = false;
  401. }
  402. else
  403. {
  404. options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
  405. if (item is Audio)
  406. {
  407. if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
  408. {
  409. options.ForceDirectStream = true;
  410. }
  411. }
  412. else if (item is Video)
  413. {
  414. if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
  415. && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
  416. && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
  417. {
  418. options.ForceDirectStream = true;
  419. }
  420. }
  421. // The MediaSource supports direct stream, now test to see if the client supports it
  422. var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
  423. ? streamBuilder.BuildAudioItem(options)
  424. : streamBuilder.BuildVideoItem(options);
  425. if (streamInfo == null || !streamInfo.IsDirectStream)
  426. {
  427. mediaSource.SupportsDirectStream = false;
  428. }
  429. if (streamInfo != null)
  430. {
  431. SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
  432. }
  433. }
  434. }
  435. if (mediaSource.SupportsTranscoding)
  436. {
  437. options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
  438. // The MediaSource supports direct stream, now test to see if the client supports it
  439. var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
  440. ? streamBuilder.BuildAudioItem(options)
  441. : streamBuilder.BuildVideoItem(options);
  442. if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
  443. {
  444. if (streamInfo != null)
  445. {
  446. streamInfo.PlaySessionId = playSessionId;
  447. streamInfo.StartPositionTicks = startTimeTicks;
  448. mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
  449. mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
  450. mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
  451. mediaSource.TranscodingContainer = streamInfo.Container;
  452. mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
  453. // Do this after the above so that StartPositionTicks is set
  454. SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
  455. }
  456. }
  457. else
  458. {
  459. if (streamInfo != null)
  460. {
  461. streamInfo.PlaySessionId = playSessionId;
  462. if (streamInfo.PlayMethod == PlayMethod.Transcode)
  463. {
  464. streamInfo.StartPositionTicks = startTimeTicks;
  465. mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
  466. if (!allowVideoStreamCopy)
  467. {
  468. mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
  469. }
  470. if (!allowAudioStreamCopy)
  471. {
  472. mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
  473. }
  474. mediaSource.TranscodingContainer = streamInfo.Container;
  475. mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
  476. }
  477. if (!allowAudioStreamCopy)
  478. {
  479. mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
  480. }
  481. mediaSource.TranscodingContainer = streamInfo.Container;
  482. mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
  483. // Do this after the above so that StartPositionTicks is set
  484. SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
  485. }
  486. }
  487. }
  488. foreach (var attachment in mediaSource.MediaAttachments)
  489. {
  490. attachment.DeliveryUrl = string.Format(
  491. CultureInfo.InvariantCulture,
  492. "/Videos/{0}/{1}/Attachments/{2}",
  493. item.Id,
  494. mediaSource.Id,
  495. attachment.Index);
  496. }
  497. }
  498. private long? GetMaxBitrate(long? clientMaxBitrate, Jellyfin.Data.Entities.User user)
  499. {
  500. var maxBitrate = clientMaxBitrate;
  501. var remoteClientMaxBitrate = user?.RemoteClientBitrateLimit ?? 0;
  502. if (remoteClientMaxBitrate <= 0)
  503. {
  504. remoteClientMaxBitrate = ServerConfigurationManager.Configuration.RemoteClientBitrateLimit;
  505. }
  506. if (remoteClientMaxBitrate > 0)
  507. {
  508. var isInLocalNetwork = _networkManager.IsInLocalNetwork(Request.RemoteIp);
  509. Logger.LogInformation("RemoteClientBitrateLimit: {0}, RemoteIp: {1}, IsInLocalNetwork: {2}", remoteClientMaxBitrate, Request.RemoteIp, isInLocalNetwork);
  510. if (!isInLocalNetwork)
  511. {
  512. maxBitrate = Math.Min(maxBitrate ?? remoteClientMaxBitrate, remoteClientMaxBitrate);
  513. }
  514. }
  515. return maxBitrate;
  516. }
  517. private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken)
  518. {
  519. var profiles = info.GetSubtitleProfiles(_mediaEncoder, false, "-", accessToken);
  520. mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex;
  521. mediaSource.TranscodeReasons = info.TranscodeReasons;
  522. foreach (var profile in profiles)
  523. {
  524. foreach (var stream in mediaSource.MediaStreams)
  525. {
  526. if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index)
  527. {
  528. stream.DeliveryMethod = profile.DeliveryMethod;
  529. if (profile.DeliveryMethod == SubtitleDeliveryMethod.External)
  530. {
  531. stream.DeliveryUrl = profile.Url.TrimStart('-');
  532. stream.IsExternalUrl = profile.IsExternalUrl;
  533. }
  534. }
  535. }
  536. }
  537. }
  538. private void SortMediaSources(PlaybackInfoResponse result, long? maxBitrate)
  539. {
  540. var originalList = result.MediaSources.ToList();
  541. result.MediaSources = result.MediaSources.OrderBy(i =>
  542. {
  543. // Nothing beats direct playing a file
  544. if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File)
  545. {
  546. return 0;
  547. }
  548. return 1;
  549. }).ThenBy(i =>
  550. {
  551. // Let's assume direct streaming a file is just as desirable as direct playing a remote url
  552. if (i.SupportsDirectPlay || i.SupportsDirectStream)
  553. {
  554. return 0;
  555. }
  556. return 1;
  557. }).ThenBy(i =>
  558. {
  559. return i.Protocol switch
  560. {
  561. MediaProtocol.File => 0,
  562. _ => 1,
  563. };
  564. }).ThenBy(i =>
  565. {
  566. if (maxBitrate.HasValue && i.Bitrate.HasValue)
  567. {
  568. return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2;
  569. }
  570. return 1;
  571. }).ThenBy(originalList.IndexOf)
  572. .ToArray();
  573. }
  574. }
  575. }