MediaInfoService.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. using MediaBrowser.Common.Net;
  2. using MediaBrowser.Controller.Configuration;
  3. using MediaBrowser.Controller.Devices;
  4. using MediaBrowser.Controller.Entities;
  5. using MediaBrowser.Controller.Library;
  6. using MediaBrowser.Controller.Net;
  7. using MediaBrowser.Model.Dlna;
  8. using MediaBrowser.Model.Dto;
  9. using MediaBrowser.Model.Entities;
  10. using MediaBrowser.Model.MediaInfo;
  11. using MediaBrowser.Model.Session;
  12. using ServiceStack;
  13. using System;
  14. using System.Collections.Generic;
  15. using System.Linq;
  16. using System.Threading;
  17. using System.Threading.Tasks;
  18. namespace MediaBrowser.Api.Playback
  19. {
  20. [Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")]
  21. public class GetLiveMediaInfo : IReturn<PlaybackInfoResponse>
  22. {
  23. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  24. public string Id { get; set; }
  25. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
  26. public string UserId { get; set; }
  27. }
  28. [Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")]
  29. public class GetPlaybackInfo : IReturn<PlaybackInfoResponse>
  30. {
  31. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  32. public string Id { get; set; }
  33. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
  34. public string UserId { get; set; }
  35. }
  36. [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")]
  37. public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
  38. {
  39. }
  40. [Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")]
  41. public class OpenMediaSource : LiveStreamRequest, IReturn<LiveStreamResponse>
  42. {
  43. }
  44. [Route("/LiveStreams/Close", "POST", Summary = "Closes a media source")]
  45. public class CloseMediaSource : IReturnVoid
  46. {
  47. [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
  48. public string LiveStreamId { get; set; }
  49. }
  50. [Authenticated]
  51. public class MediaInfoService : BaseApiService
  52. {
  53. private readonly IMediaSourceManager _mediaSourceManager;
  54. private readonly IDeviceManager _deviceManager;
  55. private readonly ILibraryManager _libraryManager;
  56. private readonly IServerConfigurationManager _config;
  57. private readonly INetworkManager _networkManager;
  58. public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager)
  59. {
  60. _mediaSourceManager = mediaSourceManager;
  61. _deviceManager = deviceManager;
  62. _libraryManager = libraryManager;
  63. _config = config;
  64. _networkManager = networkManager;
  65. }
  66. public async Task<object> Get(GetPlaybackInfo request)
  67. {
  68. var result = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }).ConfigureAwait(false);
  69. return ToOptimizedResult(result);
  70. }
  71. public async Task<object> Get(GetLiveMediaInfo request)
  72. {
  73. var result = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }).ConfigureAwait(false);
  74. return ToOptimizedResult(result);
  75. }
  76. public async Task<object> Post(OpenMediaSource request)
  77. {
  78. var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
  79. var result = await _mediaSourceManager.OpenLiveStream(request, false, CancellationToken.None).ConfigureAwait(false);
  80. var profile = request.DeviceProfile;
  81. if (profile == null)
  82. {
  83. var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
  84. if (caps != null)
  85. {
  86. profile = caps.DeviceProfile;
  87. }
  88. }
  89. if (profile != null)
  90. {
  91. var item = _libraryManager.GetItemById(request.ItemId);
  92. SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
  93. request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
  94. request.SubtitleStreamIndex, request.PlaySessionId);
  95. }
  96. else
  97. {
  98. if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
  99. {
  100. result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
  101. }
  102. }
  103. return ToOptimizedResult(result);
  104. }
  105. public void Post(CloseMediaSource request)
  106. {
  107. var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None);
  108. Task.WaitAll(task);
  109. }
  110. public async Task<object> Post(GetPostedPlaybackInfo request)
  111. {
  112. var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
  113. var profile = request.DeviceProfile;
  114. var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
  115. if (caps != null)
  116. {
  117. if (profile == null)
  118. {
  119. profile = caps.DeviceProfile;
  120. }
  121. }
  122. var info = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }, request.MediaSourceId, request.LiveStreamId).ConfigureAwait(false);
  123. if (profile != null)
  124. {
  125. var mediaSourceId = request.MediaSourceId;
  126. SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
  127. }
  128. return ToOptimizedResult(info);
  129. }
  130. private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, string[] supportedLiveMediaTypes, string mediaSourceId = null, string liveStreamId = null)
  131. {
  132. var result = new PlaybackInfoResponse();
  133. if (string.IsNullOrWhiteSpace(liveStreamId))
  134. {
  135. IEnumerable<MediaSourceInfo> mediaSources;
  136. try
  137. {
  138. mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, supportedLiveMediaTypes, CancellationToken.None).ConfigureAwait(false);
  139. }
  140. catch (PlaybackException ex)
  141. {
  142. mediaSources = new List<MediaSourceInfo>();
  143. result.ErrorCode = ex.ErrorCode;
  144. }
  145. result.MediaSources = mediaSources.ToList();
  146. if (!string.IsNullOrWhiteSpace(mediaSourceId))
  147. {
  148. result.MediaSources = result.MediaSources
  149. .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
  150. .ToList();
  151. }
  152. }
  153. else
  154. {
  155. var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
  156. result.MediaSources = new List<MediaSourceInfo> { mediaSource };
  157. }
  158. if (result.MediaSources.Count == 0)
  159. {
  160. if (!result.ErrorCode.HasValue)
  161. {
  162. result.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
  163. }
  164. }
  165. else
  166. {
  167. result.PlaySessionId = Guid.NewGuid().ToString("N");
  168. }
  169. return result;
  170. }
  171. private void SetDeviceSpecificData(string itemId,
  172. PlaybackInfoResponse result,
  173. DeviceProfile profile,
  174. AuthorizationInfo auth,
  175. int? maxBitrate,
  176. long startTimeTicks,
  177. string mediaSourceId,
  178. int? audioStreamIndex,
  179. int? subtitleStreamIndex)
  180. {
  181. var item = _libraryManager.GetItemById(itemId);
  182. foreach (var mediaSource in result.MediaSources)
  183. {
  184. SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, result.PlaySessionId);
  185. }
  186. SortMediaSources(result);
  187. }
  188. private void SetDeviceSpecificData(BaseItem item,
  189. MediaSourceInfo mediaSource,
  190. DeviceProfile profile,
  191. AuthorizationInfo auth,
  192. int? maxBitrate,
  193. long startTimeTicks,
  194. string mediaSourceId,
  195. int? audioStreamIndex,
  196. int? subtitleStreamIndex,
  197. string playSessionId)
  198. {
  199. var streamBuilder = new StreamBuilder(Logger);
  200. var options = new VideoOptions
  201. {
  202. MediaSources = new List<MediaSourceInfo> { mediaSource },
  203. Context = EncodingContext.Streaming,
  204. DeviceId = auth.DeviceId,
  205. ItemId = item.Id.ToString("N"),
  206. Profile = profile
  207. };
  208. if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
  209. {
  210. options.MediaSourceId = mediaSourceId;
  211. options.AudioStreamIndex = audioStreamIndex;
  212. options.SubtitleStreamIndex = subtitleStreamIndex;
  213. }
  214. if (mediaSource.SupportsDirectPlay)
  215. {
  216. var supportsDirectStream = mediaSource.SupportsDirectStream;
  217. // Dummy this up to fool StreamBuilder
  218. mediaSource.SupportsDirectStream = true;
  219. options.MaxBitrate = maxBitrate;
  220. // The MediaSource supports direct stream, now test to see if the client supports it
  221. var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
  222. streamBuilder.BuildAudioItem(options) :
  223. streamBuilder.BuildVideoItem(options);
  224. if (streamInfo == null || !streamInfo.IsDirectStream)
  225. {
  226. mediaSource.SupportsDirectPlay = false;
  227. }
  228. // Set this back to what it was
  229. mediaSource.SupportsDirectStream = supportsDirectStream;
  230. if (streamInfo != null)
  231. {
  232. SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
  233. }
  234. }
  235. if (mediaSource.SupportsDirectStream)
  236. {
  237. options.MaxBitrate = GetMaxBitrate(maxBitrate);
  238. // The MediaSource supports direct stream, now test to see if the client supports it
  239. var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
  240. streamBuilder.BuildAudioItem(options) :
  241. streamBuilder.BuildVideoItem(options);
  242. if (streamInfo == null || !streamInfo.IsDirectStream)
  243. {
  244. mediaSource.SupportsDirectStream = false;
  245. }
  246. if (streamInfo != null)
  247. {
  248. SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
  249. }
  250. }
  251. if (mediaSource.SupportsTranscoding)
  252. {
  253. options.MaxBitrate = GetMaxBitrate(maxBitrate);
  254. // The MediaSource supports direct stream, now test to see if the client supports it
  255. var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
  256. streamBuilder.BuildAudioItem(options) :
  257. streamBuilder.BuildVideoItem(options);
  258. if (streamInfo != null)
  259. {
  260. streamInfo.PlaySessionId = playSessionId;
  261. SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
  262. }
  263. if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode)
  264. {
  265. streamInfo.StartPositionTicks = startTimeTicks;
  266. mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
  267. mediaSource.TranscodingContainer = streamInfo.Container;
  268. mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
  269. }
  270. }
  271. }
  272. private int? GetMaxBitrate(int? clientMaxBitrate)
  273. {
  274. var maxBitrate = clientMaxBitrate;
  275. if (_config.Configuration.RemoteClientBitrateLimit > 0 && !_networkManager.IsInLocalNetwork(Request.RemoteIp))
  276. {
  277. maxBitrate = Math.Min(maxBitrate ?? _config.Configuration.RemoteClientBitrateLimit, _config.Configuration.RemoteClientBitrateLimit);
  278. }
  279. return maxBitrate;
  280. }
  281. private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken)
  282. {
  283. var profiles = info.GetSubtitleProfiles(false, "-", accessToken);
  284. mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex;
  285. foreach (var profile in profiles)
  286. {
  287. foreach (var stream in mediaSource.MediaStreams)
  288. {
  289. if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index)
  290. {
  291. stream.DeliveryMethod = profile.DeliveryMethod;
  292. if (profile.DeliveryMethod == SubtitleDeliveryMethod.External)
  293. {
  294. stream.DeliveryUrl = profile.Url.TrimStart('-');
  295. stream.IsExternalUrl = profile.IsExternalUrl;
  296. }
  297. }
  298. }
  299. }
  300. }
  301. private void SortMediaSources(PlaybackInfoResponse result)
  302. {
  303. var originalList = result.MediaSources.ToList();
  304. result.MediaSources = result.MediaSources.OrderBy(i =>
  305. {
  306. // Nothing beats direct playing a file
  307. if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File)
  308. {
  309. return 0;
  310. }
  311. return 1;
  312. }).ThenBy(i =>
  313. {
  314. // Let's assume direct streaming a file is just as desirable as direct playing a remote url
  315. if (i.SupportsDirectPlay || i.SupportsDirectStream)
  316. {
  317. return 0;
  318. }
  319. return 1;
  320. }).ThenBy(i =>
  321. {
  322. switch (i.Protocol)
  323. {
  324. case MediaProtocol.File:
  325. return 0;
  326. default:
  327. return 1;
  328. }
  329. }).ThenBy(originalList.IndexOf)
  330. .ToList();
  331. }
  332. }
  333. }