LiveTvManager.cs 81 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259
  1. using MediaBrowser.Common;
  2. using MediaBrowser.Common.Configuration;
  3. using MediaBrowser.Common.Extensions;
  4. using MediaBrowser.Common.Progress;
  5. using MediaBrowser.Common.ScheduledTasks;
  6. using MediaBrowser.Controller.Configuration;
  7. using MediaBrowser.Controller.Drawing;
  8. using MediaBrowser.Controller.Dto;
  9. using MediaBrowser.Controller.Entities;
  10. using MediaBrowser.Controller.Library;
  11. using MediaBrowser.Controller.LiveTv;
  12. using MediaBrowser.Controller.Localization;
  13. using MediaBrowser.Controller.Persistence;
  14. using MediaBrowser.Controller.Providers;
  15. using MediaBrowser.Controller.Sorting;
  16. using MediaBrowser.Model.Dto;
  17. using MediaBrowser.Model.Entities;
  18. using MediaBrowser.Model.LiveTv;
  19. using MediaBrowser.Model.Logging;
  20. using MediaBrowser.Model.Querying;
  21. using MediaBrowser.Model.Serialization;
  22. using System;
  23. using System.Collections.Concurrent;
  24. using System.Collections.Generic;
  25. using System.Linq;
  26. using System.Threading;
  27. using System.Threading.Tasks;
  28. namespace MediaBrowser.Server.Implementations.LiveTv
  29. {
  30. /// <summary>
  31. /// Class LiveTvManager
  32. /// </summary>
  33. public class LiveTvManager : ILiveTvManager, IDisposable
  34. {
  35. private readonly IServerConfigurationManager _config;
  36. private readonly ILogger _logger;
  37. private readonly IItemRepository _itemRepo;
  38. private readonly IUserManager _userManager;
  39. private readonly IUserDataManager _userDataManager;
  40. private readonly ILibraryManager _libraryManager;
  41. private readonly ITaskManager _taskManager;
  42. private readonly IJsonSerializer _jsonSerializer;
  43. private readonly IProviderManager _providerManager;
  44. private readonly IDtoService _dtoService;
  45. private readonly ILocalizationManager _localization;
  46. private readonly LiveTvDtoService _tvDtoService;
  47. private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
  48. private readonly ConcurrentDictionary<string, LiveStreamData> _openStreams =
  49. new ConcurrentDictionary<string, LiveStreamData>();
  50. private readonly SemaphoreSlim _refreshRecordingsLock = new SemaphoreSlim(1, 1);
  51. private readonly ConcurrentDictionary<Guid, Guid> _refreshedPrograms = new ConcurrentDictionary<Guid, Guid>();
  52. private readonly List<ITunerHost> _tunerHosts = new List<ITunerHost>();
  53. private readonly List<IListingsProvider> _listingProviders = new List<IListingsProvider>();
  54. public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager)
  55. {
  56. _config = config;
  57. _logger = logger;
  58. _itemRepo = itemRepo;
  59. _userManager = userManager;
  60. _libraryManager = libraryManager;
  61. _taskManager = taskManager;
  62. _localization = localization;
  63. _jsonSerializer = jsonSerializer;
  64. _providerManager = providerManager;
  65. _dtoService = dtoService;
  66. _userDataManager = userDataManager;
  67. _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger, appHost);
  68. }
  69. /// <summary>
  70. /// Gets the services.
  71. /// </summary>
  72. /// <value>The services.</value>
  73. public IReadOnlyList<ILiveTvService> Services
  74. {
  75. get { return _services; }
  76. }
  77. private LiveTvOptions GetConfiguration()
  78. {
  79. return _config.GetConfiguration<LiveTvOptions>("livetv");
  80. }
  81. /// <summary>
  82. /// Adds the parts.
  83. /// </summary>
  84. /// <param name="services">The services.</param>
  85. /// <param name="tunerHosts">The tuner hosts.</param>
  86. /// <param name="listingProviders">The listing providers.</param>
  87. public void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders)
  88. {
  89. _services.AddRange(services);
  90. _tunerHosts.AddRange(tunerHosts);
  91. _listingProviders.AddRange(listingProviders);
  92. foreach (var service in _services)
  93. {
  94. service.DataSourceChanged += service_DataSourceChanged;
  95. }
  96. }
  97. public List<ITunerHost> TunerHosts
  98. {
  99. get { return _tunerHosts; }
  100. }
  101. public List<IListingsProvider> ListingProviders
  102. {
  103. get { return _listingProviders; }
  104. }
  105. void service_DataSourceChanged(object sender, EventArgs e)
  106. {
  107. _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
  108. }
  109. public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, CancellationToken cancellationToken)
  110. {
  111. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
  112. var channels = _libraryManager.GetItems(new InternalItemsQuery
  113. {
  114. IncludeItemTypes = new[] { typeof(LiveTvChannel).Name }
  115. }).Items.Cast<LiveTvChannel>();
  116. if (user != null)
  117. {
  118. // Avoid implicitly captured closure
  119. var currentUser = user;
  120. channels = channels
  121. .Where(i => i.IsVisible(currentUser))
  122. .OrderBy(i =>
  123. {
  124. double number = 0;
  125. if (!string.IsNullOrEmpty(i.Number))
  126. {
  127. double.TryParse(i.Number, out number);
  128. }
  129. return number;
  130. });
  131. if (query.IsFavorite.HasValue)
  132. {
  133. var val = query.IsFavorite.Value;
  134. channels = channels
  135. .Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite == val);
  136. }
  137. if (query.IsLiked.HasValue)
  138. {
  139. var val = query.IsLiked.Value;
  140. channels = channels
  141. .Where(i =>
  142. {
  143. var likes = _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).Likes;
  144. return likes.HasValue && likes.Value == val;
  145. });
  146. }
  147. if (query.IsDisliked.HasValue)
  148. {
  149. var val = query.IsDisliked.Value;
  150. channels = channels
  151. .Where(i =>
  152. {
  153. var likes = _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).Likes;
  154. return likes.HasValue && likes.Value != val;
  155. });
  156. }
  157. }
  158. var enableFavoriteSorting = query.EnableFavoriteSorting;
  159. channels = channels.OrderBy(i =>
  160. {
  161. if (enableFavoriteSorting)
  162. {
  163. var userData = _userDataManager.GetUserData(user.Id, i.GetUserDataKey());
  164. if (userData.IsFavorite)
  165. {
  166. return 0;
  167. }
  168. if (userData.Likes.HasValue)
  169. {
  170. if (!userData.Likes.Value)
  171. {
  172. return 3;
  173. }
  174. return 1;
  175. }
  176. }
  177. return 2;
  178. });
  179. var allChannels = channels.ToList();
  180. IEnumerable<LiveTvChannel> allEnumerable = allChannels;
  181. if (query.StartIndex.HasValue)
  182. {
  183. allEnumerable = allEnumerable.Skip(query.StartIndex.Value);
  184. }
  185. if (query.Limit.HasValue)
  186. {
  187. allEnumerable = allEnumerable.Take(query.Limit.Value);
  188. }
  189. var result = new QueryResult<LiveTvChannel>
  190. {
  191. Items = allEnumerable.ToArray(),
  192. TotalRecordCount = allChannels.Count
  193. };
  194. return result;
  195. }
  196. public async Task<QueryResult<ChannelInfoDto>> GetChannels(LiveTvChannelQuery query, CancellationToken cancellationToken)
  197. {
  198. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
  199. var internalResult = await GetInternalChannels(query, cancellationToken).ConfigureAwait(false);
  200. var returnList = new List<ChannelInfoDto>();
  201. var now = DateTime.UtcNow;
  202. var programs = _libraryManager.GetItems(new InternalItemsQuery
  203. {
  204. IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
  205. MaxStartDate = now,
  206. MinEndDate = now
  207. }).Items.Cast<LiveTvProgram>().OrderBy(i => i.StartDate).ToList();
  208. foreach (var channel in internalResult.Items)
  209. {
  210. var channelIdString = channel.Id.ToString("N");
  211. var currentProgram = programs.FirstOrDefault(i => string.Equals(i.ChannelId, channelIdString, StringComparison.OrdinalIgnoreCase));
  212. returnList.Add(_tvDtoService.GetChannelInfoDto(channel, currentProgram, user));
  213. }
  214. var result = new QueryResult<ChannelInfoDto>
  215. {
  216. Items = returnList.ToArray(),
  217. TotalRecordCount = internalResult.TotalRecordCount
  218. };
  219. return result;
  220. }
  221. public LiveTvChannel GetInternalChannel(string id)
  222. {
  223. return GetInternalChannel(new Guid(id));
  224. }
  225. private LiveTvChannel GetInternalChannel(Guid id)
  226. {
  227. return _libraryManager.GetItemById(id) as LiveTvChannel;
  228. }
  229. internal LiveTvProgram GetInternalProgram(string id)
  230. {
  231. return _libraryManager.GetItemById(id) as LiveTvProgram;
  232. }
  233. internal LiveTvProgram GetInternalProgram(Guid id)
  234. {
  235. return _libraryManager.GetItemById(id) as LiveTvProgram;
  236. }
  237. public async Task<ILiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken)
  238. {
  239. var result = await GetInternalRecordings(new RecordingQuery
  240. {
  241. Id = id
  242. }, cancellationToken).ConfigureAwait(false);
  243. return result.Items.FirstOrDefault() as ILiveTvRecording;
  244. }
  245. private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
  246. public async Task<MediaSourceInfo> GetRecordingStream(string id, CancellationToken cancellationToken)
  247. {
  248. return await GetLiveStream(id, null, false, cancellationToken).ConfigureAwait(false);
  249. }
  250. public async Task<MediaSourceInfo> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken)
  251. {
  252. return await GetLiveStream(id, mediaSourceId, true, cancellationToken).ConfigureAwait(false);
  253. }
  254. public async Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(string id, CancellationToken cancellationToken)
  255. {
  256. var item = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
  257. var service = GetService(item);
  258. return await service.GetRecordingStreamMediaSources(id, cancellationToken).ConfigureAwait(false);
  259. }
  260. public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken)
  261. {
  262. var item = GetInternalChannel(id);
  263. var service = GetService(item);
  264. var sources = await service.GetChannelStreamMediaSources(item.ExternalId, cancellationToken).ConfigureAwait(false);
  265. var list = sources.ToList();
  266. foreach (var source in list)
  267. {
  268. Normalize(source, item.ChannelType == ChannelType.TV);
  269. }
  270. return list;
  271. }
  272. private ILiveTvService GetService(ILiveTvItem item)
  273. {
  274. return GetService(item.ServiceName);
  275. }
  276. private ILiveTvService GetService(string name)
  277. {
  278. return _services.FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
  279. }
  280. private async Task<MediaSourceInfo> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken)
  281. {
  282. await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
  283. try
  284. {
  285. MediaSourceInfo info;
  286. bool isVideo;
  287. if (isChannel)
  288. {
  289. var channel = GetInternalChannel(id);
  290. isVideo = channel.ChannelType == ChannelType.TV;
  291. var service = GetService(channel);
  292. _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
  293. info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
  294. info.RequiresClosing = true;
  295. if (info.RequiresClosing ?? false)
  296. {
  297. var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
  298. info.LiveStreamId = idPrefix + info.Id;
  299. }
  300. }
  301. else
  302. {
  303. var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
  304. isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
  305. var service = GetService(recording);
  306. _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId);
  307. info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false);
  308. info.RequiresClosing = true;
  309. if (info.RequiresClosing ?? false)
  310. {
  311. var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
  312. info.LiveStreamId = idPrefix + info.Id;
  313. }
  314. }
  315. _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
  316. Normalize(info, isVideo);
  317. var data = new LiveStreamData
  318. {
  319. Info = info,
  320. IsChannel = isChannel,
  321. ItemId = id
  322. };
  323. _openStreams.AddOrUpdate(info.Id, data, (key, i) => data);
  324. return info;
  325. }
  326. catch (Exception ex)
  327. {
  328. _logger.ErrorException("Error getting channel stream", ex);
  329. throw;
  330. }
  331. finally
  332. {
  333. _liveStreamSemaphore.Release();
  334. }
  335. }
  336. private void Normalize(MediaSourceInfo mediaSource, bool isVideo)
  337. {
  338. if (mediaSource.MediaStreams.Count == 0)
  339. {
  340. if (isVideo)
  341. {
  342. mediaSource.MediaStreams.AddRange(new List<MediaStream>
  343. {
  344. new MediaStream
  345. {
  346. Type = MediaStreamType.Video,
  347. // Set the index to -1 because we don't know the exact index of the video stream within the container
  348. Index = -1,
  349. // Set to true if unknown to enable deinterlacing
  350. IsInterlaced = true
  351. },
  352. new MediaStream
  353. {
  354. Type = MediaStreamType.Audio,
  355. // Set the index to -1 because we don't know the exact index of the audio stream within the container
  356. Index = -1
  357. }
  358. });
  359. }
  360. else
  361. {
  362. mediaSource.MediaStreams.AddRange(new List<MediaStream>
  363. {
  364. new MediaStream
  365. {
  366. Type = MediaStreamType.Audio,
  367. // Set the index to -1 because we don't know the exact index of the audio stream within the container
  368. Index = -1
  369. }
  370. });
  371. }
  372. }
  373. // Clean some bad data coming from providers
  374. foreach (var stream in mediaSource.MediaStreams)
  375. {
  376. if (stream.BitRate.HasValue && stream.BitRate <= 0)
  377. {
  378. stream.BitRate = null;
  379. }
  380. if (stream.Channels.HasValue && stream.Channels <= 0)
  381. {
  382. stream.Channels = null;
  383. }
  384. if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0)
  385. {
  386. stream.AverageFrameRate = null;
  387. }
  388. if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0)
  389. {
  390. stream.RealFrameRate = null;
  391. }
  392. if (stream.Width.HasValue && stream.Width <= 0)
  393. {
  394. stream.Width = null;
  395. }
  396. if (stream.Height.HasValue && stream.Height <= 0)
  397. {
  398. stream.Height = null;
  399. }
  400. if (stream.SampleRate.HasValue && stream.SampleRate <= 0)
  401. {
  402. stream.SampleRate = null;
  403. }
  404. if (stream.Level.HasValue && stream.Level <= 0)
  405. {
  406. stream.Level = null;
  407. }
  408. }
  409. var indexes = mediaSource.MediaStreams.Select(i => i.Index).Distinct().ToList();
  410. // If there are duplicate stream indexes, set them all to unknown
  411. if (indexes.Count != mediaSource.MediaStreams.Count)
  412. {
  413. foreach (var stream in mediaSource.MediaStreams)
  414. {
  415. stream.Index = -1;
  416. }
  417. }
  418. // Set the total bitrate if not already supplied
  419. if (!mediaSource.Bitrate.HasValue)
  420. {
  421. var total = mediaSource.MediaStreams.Select(i => i.BitRate ?? 0).Sum();
  422. if (total > 0)
  423. {
  424. mediaSource.Bitrate = total;
  425. }
  426. }
  427. }
  428. private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
  429. {
  430. var isNew = false;
  431. var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id);
  432. var item = _itemRepo.RetrieveItem(id) as LiveTvChannel;
  433. if (item == null)
  434. {
  435. item = new LiveTvChannel
  436. {
  437. Name = channelInfo.Name,
  438. Id = id,
  439. DateCreated = DateTime.UtcNow,
  440. };
  441. isNew = true;
  442. }
  443. item.ChannelType = channelInfo.ChannelType;
  444. item.ExternalId = channelInfo.Id;
  445. item.ServiceName = serviceName;
  446. item.Number = channelInfo.Number;
  447. var replaceImages = new List<ImageType>();
  448. //if (!string.Equals(item.ProviderImageUrl, channelInfo.ImageUrl, StringComparison.OrdinalIgnoreCase))
  449. //{
  450. // isNew = true;
  451. // replaceImages.Add(ImageType.Primary);
  452. //}
  453. //if (!string.Equals(item.ProviderImagePath, channelInfo.ImagePath, StringComparison.OrdinalIgnoreCase))
  454. //{
  455. // isNew = true;
  456. // replaceImages.Add(ImageType.Primary);
  457. //}
  458. item.ProviderImageUrl = channelInfo.ImageUrl;
  459. item.HasProviderImage = channelInfo.HasImage;
  460. item.ProviderImagePath = channelInfo.ImagePath;
  461. if (string.IsNullOrEmpty(item.Name))
  462. {
  463. item.Name = channelInfo.Name;
  464. }
  465. await item.RefreshMetadata(new MetadataRefreshOptions
  466. {
  467. ForceSave = isNew,
  468. ReplaceImages = replaceImages.Distinct().ToList()
  469. }, cancellationToken);
  470. return item;
  471. }
  472. private async Task<LiveTvProgram> GetProgram(ProgramInfo info, string channelId, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
  473. {
  474. var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id);
  475. var item = _libraryManager.GetItemById(id) as LiveTvProgram;
  476. var isNew = false;
  477. if (item == null)
  478. {
  479. isNew = true;
  480. item = new LiveTvProgram
  481. {
  482. Name = info.Name,
  483. Id = id,
  484. DateCreated = DateTime.UtcNow,
  485. DateModified = DateTime.UtcNow
  486. };
  487. }
  488. item.ChannelType = channelType;
  489. item.ServiceName = serviceName;
  490. item.Audio = info.Audio;
  491. item.ChannelId = channelId;
  492. item.CommunityRating = item.CommunityRating ?? info.CommunityRating;
  493. item.EndDate = info.EndDate;
  494. item.EpisodeTitle = info.EpisodeTitle;
  495. item.ExternalId = info.Id;
  496. item.Genres = info.Genres;
  497. item.HasProviderImage = info.HasImage;
  498. item.IsHD = info.IsHD;
  499. item.IsKids = info.IsKids;
  500. item.IsLive = info.IsLive;
  501. item.IsMovie = info.IsMovie;
  502. item.IsNews = info.IsNews;
  503. item.IsPremiere = info.IsPremiere;
  504. item.IsRepeat = info.IsRepeat;
  505. item.IsSeries = info.IsSeries;
  506. item.IsSports = info.IsSports;
  507. item.Name = info.Name;
  508. item.OfficialRating = item.OfficialRating ?? info.OfficialRating;
  509. item.Overview = item.Overview ?? info.Overview;
  510. item.OriginalAirDate = info.OriginalAirDate;
  511. item.ProviderImagePath = info.ImagePath;
  512. item.ProviderImageUrl = info.ImageUrl;
  513. item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
  514. item.StartDate = info.StartDate;
  515. item.ProductionYear = info.ProductionYear;
  516. item.PremiereDate = item.PremiereDate ?? info.OriginalAirDate;
  517. if (isNew)
  518. {
  519. await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
  520. }
  521. else
  522. {
  523. await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
  524. }
  525. var maxStartDate = DateTime.UtcNow.AddDays(3);
  526. _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions
  527. {
  528. ImageRefreshMode = info.StartDate <= maxStartDate ? ImageRefreshMode.Default : ImageRefreshMode.ValidationOnly
  529. });
  530. return item;
  531. }
  532. private void RefreshIfNeeded(LiveTvProgram program)
  533. {
  534. if (_refreshedPrograms.TryAdd(program.Id, program.Id))
  535. {
  536. _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions());
  537. }
  538. }
  539. private async Task<Guid> CreateRecordingRecord(RecordingInfo info, string serviceName, CancellationToken cancellationToken)
  540. {
  541. var isNew = false;
  542. var id = _tvDtoService.GetInternalRecordingId(serviceName, info.Id);
  543. var item = _itemRepo.RetrieveItem(id);
  544. if (item == null)
  545. {
  546. if (info.ChannelType == ChannelType.TV)
  547. {
  548. item = new LiveTvVideoRecording
  549. {
  550. Name = info.Name,
  551. Id = id,
  552. DateCreated = DateTime.UtcNow,
  553. DateModified = DateTime.UtcNow,
  554. VideoType = VideoType.VideoFile
  555. };
  556. }
  557. else
  558. {
  559. item = new LiveTvAudioRecording
  560. {
  561. Name = info.Name,
  562. Id = id,
  563. DateCreated = DateTime.UtcNow,
  564. DateModified = DateTime.UtcNow
  565. };
  566. }
  567. isNew = true;
  568. }
  569. item.ChannelId = _tvDtoService.GetInternalChannelId(serviceName, info.ChannelId).ToString("N");
  570. item.CommunityRating = info.CommunityRating;
  571. item.OfficialRating = info.OfficialRating;
  572. item.Overview = info.Overview;
  573. item.EndDate = info.EndDate;
  574. item.Genres = info.Genres;
  575. var recording = (ILiveTvRecording)item;
  576. recording.ExternalId = info.Id;
  577. recording.ProgramId = _tvDtoService.GetInternalProgramId(serviceName, info.ProgramId).ToString("N");
  578. recording.Audio = info.Audio;
  579. recording.ChannelType = info.ChannelType;
  580. recording.EndDate = info.EndDate;
  581. recording.EpisodeTitle = info.EpisodeTitle;
  582. recording.ProviderImagePath = info.ImagePath;
  583. recording.ProviderImageUrl = info.ImageUrl;
  584. recording.IsHD = info.IsHD;
  585. recording.IsKids = info.IsKids;
  586. recording.IsLive = info.IsLive;
  587. recording.IsMovie = info.IsMovie;
  588. recording.IsNews = info.IsNews;
  589. recording.IsPremiere = info.IsPremiere;
  590. recording.IsRepeat = info.IsRepeat;
  591. recording.IsSeries = info.IsSeries;
  592. recording.IsSports = info.IsSports;
  593. recording.OriginalAirDate = info.OriginalAirDate;
  594. recording.SeriesTimerId = info.SeriesTimerId;
  595. recording.StartDate = info.StartDate;
  596. recording.Status = info.Status;
  597. recording.ServiceName = serviceName;
  598. var originalPath = item.Path;
  599. if (!string.IsNullOrEmpty(info.Path))
  600. {
  601. item.Path = info.Path;
  602. }
  603. else if (!string.IsNullOrEmpty(info.Url))
  604. {
  605. item.Path = info.Url;
  606. }
  607. var pathChanged = !string.Equals(originalPath, item.Path);
  608. if (isNew)
  609. {
  610. await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
  611. }
  612. else if (pathChanged)
  613. {
  614. await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
  615. }
  616. _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions());
  617. return item.Id;
  618. }
  619. public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
  620. {
  621. var program = GetInternalProgram(id);
  622. RefreshIfNeeded(program);
  623. var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
  624. await AddRecordingInfo(new[] { dto }, cancellationToken).ConfigureAwait(false);
  625. return dto;
  626. }
  627. public async Task<QueryResult<BaseItemDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken)
  628. {
  629. var internalQuery = new InternalItemsQuery
  630. {
  631. IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
  632. MinEndDate = query.MinEndDate,
  633. MinStartDate = query.MinStartDate,
  634. MaxEndDate = query.MaxEndDate,
  635. MaxStartDate = query.MaxStartDate,
  636. ChannelIds = query.ChannelIds,
  637. IsMovie = query.IsMovie,
  638. IsSports = query.IsSports
  639. };
  640. if (query.HasAired.HasValue)
  641. {
  642. if (query.HasAired.Value)
  643. {
  644. internalQuery.MaxEndDate = DateTime.UtcNow;
  645. }
  646. else
  647. {
  648. internalQuery.MinEndDate = DateTime.UtcNow;
  649. }
  650. }
  651. IEnumerable<LiveTvProgram> programs = _libraryManager.GetItems(internalQuery).Items.Cast<LiveTvProgram>();
  652. // Apply genre filter
  653. if (query.Genres.Length > 0)
  654. {
  655. programs = programs.Where(p => p.Genres.Any(g => query.Genres.Contains(g, StringComparer.OrdinalIgnoreCase)));
  656. }
  657. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
  658. if (user != null)
  659. {
  660. // Avoid implicitly captured closure
  661. var currentUser = user;
  662. programs = programs.Where(i => i.IsVisible(currentUser));
  663. }
  664. programs = _libraryManager.Sort(programs, user, query.SortBy, query.SortOrder ?? SortOrder.Ascending)
  665. .Cast<LiveTvProgram>();
  666. var programList = programs.ToList();
  667. IEnumerable<LiveTvProgram> returnPrograms = programList;
  668. if (query.StartIndex.HasValue)
  669. {
  670. returnPrograms = returnPrograms.Skip(query.StartIndex.Value);
  671. }
  672. if (query.Limit.HasValue)
  673. {
  674. returnPrograms = returnPrograms.Take(query.Limit.Value);
  675. }
  676. var returnArray = returnPrograms
  677. .Select(i =>
  678. {
  679. RefreshIfNeeded(i);
  680. return _dtoService.GetBaseItemDto(i, new DtoOptions(), user);
  681. })
  682. .ToArray();
  683. await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false);
  684. var result = new QueryResult<BaseItemDto>
  685. {
  686. Items = returnArray,
  687. TotalRecordCount = programList.Count
  688. };
  689. return result;
  690. }
  691. public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken)
  692. {
  693. var internalQuery = new InternalItemsQuery
  694. {
  695. IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
  696. IsAiring = query.IsAiring,
  697. IsMovie = query.IsMovie,
  698. IsSports = query.IsSports
  699. };
  700. if (query.HasAired.HasValue)
  701. {
  702. if (query.HasAired.Value)
  703. {
  704. internalQuery.MaxEndDate = DateTime.UtcNow;
  705. }
  706. else
  707. {
  708. internalQuery.MinEndDate = DateTime.UtcNow;
  709. }
  710. }
  711. IEnumerable<LiveTvProgram> programs = _libraryManager.GetItems(internalQuery).Items.Cast<LiveTvProgram>();
  712. var user = _userManager.GetUserById(query.UserId);
  713. // Avoid implicitly captured closure
  714. var currentUser = user;
  715. programs = programs.Where(i => i.IsVisible(currentUser));
  716. var programList = programs.ToList();
  717. var genres = programList.SelectMany(i => i.Genres)
  718. .DistinctNames()
  719. .Select(i => _libraryManager.GetGenre(i))
  720. .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
  721. programs = programList.OrderBy(i => i.HasImage(ImageType.Primary) ? 0 : 1)
  722. .ThenByDescending(i => GetRecommendationScore(i, user.Id, genres))
  723. .ThenBy(i => i.StartDate);
  724. if (query.Limit.HasValue)
  725. {
  726. programs = programs.Take(query.Limit.Value)
  727. .OrderBy(i => i.StartDate);
  728. }
  729. programList = programs.ToList();
  730. var returnArray = programList.ToArray();
  731. foreach (var program in returnArray)
  732. {
  733. RefreshIfNeeded(program);
  734. }
  735. var result = new QueryResult<LiveTvProgram>
  736. {
  737. Items = returnArray,
  738. TotalRecordCount = returnArray.Length
  739. };
  740. return result;
  741. }
  742. public async Task<QueryResult<BaseItemDto>> GetRecommendedPrograms(RecommendedProgramQuery query, CancellationToken cancellationToken)
  743. {
  744. var internalResult = await GetRecommendedProgramsInternal(query, cancellationToken).ConfigureAwait(false);
  745. var user = _userManager.GetUserById(query.UserId);
  746. var returnArray = internalResult.Items
  747. .Select(i => _dtoService.GetBaseItemDto(i, new DtoOptions(), user))
  748. .ToArray();
  749. await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false);
  750. var result = new QueryResult<BaseItemDto>
  751. {
  752. Items = returnArray,
  753. TotalRecordCount = internalResult.TotalRecordCount
  754. };
  755. return result;
  756. }
  757. private int GetRecommendationScore(LiveTvProgram program, Guid userId, Dictionary<string, Genre> genres)
  758. {
  759. var score = 0;
  760. if (program.IsLive)
  761. {
  762. score++;
  763. }
  764. if (program.IsSeries && !program.IsRepeat)
  765. {
  766. score++;
  767. }
  768. var channel = GetInternalChannel(program.ChannelId);
  769. var channelUserdata = _userDataManager.GetUserData(userId, channel.GetUserDataKey());
  770. if ((channelUserdata.Likes ?? false))
  771. {
  772. score += 2;
  773. }
  774. else if (!(channelUserdata.Likes ?? true))
  775. {
  776. score -= 2;
  777. }
  778. if (channelUserdata.IsFavorite)
  779. {
  780. score += 3;
  781. }
  782. score += GetGenreScore(program.Genres, userId, genres);
  783. return score;
  784. }
  785. private int GetGenreScore(IEnumerable<string> programGenres, Guid userId, Dictionary<string, Genre> genres)
  786. {
  787. return programGenres.Select(i =>
  788. {
  789. var score = 0;
  790. Genre genre;
  791. if (genres.TryGetValue(i, out genre))
  792. {
  793. var genreUserdata = _userDataManager.GetUserData(userId, genre.GetUserDataKey());
  794. if ((genreUserdata.Likes ?? false))
  795. {
  796. score++;
  797. }
  798. else if (!(genreUserdata.Likes ?? true))
  799. {
  800. score--;
  801. }
  802. if (genreUserdata.IsFavorite)
  803. {
  804. score += 2;
  805. }
  806. }
  807. return score;
  808. }).Sum();
  809. }
  810. private async Task AddRecordingInfo(IEnumerable<BaseItemDto> programs, CancellationToken cancellationToken)
  811. {
  812. var timers = new Dictionary<string, List<TimerInfo>>();
  813. foreach (var program in programs)
  814. {
  815. var internalProgram = GetInternalProgram(program.Id);
  816. List<TimerInfo> timerList;
  817. if (!timers.TryGetValue(internalProgram.ServiceName, out timerList))
  818. {
  819. try
  820. {
  821. var tempTimers = await GetService(internalProgram.ServiceName).GetTimersAsync(cancellationToken).ConfigureAwait(false);
  822. timers[internalProgram.ServiceName] = timerList = tempTimers.ToList();
  823. }
  824. catch (Exception ex)
  825. {
  826. _logger.ErrorException("Error getting timer infos", ex);
  827. timers[internalProgram.ServiceName] = timerList = new List<TimerInfo>();
  828. }
  829. }
  830. var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, internalProgram.ExternalId, StringComparison.OrdinalIgnoreCase));
  831. if (timer != null)
  832. {
  833. program.TimerId = _tvDtoService.GetInternalTimerId(internalProgram.ServiceName, timer.Id)
  834. .ToString("N");
  835. if (!string.IsNullOrEmpty(timer.SeriesTimerId))
  836. {
  837. program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(internalProgram.ServiceName, timer.SeriesTimerId)
  838. .ToString("N");
  839. }
  840. }
  841. }
  842. }
  843. internal Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
  844. {
  845. return RefreshChannelsInternal(progress, cancellationToken);
  846. }
  847. private async Task RefreshChannelsInternal(IProgress<double> progress, CancellationToken cancellationToken)
  848. {
  849. var numComplete = 0;
  850. double progressPerService = _services.Count == 0
  851. ? 0
  852. : 1 / _services.Count;
  853. var newChannelIdList = new List<Guid>();
  854. var newProgramIdList = new List<Guid>();
  855. foreach (var service in _services)
  856. {
  857. cancellationToken.ThrowIfCancellationRequested();
  858. try
  859. {
  860. var innerProgress = new ActionableProgress<double>();
  861. innerProgress.RegisterAction(p => progress.Report(p * progressPerService));
  862. var idList = await RefreshChannelsInternal(service, innerProgress, cancellationToken).ConfigureAwait(false);
  863. newChannelIdList.AddRange(idList.Item1);
  864. newProgramIdList.AddRange(idList.Item2);
  865. }
  866. catch (OperationCanceledException)
  867. {
  868. throw;
  869. }
  870. catch (Exception ex)
  871. {
  872. _logger.ErrorException("Error refreshing channels for service", ex);
  873. }
  874. numComplete++;
  875. double percent = numComplete;
  876. percent /= _services.Count;
  877. progress.Report(100 * percent);
  878. }
  879. await CleanDatabaseInternal(newChannelIdList, new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken).ConfigureAwait(false);
  880. await CleanDatabaseInternal(newProgramIdList, new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken).ConfigureAwait(false);
  881. _refreshedPrograms.Clear();
  882. // Load these now which will prefetch metadata
  883. var dtoOptions = new DtoOptions();
  884. dtoOptions.Fields.Remove(ItemFields.SyncInfo);
  885. await GetRecordings(new RecordingQuery(), dtoOptions, cancellationToken).ConfigureAwait(false);
  886. progress.Report(100);
  887. }
  888. private async Task<Tuple<List<Guid>, List<Guid>>> RefreshChannelsInternal(ILiveTvService service, IProgress<double> progress, CancellationToken cancellationToken)
  889. {
  890. progress.Report(10);
  891. var allChannels = await GetChannels(service, cancellationToken).ConfigureAwait(false);
  892. var allChannelsList = allChannels.ToList();
  893. var list = new List<LiveTvChannel>();
  894. var numComplete = 0;
  895. foreach (var channelInfo in allChannelsList)
  896. {
  897. cancellationToken.ThrowIfCancellationRequested();
  898. try
  899. {
  900. var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, cancellationToken).ConfigureAwait(false);
  901. list.Add(item);
  902. _libraryManager.RegisterItem(item);
  903. }
  904. catch (OperationCanceledException)
  905. {
  906. throw;
  907. }
  908. catch (Exception ex)
  909. {
  910. _logger.ErrorException("Error getting channel information for {0}", ex, channelInfo.Item2.Name);
  911. }
  912. numComplete++;
  913. double percent = numComplete;
  914. percent /= allChannelsList.Count;
  915. progress.Report(5 * percent + 10);
  916. }
  917. progress.Report(15);
  918. numComplete = 0;
  919. var programs = new List<Guid>();
  920. var channels = new List<Guid>();
  921. var guideDays = GetGuideDays(list.Count);
  922. cancellationToken.ThrowIfCancellationRequested();
  923. foreach (var currentChannel in list)
  924. {
  925. channels.Add(currentChannel.Id);
  926. cancellationToken.ThrowIfCancellationRequested();
  927. try
  928. {
  929. var start = DateTime.UtcNow.AddHours(-1);
  930. var end = start.AddDays(guideDays);
  931. var channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false);
  932. var channelId = currentChannel.Id.ToString("N");
  933. foreach (var program in channelPrograms)
  934. {
  935. var programItem = await GetProgram(program, channelId, currentChannel.ChannelType, service.Name, cancellationToken).ConfigureAwait(false);
  936. programs.Add(programItem.Id);
  937. }
  938. }
  939. catch (OperationCanceledException)
  940. {
  941. throw;
  942. }
  943. catch (Exception ex)
  944. {
  945. _logger.ErrorException("Error getting programs for channel {0}", ex, currentChannel.Name);
  946. }
  947. numComplete++;
  948. double percent = numComplete;
  949. percent /= allChannelsList.Count;
  950. progress.Report(80 * percent + 10);
  951. }
  952. progress.Report(100);
  953. return new Tuple<List<Guid>, List<Guid>>(channels, programs);
  954. }
  955. private async Task CleanDatabaseInternal(List<Guid> currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
  956. {
  957. var list = _itemRepo.GetItemIds(new InternalItemsQuery
  958. {
  959. IncludeItemTypes = validTypes
  960. }).Items.ToList();
  961. var numComplete = 0;
  962. foreach (var itemId in list)
  963. {
  964. cancellationToken.ThrowIfCancellationRequested();
  965. if (!currentIdList.Contains(itemId))
  966. {
  967. var item = _libraryManager.GetItemById(itemId);
  968. if (item != null)
  969. {
  970. await _libraryManager.DeleteItem(item, new DeleteOptions
  971. {
  972. DeleteFileLocation = false
  973. }).ConfigureAwait(false);
  974. }
  975. }
  976. numComplete++;
  977. double percent = numComplete;
  978. percent /= list.Count;
  979. progress.Report(100 * percent);
  980. }
  981. }
  982. private double GetGuideDays(int channelCount)
  983. {
  984. var config = GetConfiguration();
  985. if (config.GuideDays.HasValue)
  986. {
  987. return config.GuideDays.Value;
  988. }
  989. var programsPerDay = channelCount * 48;
  990. const int maxPrograms = 24000;
  991. var days = Math.Round(((double)maxPrograms) / programsPerDay);
  992. return Math.Max(3, Math.Min(days, 14));
  993. }
  994. private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)
  995. {
  996. var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false);
  997. return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i));
  998. }
  999. private DateTime _lastRecordingRefreshTime;
  1000. private async Task RefreshRecordings(CancellationToken cancellationToken)
  1001. {
  1002. const int cacheMinutes = 5;
  1003. if ((DateTime.UtcNow - _lastRecordingRefreshTime).TotalMinutes < cacheMinutes)
  1004. {
  1005. return;
  1006. }
  1007. await _refreshRecordingsLock.WaitAsync(cancellationToken).ConfigureAwait(false);
  1008. try
  1009. {
  1010. if ((DateTime.UtcNow - _lastRecordingRefreshTime).TotalMinutes < cacheMinutes)
  1011. {
  1012. return;
  1013. }
  1014. var tasks = _services.Select(async i =>
  1015. {
  1016. try
  1017. {
  1018. var recs = await i.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
  1019. return recs.Select(r => new Tuple<RecordingInfo, ILiveTvService>(r, i));
  1020. }
  1021. catch (Exception ex)
  1022. {
  1023. _logger.ErrorException("Error getting recordings", ex);
  1024. return new List<Tuple<RecordingInfo, ILiveTvService>>();
  1025. }
  1026. });
  1027. var results = await Task.WhenAll(tasks).ConfigureAwait(false);
  1028. var recordingTasks = results.SelectMany(i => i.ToList()).Select(i => CreateRecordingRecord(i.Item1, i.Item2.Name, cancellationToken));
  1029. var idList = await Task.WhenAll(recordingTasks).ConfigureAwait(false);
  1030. CleanDatabaseInternal(idList.ToList(), new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }, new Progress<double>(), cancellationToken).ConfigureAwait(false);
  1031. _lastRecordingRefreshTime = DateTime.UtcNow;
  1032. }
  1033. finally
  1034. {
  1035. _refreshRecordingsLock.Release();
  1036. }
  1037. }
  1038. public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken)
  1039. {
  1040. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
  1041. if (user != null && !IsLiveTvEnabled(user))
  1042. {
  1043. return new QueryResult<BaseItem>();
  1044. }
  1045. await RefreshRecordings(cancellationToken).ConfigureAwait(false);
  1046. var internalQuery = new InternalItemsQuery
  1047. {
  1048. IncludeItemTypes = new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }
  1049. };
  1050. if (!string.IsNullOrEmpty(query.ChannelId))
  1051. {
  1052. internalQuery.ChannelIds = new[] { query.ChannelId };
  1053. }
  1054. var queryResult = _libraryManager.GetItems(internalQuery);
  1055. IEnumerable<ILiveTvRecording> recordings = queryResult.Items.Cast<ILiveTvRecording>();
  1056. if (!string.IsNullOrEmpty(query.Id))
  1057. {
  1058. var guid = new Guid(query.Id);
  1059. recordings = recordings
  1060. .Where(i => i.Id == guid);
  1061. }
  1062. if (!string.IsNullOrEmpty(query.GroupId))
  1063. {
  1064. var guid = new Guid(query.GroupId);
  1065. recordings = recordings.Where(i => GetRecordingGroupIds(i).Contains(guid));
  1066. }
  1067. if (query.IsInProgress.HasValue)
  1068. {
  1069. var val = query.IsInProgress.Value;
  1070. recordings = recordings.Where(i => (i.Status == RecordingStatus.InProgress) == val);
  1071. }
  1072. if (query.Status.HasValue)
  1073. {
  1074. var val = query.Status.Value;
  1075. recordings = recordings.Where(i => (i.Status == val));
  1076. }
  1077. if (!string.IsNullOrEmpty(query.SeriesTimerId))
  1078. {
  1079. var guid = new Guid(query.SeriesTimerId);
  1080. recordings = recordings
  1081. .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.ServiceName, i.SeriesTimerId) == guid);
  1082. }
  1083. if (user != null)
  1084. {
  1085. var currentUser = user;
  1086. recordings = recordings.Where(i => i.IsParentalAllowed(currentUser));
  1087. }
  1088. recordings = recordings.OrderByDescending(i => i.StartDate);
  1089. var entityList = recordings.ToList();
  1090. IEnumerable<ILiveTvRecording> entities = entityList;
  1091. if (query.StartIndex.HasValue)
  1092. {
  1093. entities = entities.Skip(query.StartIndex.Value);
  1094. }
  1095. if (query.Limit.HasValue)
  1096. {
  1097. entities = entities.Take(query.Limit.Value);
  1098. }
  1099. return new QueryResult<BaseItem>
  1100. {
  1101. Items = entities.Cast<BaseItem>().ToArray(),
  1102. TotalRecordCount = entityList.Count
  1103. };
  1104. }
  1105. public void AddInfoToProgramDto(BaseItem item, BaseItemDto dto, User user = null)
  1106. {
  1107. var program = (LiveTvProgram)item;
  1108. var service = GetService(program);
  1109. var channel = GetInternalChannel(program.ChannelId);
  1110. dto.Id = _tvDtoService.GetInternalProgramId(service.Name, program.ExternalId).ToString("N");
  1111. dto.ChannelId = item.ChannelId;
  1112. dto.StartDate = program.StartDate;
  1113. dto.IsRepeat = program.IsRepeat;
  1114. dto.EpisodeTitle = program.EpisodeTitle;
  1115. dto.ChannelType = program.ChannelType;
  1116. dto.Audio = program.Audio;
  1117. dto.IsHD = program.IsHD;
  1118. dto.IsMovie = program.IsMovie;
  1119. dto.IsSeries = program.IsSeries;
  1120. dto.IsSports = program.IsSports;
  1121. dto.IsLive = program.IsLive;
  1122. dto.IsNews = program.IsNews;
  1123. dto.IsKids = program.IsKids;
  1124. dto.IsPremiere = program.IsPremiere;
  1125. dto.OriginalAirDate = program.OriginalAirDate;
  1126. if (channel != null)
  1127. {
  1128. dto.ChannelName = channel.Name;
  1129. if (!string.IsNullOrEmpty(channel.PrimaryImagePath))
  1130. {
  1131. dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
  1132. }
  1133. }
  1134. }
  1135. public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, User user = null)
  1136. {
  1137. var recording = (ILiveTvRecording)item;
  1138. var service = GetService(recording);
  1139. var channel = string.IsNullOrWhiteSpace(recording.ChannelId) ? null : GetInternalChannel(recording.ChannelId);
  1140. var info = recording;
  1141. dto.Id = item.Id.ToString("N");
  1142. dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
  1143. ? null
  1144. : _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N");
  1145. dto.ChannelId = item.ChannelId;
  1146. dto.StartDate = info.StartDate;
  1147. dto.RecordingStatus = info.Status;
  1148. dto.IsRepeat = info.IsRepeat;
  1149. dto.EpisodeTitle = info.EpisodeTitle;
  1150. dto.ChannelType = info.ChannelType;
  1151. dto.Audio = info.Audio;
  1152. dto.IsHD = info.IsHD;
  1153. dto.IsMovie = info.IsMovie;
  1154. dto.IsSeries = info.IsSeries;
  1155. dto.IsSports = info.IsSports;
  1156. dto.IsLive = info.IsLive;
  1157. dto.IsNews = info.IsNews;
  1158. dto.IsKids = info.IsKids;
  1159. dto.IsPremiere = info.IsPremiere;
  1160. dto.OriginalAirDate = info.OriginalAirDate;
  1161. dto.CanDelete = user == null
  1162. ? recording.CanDelete()
  1163. : recording.CanDelete(user);
  1164. if (dto.MediaSources == null)
  1165. {
  1166. dto.MediaSources = recording.GetMediaSources(true).ToList();
  1167. }
  1168. if (dto.MediaStreams == null)
  1169. {
  1170. dto.MediaStreams = dto.MediaSources.SelectMany(i => i.MediaStreams).ToList();
  1171. }
  1172. if (info.Status == RecordingStatus.InProgress && info.EndDate.HasValue)
  1173. {
  1174. var now = DateTime.UtcNow.Ticks;
  1175. var start = info.StartDate.Ticks;
  1176. var end = info.EndDate.Value.Ticks;
  1177. var pct = now - start;
  1178. pct /= end;
  1179. pct *= 100;
  1180. dto.CompletionPercentage = pct;
  1181. }
  1182. dto.ProgramId = info.ProgramId;
  1183. if (channel != null)
  1184. {
  1185. dto.ChannelName = channel.Name;
  1186. if (!string.IsNullOrEmpty(channel.PrimaryImagePath))
  1187. {
  1188. dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
  1189. }
  1190. }
  1191. }
  1192. public async Task<QueryResult<BaseItemDto>> GetRecordings(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken)
  1193. {
  1194. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
  1195. var internalResult = await GetInternalRecordings(query, cancellationToken).ConfigureAwait(false);
  1196. var returnArray = internalResult.Items
  1197. .Select(i => _dtoService.GetBaseItemDto(i, options, user))
  1198. .ToArray();
  1199. if (user != null)
  1200. {
  1201. _dtoService.FillSyncInfo(returnArray, new DtoOptions(), user);
  1202. }
  1203. return new QueryResult<BaseItemDto>
  1204. {
  1205. Items = returnArray,
  1206. TotalRecordCount = internalResult.TotalRecordCount
  1207. };
  1208. }
  1209. public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
  1210. {
  1211. var tasks = _services.Select(async i =>
  1212. {
  1213. try
  1214. {
  1215. var recs = await i.GetTimersAsync(cancellationToken).ConfigureAwait(false);
  1216. return recs.Select(r => new Tuple<TimerInfo, ILiveTvService>(r, i));
  1217. }
  1218. catch (Exception ex)
  1219. {
  1220. _logger.ErrorException("Error getting recordings", ex);
  1221. return new List<Tuple<TimerInfo, ILiveTvService>>();
  1222. }
  1223. });
  1224. var results = await Task.WhenAll(tasks).ConfigureAwait(false);
  1225. var timers = results.SelectMany(i => i.ToList());
  1226. if (!string.IsNullOrEmpty(query.ChannelId))
  1227. {
  1228. var guid = new Guid(query.ChannelId);
  1229. timers = timers.Where(i => guid == _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId));
  1230. }
  1231. if (!string.IsNullOrEmpty(query.SeriesTimerId))
  1232. {
  1233. var guid = new Guid(query.SeriesTimerId);
  1234. timers = timers
  1235. .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item2.Name, i.Item1.SeriesTimerId) == guid);
  1236. }
  1237. var returnList = new List<TimerInfoDto>();
  1238. foreach (var i in timers)
  1239. {
  1240. var program = string.IsNullOrEmpty(i.Item1.ProgramId) ?
  1241. null :
  1242. GetInternalProgram(_tvDtoService.GetInternalProgramId(i.Item2.Name, i.Item1.ProgramId).ToString("N"));
  1243. var channel = string.IsNullOrEmpty(i.Item1.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId));
  1244. returnList.Add(_tvDtoService.GetTimerInfoDto(i.Item1, i.Item2, program, channel));
  1245. }
  1246. var returnArray = returnList
  1247. .OrderBy(i => i.StartDate)
  1248. .ToArray();
  1249. return new QueryResult<TimerInfoDto>
  1250. {
  1251. Items = returnArray,
  1252. TotalRecordCount = returnArray.Length
  1253. };
  1254. }
  1255. public async Task DeleteRecording(string recordingId)
  1256. {
  1257. var recording = await GetInternalRecording(recordingId, CancellationToken.None).ConfigureAwait(false);
  1258. if (recording == null)
  1259. {
  1260. throw new ResourceNotFoundException(string.Format("Recording with Id {0} not found", recordingId));
  1261. }
  1262. var service = GetService(recording.ServiceName);
  1263. await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false);
  1264. _lastRecordingRefreshTime = DateTime.MinValue;
  1265. }
  1266. public async Task CancelTimer(string id)
  1267. {
  1268. var timer = await GetTimer(id, CancellationToken.None).ConfigureAwait(false);
  1269. if (timer == null)
  1270. {
  1271. throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
  1272. }
  1273. var service = GetService(timer.ServiceName);
  1274. await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
  1275. _lastRecordingRefreshTime = DateTime.MinValue;
  1276. }
  1277. public async Task CancelSeriesTimer(string id)
  1278. {
  1279. var timer = await GetSeriesTimer(id, CancellationToken.None).ConfigureAwait(false);
  1280. if (timer == null)
  1281. {
  1282. throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
  1283. }
  1284. var service = GetService(timer.ServiceName);
  1285. await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
  1286. _lastRecordingRefreshTime = DateTime.MinValue;
  1287. }
  1288. public async Task<BaseItemDto> GetRecording(string id, DtoOptions options, CancellationToken cancellationToken, User user = null)
  1289. {
  1290. var item = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
  1291. if (item == null)
  1292. {
  1293. return null;
  1294. }
  1295. return _dtoService.GetBaseItemDto((BaseItem)item, options, user);
  1296. }
  1297. public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
  1298. {
  1299. var results = await GetTimers(new TimerQuery(), cancellationToken).ConfigureAwait(false);
  1300. return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
  1301. }
  1302. public async Task<SeriesTimerInfoDto> GetSeriesTimer(string id, CancellationToken cancellationToken)
  1303. {
  1304. var results = await GetSeriesTimers(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false);
  1305. return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
  1306. }
  1307. public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
  1308. {
  1309. var tasks = _services.Select(async i =>
  1310. {
  1311. try
  1312. {
  1313. var recs = await i.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
  1314. return recs.Select(r => new Tuple<SeriesTimerInfo, ILiveTvService>(r, i));
  1315. }
  1316. catch (Exception ex)
  1317. {
  1318. _logger.ErrorException("Error getting recordings", ex);
  1319. return new List<Tuple<SeriesTimerInfo, ILiveTvService>>();
  1320. }
  1321. });
  1322. var results = await Task.WhenAll(tasks).ConfigureAwait(false);
  1323. var timers = results.SelectMany(i => i.ToList());
  1324. if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
  1325. {
  1326. timers = query.SortOrder == SortOrder.Descending ?
  1327. timers.OrderBy(i => i.Item1.Priority).ThenByStringDescending(i => i.Item1.Name) :
  1328. timers.OrderByDescending(i => i.Item1.Priority).ThenByString(i => i.Item1.Name);
  1329. }
  1330. else
  1331. {
  1332. timers = query.SortOrder == SortOrder.Descending ?
  1333. timers.OrderByStringDescending(i => i.Item1.Name) :
  1334. timers.OrderByString(i => i.Item1.Name);
  1335. }
  1336. var returnArray = timers
  1337. .Select(i =>
  1338. {
  1339. string channelName = null;
  1340. if (!string.IsNullOrEmpty(i.Item1.ChannelId))
  1341. {
  1342. var internalChannelId = _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId);
  1343. var channel = GetInternalChannel(internalChannelId);
  1344. channelName = channel == null ? null : channel.Name;
  1345. }
  1346. return _tvDtoService.GetSeriesTimerInfoDto(i.Item1, i.Item2, channelName);
  1347. })
  1348. .ToArray();
  1349. return new QueryResult<SeriesTimerInfoDto>
  1350. {
  1351. Items = returnArray,
  1352. TotalRecordCount = returnArray.Length
  1353. };
  1354. }
  1355. public async Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null)
  1356. {
  1357. var channel = GetInternalChannel(id);
  1358. var now = DateTime.UtcNow;
  1359. var programs = _libraryManager.GetItems(new InternalItemsQuery
  1360. {
  1361. IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
  1362. ChannelIds = new[] { id },
  1363. MaxStartDate = now,
  1364. MinEndDate = now,
  1365. Limit = 1
  1366. }).Items.Cast<LiveTvProgram>();
  1367. var currentProgram = programs
  1368. .OrderBy(i => i.StartDate)
  1369. .FirstOrDefault();
  1370. var dto = _tvDtoService.GetChannelInfoDto(channel, currentProgram, user);
  1371. return dto;
  1372. }
  1373. private async Task<Tuple<SeriesTimerInfo, ILiveTvService>> GetNewTimerDefaultsInternal(CancellationToken cancellationToken, LiveTvProgram program = null)
  1374. {
  1375. var service = program != null && !string.IsNullOrWhiteSpace(program.ServiceName) ?
  1376. GetService(program) :
  1377. _services.FirstOrDefault();
  1378. ProgramInfo programInfo = null;
  1379. if (program != null)
  1380. {
  1381. var channel = GetInternalChannel(program.ChannelId);
  1382. programInfo = new ProgramInfo
  1383. {
  1384. Audio = program.Audio,
  1385. ChannelId = channel.ExternalId,
  1386. CommunityRating = program.CommunityRating,
  1387. EndDate = program.EndDate ?? DateTime.MinValue,
  1388. EpisodeTitle = program.EpisodeTitle,
  1389. Genres = program.Genres,
  1390. HasImage = program.HasProviderImage,
  1391. Id = program.ExternalId,
  1392. IsHD = program.IsHD,
  1393. IsKids = program.IsKids,
  1394. IsLive = program.IsLive,
  1395. IsMovie = program.IsMovie,
  1396. IsNews = program.IsNews,
  1397. IsPremiere = program.IsPremiere,
  1398. IsRepeat = program.IsRepeat,
  1399. IsSeries = program.IsSeries,
  1400. IsSports = program.IsSports,
  1401. OriginalAirDate = program.PremiereDate,
  1402. Overview = program.Overview,
  1403. StartDate = program.StartDate,
  1404. ImagePath = program.ProviderImagePath,
  1405. ImageUrl = program.ProviderImageUrl,
  1406. Name = program.Name,
  1407. OfficialRating = program.OfficialRating
  1408. };
  1409. }
  1410. var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);
  1411. info.Id = null;
  1412. return new Tuple<SeriesTimerInfo, ILiveTvService>(info, service);
  1413. }
  1414. public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
  1415. {
  1416. var info = await GetNewTimerDefaultsInternal(cancellationToken).ConfigureAwait(false);
  1417. var obj = _tvDtoService.GetSeriesTimerInfoDto(info.Item1, info.Item2, null);
  1418. return obj;
  1419. }
  1420. public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
  1421. {
  1422. var program = GetInternalProgram(programId);
  1423. var programDto = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
  1424. var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
  1425. var info = _tvDtoService.GetSeriesTimerInfoDto(defaults.Item1, defaults.Item2, null);
  1426. info.Days = new List<DayOfWeek>
  1427. {
  1428. program.StartDate.ToLocalTime().DayOfWeek
  1429. };
  1430. info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
  1431. info.Name = program.Name;
  1432. info.ChannelId = programDto.ChannelId;
  1433. info.ChannelName = programDto.ChannelName;
  1434. info.StartDate = program.StartDate;
  1435. info.Name = program.Name;
  1436. info.Overview = program.Overview;
  1437. info.ProgramId = programDto.Id;
  1438. info.ExternalProgramId = program.ExternalId;
  1439. if (program.EndDate.HasValue)
  1440. {
  1441. info.EndDate = program.EndDate.Value;
  1442. }
  1443. return info;
  1444. }
  1445. public async Task CreateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
  1446. {
  1447. var service = GetService(timer.ServiceName);
  1448. var info = await _tvDtoService.GetTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
  1449. // Set priority from default values
  1450. var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
  1451. info.Priority = defaultValues.Priority;
  1452. await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
  1453. _lastRecordingRefreshTime = DateTime.MinValue;
  1454. }
  1455. public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
  1456. {
  1457. var service = GetService(timer.ServiceName);
  1458. var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
  1459. // Set priority from default values
  1460. var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
  1461. info.Priority = defaultValues.Priority;
  1462. await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
  1463. _lastRecordingRefreshTime = DateTime.MinValue;
  1464. }
  1465. public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
  1466. {
  1467. var info = await _tvDtoService.GetTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
  1468. var service = GetService(timer.ServiceName);
  1469. await service.UpdateTimerAsync(info, cancellationToken).ConfigureAwait(false);
  1470. _lastRecordingRefreshTime = DateTime.MinValue;
  1471. }
  1472. public async Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
  1473. {
  1474. var info = await _tvDtoService.GetSeriesTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
  1475. var service = GetService(timer.ServiceName);
  1476. await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
  1477. _lastRecordingRefreshTime = DateTime.MinValue;
  1478. }
  1479. private IEnumerable<string> GetRecordingGroupNames(ILiveTvRecording recording)
  1480. {
  1481. var list = new List<string>();
  1482. if (recording.IsSeries)
  1483. {
  1484. list.Add(recording.Name);
  1485. }
  1486. if (recording.IsKids)
  1487. {
  1488. list.Add("Kids");
  1489. }
  1490. if (recording.IsMovie)
  1491. {
  1492. list.Add("Movies");
  1493. }
  1494. if (recording.IsNews)
  1495. {
  1496. list.Add("News");
  1497. }
  1498. if (recording.IsSports)
  1499. {
  1500. list.Add("Sports");
  1501. }
  1502. if (!recording.IsSports && !recording.IsNews && !recording.IsMovie && !recording.IsKids && !recording.IsSeries)
  1503. {
  1504. list.Add("Others");
  1505. }
  1506. return list;
  1507. }
  1508. private List<Guid> GetRecordingGroupIds(ILiveTvRecording recording)
  1509. {
  1510. return GetRecordingGroupNames(recording).Select(i => i.ToLower()
  1511. .GetMD5())
  1512. .ToList();
  1513. }
  1514. public async Task<QueryResult<BaseItemDto>> GetRecordingGroups(RecordingGroupQuery query, CancellationToken cancellationToken)
  1515. {
  1516. var recordingResult = await GetInternalRecordings(new RecordingQuery
  1517. {
  1518. UserId = query.UserId
  1519. }, cancellationToken).ConfigureAwait(false);
  1520. var recordings = recordingResult.Items.Cast<ILiveTvRecording>().ToList();
  1521. var groups = new List<BaseItemDto>();
  1522. var series = recordings
  1523. .Where(i => i.IsSeries)
  1524. .ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase)
  1525. .ToList();
  1526. groups.AddRange(series.OrderByString(i => i.Key).Select(i => new BaseItemDto
  1527. {
  1528. Name = i.Key,
  1529. RecordingCount = i.Count()
  1530. }));
  1531. groups.Add(new BaseItemDto
  1532. {
  1533. Name = "Kids",
  1534. RecordingCount = recordings.Count(i => i.IsKids)
  1535. });
  1536. groups.Add(new BaseItemDto
  1537. {
  1538. Name = "Movies",
  1539. RecordingCount = recordings.Count(i => i.IsMovie)
  1540. });
  1541. groups.Add(new BaseItemDto
  1542. {
  1543. Name = "News",
  1544. RecordingCount = recordings.Count(i => i.IsNews)
  1545. });
  1546. groups.Add(new BaseItemDto
  1547. {
  1548. Name = "Sports",
  1549. RecordingCount = recordings.Count(i => i.IsSports)
  1550. });
  1551. groups.Add(new BaseItemDto
  1552. {
  1553. Name = "Others",
  1554. RecordingCount = recordings.Count(i => !i.IsSports && !i.IsNews && !i.IsMovie && !i.IsKids && !i.IsSeries)
  1555. });
  1556. groups = groups
  1557. .Where(i => i.RecordingCount > 0)
  1558. .ToList();
  1559. foreach (var group in groups)
  1560. {
  1561. group.Id = group.Name.ToLower().GetMD5().ToString("N");
  1562. }
  1563. return new QueryResult<BaseItemDto>
  1564. {
  1565. Items = groups.ToArray(),
  1566. TotalRecordCount = groups.Count
  1567. };
  1568. }
  1569. class LiveStreamData
  1570. {
  1571. internal MediaSourceInfo Info;
  1572. internal string ItemId;
  1573. internal bool IsChannel;
  1574. }
  1575. public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
  1576. {
  1577. await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
  1578. try
  1579. {
  1580. var parts = id.Split(new[] { '_' }, 2);
  1581. var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
  1582. if (service == null)
  1583. {
  1584. throw new ArgumentException("Service not found.");
  1585. }
  1586. id = parts[1];
  1587. LiveStreamData data;
  1588. _openStreams.TryRemove(id, out data);
  1589. _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id);
  1590. await service.CloseLiveStream(id, cancellationToken).ConfigureAwait(false);
  1591. }
  1592. catch (Exception ex)
  1593. {
  1594. _logger.ErrorException("Error closing live stream", ex);
  1595. throw;
  1596. }
  1597. finally
  1598. {
  1599. _liveStreamSemaphore.Release();
  1600. }
  1601. }
  1602. public GuideInfo GetGuideInfo()
  1603. {
  1604. var startDate = DateTime.UtcNow;
  1605. var endDate = startDate.AddDays(14);
  1606. return new GuideInfo
  1607. {
  1608. StartDate = startDate,
  1609. EndDate = endDate
  1610. };
  1611. }
  1612. /// <summary>
  1613. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  1614. /// </summary>
  1615. public void Dispose()
  1616. {
  1617. Dispose(true);
  1618. }
  1619. private readonly object _disposeLock = new object();
  1620. /// <summary>
  1621. /// Releases unmanaged and - optionally - managed resources.
  1622. /// </summary>
  1623. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  1624. protected virtual void Dispose(bool dispose)
  1625. {
  1626. if (dispose)
  1627. {
  1628. lock (_disposeLock)
  1629. {
  1630. foreach (var stream in _openStreams.Values.ToList())
  1631. {
  1632. var task = CloseLiveStream(stream.Info.Id, CancellationToken.None);
  1633. Task.WaitAll(task);
  1634. }
  1635. _openStreams.Clear();
  1636. }
  1637. }
  1638. }
  1639. private async Task<IEnumerable<LiveTvServiceInfo>> GetServiceInfos(CancellationToken cancellationToken)
  1640. {
  1641. var tasks = Services.Select(i => GetServiceInfo(i, cancellationToken));
  1642. return await Task.WhenAll(tasks).ConfigureAwait(false);
  1643. }
  1644. private async Task<LiveTvServiceInfo> GetServiceInfo(ILiveTvService service, CancellationToken cancellationToken)
  1645. {
  1646. var info = new LiveTvServiceInfo
  1647. {
  1648. Name = service.Name
  1649. };
  1650. var tunerIdPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
  1651. try
  1652. {
  1653. var statusInfo = await service.GetStatusInfoAsync(cancellationToken).ConfigureAwait(false);
  1654. info.Status = statusInfo.Status;
  1655. info.StatusMessage = statusInfo.StatusMessage;
  1656. info.Version = statusInfo.Version;
  1657. info.HasUpdateAvailable = statusInfo.HasUpdateAvailable;
  1658. info.HomePageUrl = service.HomePageUrl;
  1659. info.IsVisible = statusInfo.IsVisible;
  1660. info.Tuners = statusInfo.Tuners.Select(i =>
  1661. {
  1662. string channelName = null;
  1663. if (!string.IsNullOrEmpty(i.ChannelId))
  1664. {
  1665. var internalChannelId = _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId);
  1666. var channel = GetInternalChannel(internalChannelId);
  1667. channelName = channel == null ? null : channel.Name;
  1668. }
  1669. var dto = _tvDtoService.GetTunerInfoDto(service.Name, i, channelName);
  1670. dto.Id = tunerIdPrefix + dto.Id;
  1671. return dto;
  1672. }).ToList();
  1673. }
  1674. catch (Exception ex)
  1675. {
  1676. _logger.ErrorException("Error getting service status info from {0}", ex, service.Name ?? string.Empty);
  1677. info.Status = LiveTvServiceStatus.Unavailable;
  1678. info.StatusMessage = ex.Message;
  1679. }
  1680. return info;
  1681. }
  1682. public async Task<LiveTvInfo> GetLiveTvInfo(CancellationToken cancellationToken)
  1683. {
  1684. var services = await GetServiceInfos(CancellationToken.None).ConfigureAwait(false);
  1685. var servicesList = services.ToList();
  1686. var info = new LiveTvInfo
  1687. {
  1688. Services = servicesList.ToList(),
  1689. IsEnabled = servicesList.Count > 0
  1690. };
  1691. info.EnabledUsers = _userManager.Users
  1692. .Where(IsLiveTvEnabled)
  1693. .Select(i => i.Id.ToString("N"))
  1694. .ToList();
  1695. return info;
  1696. }
  1697. private bool IsLiveTvEnabled(User user)
  1698. {
  1699. return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count(i => i.IsEnabled) > 0);
  1700. }
  1701. public IEnumerable<User> GetEnabledUsers()
  1702. {
  1703. return _userManager.Users
  1704. .Where(IsLiveTvEnabled);
  1705. }
  1706. /// <summary>
  1707. /// Resets the tuner.
  1708. /// </summary>
  1709. /// <param name="id">The identifier.</param>
  1710. /// <param name="cancellationToken">The cancellation token.</param>
  1711. /// <returns>Task.</returns>
  1712. public Task ResetTuner(string id, CancellationToken cancellationToken)
  1713. {
  1714. var parts = id.Split(new[] { '_' }, 2);
  1715. var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
  1716. if (service == null)
  1717. {
  1718. throw new ArgumentException("Service not found.");
  1719. }
  1720. return service.ResetTuner(parts[1], cancellationToken);
  1721. }
  1722. public async Task<BaseItemDto> GetLiveTvFolder(string userId, CancellationToken cancellationToken)
  1723. {
  1724. var user = string.IsNullOrEmpty(userId) ? null : _userManager.GetUserById(userId);
  1725. var folder = await GetInternalLiveTvFolder(userId, cancellationToken).ConfigureAwait(false);
  1726. return _dtoService.GetBaseItemDto(folder, new DtoOptions(), user);
  1727. }
  1728. public async Task<Folder> GetInternalLiveTvFolder(string userId, CancellationToken cancellationToken)
  1729. {
  1730. var name = _localization.GetLocalizedString("ViewTypeLiveTV");
  1731. var user = _userManager.GetUserById(userId);
  1732. return await _libraryManager.GetNamedView(user, name, "livetv", "zz_" + name, cancellationToken).ConfigureAwait(false);
  1733. }
  1734. public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info)
  1735. {
  1736. info = (TunerHostInfo)_jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(info), typeof(TunerHostInfo));
  1737. var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
  1738. if (provider == null)
  1739. {
  1740. throw new ResourceNotFoundException();
  1741. }
  1742. await provider.Validate(info).ConfigureAwait(false);
  1743. var config = GetConfiguration();
  1744. var index = config.TunerHosts.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
  1745. if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
  1746. {
  1747. info.Id = Guid.NewGuid().ToString("N");
  1748. config.TunerHosts.Add(info);
  1749. }
  1750. else
  1751. {
  1752. config.TunerHosts[index] = info;
  1753. }
  1754. _config.SaveConfiguration("livetv", config);
  1755. _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
  1756. return info;
  1757. }
  1758. public async Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings)
  1759. {
  1760. info = (ListingsProviderInfo)_jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(info), typeof(ListingsProviderInfo));
  1761. var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
  1762. if (provider == null)
  1763. {
  1764. throw new ResourceNotFoundException();
  1765. }
  1766. await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false);
  1767. var config = GetConfiguration();
  1768. var index = config.ListingProviders.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
  1769. if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
  1770. {
  1771. info.Id = Guid.NewGuid().ToString("N");
  1772. config.ListingProviders.Add(info);
  1773. }
  1774. else
  1775. {
  1776. config.ListingProviders[index] = info;
  1777. }
  1778. _config.SaveConfiguration("livetv", config);
  1779. _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
  1780. return info;
  1781. }
  1782. public Task<List<NameIdPair>> GetLineups(string providerId, string country, string location)
  1783. {
  1784. var config = GetConfiguration();
  1785. var info = config.ListingProviders.FirstOrDefault(i => string.Equals(i.Id, providerId, StringComparison.OrdinalIgnoreCase));
  1786. var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
  1787. if (provider == null)
  1788. {
  1789. throw new ResourceNotFoundException();
  1790. }
  1791. return provider.GetLineups(info, country, location);
  1792. }
  1793. }
  1794. }