MediaInfoService.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. using MediaBrowser.Controller.Devices;
  2. using MediaBrowser.Controller.Entities;
  3. using MediaBrowser.Controller.Library;
  4. using MediaBrowser.Controller.Net;
  5. using MediaBrowser.Model.Dlna;
  6. using MediaBrowser.Model.Dto;
  7. using MediaBrowser.Model.Entities;
  8. using MediaBrowser.Model.MediaInfo;
  9. using MediaBrowser.Model.Session;
  10. using ServiceStack;
  11. using System;
  12. using System.Collections.Generic;
  13. using System.Linq;
  14. using System.Threading;
  15. using System.Threading.Tasks;
  16. namespace MediaBrowser.Api.Playback
  17. {
  18. [Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")]
  19. public class GetLiveMediaInfo : IReturn<PlaybackInfoResponse>
  20. {
  21. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  22. public string Id { get; set; }
  23. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
  24. public string UserId { get; set; }
  25. }
  26. [Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")]
  27. public class GetPlaybackInfo : IReturn<PlaybackInfoResponse>
  28. {
  29. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  30. public string Id { get; set; }
  31. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
  32. public string UserId { get; set; }
  33. }
  34. [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")]
  35. public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
  36. {
  37. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
  38. public string Id { get; set; }
  39. [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
  40. public string UserId { get; set; }
  41. [ApiMember(Name = "MaxStreamingBitrate", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
  42. public int? MaxStreamingBitrate { get; set; }
  43. [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
  44. public long? StartTimeTicks { get; set; }
  45. [ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
  46. public int? AudioStreamIndex { get; set; }
  47. [ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
  48. public int? SubtitleStreamIndex { get; set; }
  49. [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
  50. public string MediaSourceId { get; set; }
  51. }
  52. [Route("/MediaSources/Open", "POST", Summary = "Opens a media source")]
  53. public class OpenMediaSource : IReturn<MediaSourceInfo>
  54. {
  55. [ApiMember(Name = "OpenToken", Description = "OpenToken", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
  56. public string OpenToken { get; set; }
  57. }
  58. [Route("/MediaSources/Close", "POST", Summary = "Closes a media source")]
  59. public class CloseMediaSource : IReturnVoid
  60. {
  61. [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
  62. public string LiveStreamId { get; set; }
  63. }
  64. [Authenticated]
  65. public class MediaInfoService : BaseApiService
  66. {
  67. private readonly IMediaSourceManager _mediaSourceManager;
  68. private readonly IDeviceManager _deviceManager;
  69. private readonly ILibraryManager _libraryManager;
  70. public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager)
  71. {
  72. _mediaSourceManager = mediaSourceManager;
  73. _deviceManager = deviceManager;
  74. _libraryManager = libraryManager;
  75. }
  76. public async Task<object> Get(GetPlaybackInfo request)
  77. {
  78. var result = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false);
  79. return ToOptimizedResult(result);
  80. }
  81. public async Task<object> Get(GetLiveMediaInfo request)
  82. {
  83. var result = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false);
  84. return ToOptimizedResult(result);
  85. }
  86. public async Task<object> Post(OpenMediaSource request)
  87. {
  88. var result = await _mediaSourceManager.OpenLiveStream(request.OpenToken, false, CancellationToken.None).ConfigureAwait(false);
  89. return ToOptimizedResult(result);
  90. }
  91. public void Post(CloseMediaSource request)
  92. {
  93. var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None);
  94. Task.WaitAll(task);
  95. }
  96. public async Task<object> Post(GetPostedPlaybackInfo request)
  97. {
  98. var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId).ConfigureAwait(false);
  99. var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
  100. var profile = request.DeviceProfile;
  101. if (profile == null)
  102. {
  103. var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
  104. if (caps != null)
  105. {
  106. profile = caps.DeviceProfile;
  107. }
  108. }
  109. if (profile != null)
  110. {
  111. var mediaSourceId = request.MediaSourceId;
  112. SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
  113. }
  114. return ToOptimizedResult(info);
  115. }
  116. private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, string mediaSourceId = null)
  117. {
  118. var result = new PlaybackInfoResponse();
  119. IEnumerable<MediaSourceInfo> mediaSources;
  120. try
  121. {
  122. mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false);
  123. }
  124. catch (PlaybackException ex)
  125. {
  126. mediaSources = new List<MediaSourceInfo>();
  127. result.ErrorCode = ex.ErrorCode;
  128. }
  129. result.MediaSources = mediaSources.ToList();
  130. if (!string.IsNullOrWhiteSpace(mediaSourceId))
  131. {
  132. result.MediaSources = result.MediaSources
  133. .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
  134. .ToList();
  135. }
  136. if (result.MediaSources.Count == 0)
  137. {
  138. if (!result.ErrorCode.HasValue)
  139. {
  140. result.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
  141. }
  142. }
  143. else
  144. {
  145. result.StreamId = Guid.NewGuid().ToString("N");
  146. }
  147. return result;
  148. }
  149. private void SetDeviceSpecificData(string itemId,
  150. PlaybackInfoResponse result,
  151. DeviceProfile profile,
  152. AuthorizationInfo auth,
  153. int? maxBitrate,
  154. long startTimeTicks,
  155. string mediaSourceId,
  156. int? audioStreamIndex,
  157. int? subtitleStreamIndex)
  158. {
  159. var item = _libraryManager.GetItemById(itemId);
  160. foreach (var mediaSource in result.MediaSources)
  161. {
  162. SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
  163. }
  164. SortMediaSources(result);
  165. }
  166. private void SetDeviceSpecificData(BaseItem item,
  167. MediaSourceInfo mediaSource,
  168. DeviceProfile profile,
  169. AuthorizationInfo auth,
  170. int? maxBitrate,
  171. long startTimeTicks,
  172. string mediaSourceId,
  173. int? audioStreamIndex,
  174. int? subtitleStreamIndex)
  175. {
  176. var streamBuilder = new StreamBuilder();
  177. var options = new VideoOptions
  178. {
  179. MediaSources = new List<MediaSourceInfo> { mediaSource },
  180. Context = EncodingContext.Streaming,
  181. DeviceId = auth.DeviceId,
  182. ItemId = item.Id.ToString("N"),
  183. Profile = profile,
  184. MaxBitrate = maxBitrate
  185. };
  186. if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
  187. {
  188. options.MediaSourceId = mediaSourceId;
  189. options.AudioStreamIndex = audioStreamIndex;
  190. options.SubtitleStreamIndex = subtitleStreamIndex;
  191. }
  192. if (mediaSource.SupportsDirectPlay)
  193. {
  194. var supportsDirectStream = mediaSource.SupportsDirectStream;
  195. // Dummy this up to fool StreamBuilder
  196. mediaSource.SupportsDirectStream = true;
  197. // The MediaSource supports direct stream, now test to see if the client supports it
  198. var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
  199. streamBuilder.BuildAudioItem(options) :
  200. streamBuilder.BuildVideoItem(options);
  201. if (streamInfo == null || !streamInfo.IsDirectStream)
  202. {
  203. mediaSource.SupportsDirectPlay = false;
  204. }
  205. // Set this back to what it was
  206. mediaSource.SupportsDirectStream = supportsDirectStream;
  207. }
  208. if (mediaSource.SupportsDirectStream)
  209. {
  210. // The MediaSource supports direct stream, now test to see if the client supports it
  211. var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
  212. streamBuilder.BuildAudioItem(options) :
  213. streamBuilder.BuildVideoItem(options);
  214. if (streamInfo == null || !streamInfo.IsDirectStream)
  215. {
  216. mediaSource.SupportsDirectStream = false;
  217. }
  218. }
  219. if (mediaSource.SupportsTranscoding)
  220. {
  221. // The MediaSource supports direct stream, now test to see if the client supports it
  222. var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
  223. streamBuilder.BuildAudioItem(options) :
  224. streamBuilder.BuildVideoItem(options);
  225. if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode)
  226. {
  227. streamInfo.StartPositionTicks = startTimeTicks;
  228. mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).Substring(1);
  229. mediaSource.TranscodingContainer = streamInfo.Container;
  230. mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
  231. }
  232. }
  233. }
  234. private void SortMediaSources(PlaybackInfoResponse result)
  235. {
  236. var originalList = result.MediaSources.ToList();
  237. result.MediaSources = result.MediaSources.OrderBy(i =>
  238. {
  239. // Nothing beats direct playing a file
  240. if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File)
  241. {
  242. return 0;
  243. }
  244. return 1;
  245. }).ThenBy(i =>
  246. {
  247. // Let's assume direct streaming a file is just as desirable as direct playing a remote url
  248. if (i.SupportsDirectPlay || i.SupportsDirectStream)
  249. {
  250. return 0;
  251. }
  252. return 1;
  253. }).ThenBy(i =>
  254. {
  255. switch (i.Protocol)
  256. {
  257. case MediaProtocol.File:
  258. return 0;
  259. default:
  260. return 1;
  261. }
  262. }).ThenBy(originalList.IndexOf)
  263. .ToList();
  264. }
  265. }
  266. }