LiveTvManager.cs 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Common.IO;
  3. using MediaBrowser.Controller;
  4. using MediaBrowser.Controller.Configuration;
  5. using MediaBrowser.Controller.Drawing;
  6. using MediaBrowser.Controller.Dto;
  7. using MediaBrowser.Controller.Entities;
  8. using MediaBrowser.Controller.Library;
  9. using MediaBrowser.Controller.LiveTv;
  10. using MediaBrowser.Controller.MediaInfo;
  11. using MediaBrowser.Controller.Persistence;
  12. using MediaBrowser.Model.Entities;
  13. using MediaBrowser.Model.LiveTv;
  14. using MediaBrowser.Model.Logging;
  15. using MediaBrowser.Model.Querying;
  16. using System;
  17. using System.Collections.Concurrent;
  18. using System.Collections.Generic;
  19. using System.IO;
  20. using System.Linq;
  21. using System.Threading;
  22. using System.Threading.Tasks;
  23. namespace MediaBrowser.Server.Implementations.LiveTv
  24. {
  25. /// <summary>
  26. /// Class LiveTvManager
  27. /// </summary>
  28. public class LiveTvManager : ILiveTvManager, IDisposable
  29. {
  30. private readonly IServerConfigurationManager _config;
  31. private readonly IFileSystem _fileSystem;
  32. private readonly ILogger _logger;
  33. private readonly IItemRepository _itemRepo;
  34. private readonly IUserManager _userManager;
  35. private readonly IUserDataManager _userDataManager;
  36. private readonly ILibraryManager _libraryManager;
  37. private readonly IMediaEncoder _mediaEncoder;
  38. private readonly LiveTvDtoService _tvDtoService;
  39. private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
  40. private readonly ConcurrentDictionary<string, LiveStreamInfo> _openStreams =
  41. new ConcurrentDictionary<string, LiveStreamInfo>();
  42. private List<Guid> _channelIdList = new List<Guid>();
  43. private Dictionary<Guid, LiveTvProgram> _programs = new Dictionary<Guid, LiveTvProgram>();
  44. public LiveTvManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, IMediaEncoder mediaEncoder)
  45. {
  46. _config = config;
  47. _fileSystem = fileSystem;
  48. _logger = logger;
  49. _itemRepo = itemRepo;
  50. _userManager = userManager;
  51. _libraryManager = libraryManager;
  52. _mediaEncoder = mediaEncoder;
  53. _userDataManager = userDataManager;
  54. _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger, _itemRepo);
  55. }
  56. /// <summary>
  57. /// Gets the services.
  58. /// </summary>
  59. /// <value>The services.</value>
  60. public IReadOnlyList<ILiveTvService> Services
  61. {
  62. get { return _services; }
  63. }
  64. public ILiveTvService ActiveService { get; private set; }
  65. /// <summary>
  66. /// Adds the parts.
  67. /// </summary>
  68. /// <param name="services">The services.</param>
  69. public void AddParts(IEnumerable<ILiveTvService> services)
  70. {
  71. _services.AddRange(services);
  72. ActiveService = _services.FirstOrDefault();
  73. }
  74. public Task<QueryResult<ChannelInfoDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken)
  75. {
  76. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
  77. var channels = _channelIdList.Select(_libraryManager.GetItemById)
  78. .Where(i => i != null)
  79. .OfType<LiveTvChannel>();
  80. if (user != null)
  81. {
  82. // Avoid implicitly captured closure
  83. var currentUser = user;
  84. channels = channels
  85. .Where(i => i.IsParentalAllowed(currentUser))
  86. .OrderBy(i =>
  87. {
  88. double number = 0;
  89. if (!string.IsNullOrEmpty(i.ChannelInfo.Number))
  90. {
  91. double.TryParse(i.ChannelInfo.Number, out number);
  92. }
  93. return number;
  94. });
  95. }
  96. channels = channels.OrderBy(i =>
  97. {
  98. double number = 0;
  99. if (!string.IsNullOrEmpty(i.ChannelInfo.Number))
  100. {
  101. double.TryParse(i.ChannelInfo.Number, out number);
  102. }
  103. return number;
  104. }).ThenBy(i => i.Name);
  105. var allChannels = channels.ToList();
  106. IEnumerable<LiveTvChannel> allEnumerable = allChannels;
  107. if (query.StartIndex.HasValue)
  108. {
  109. allEnumerable = allEnumerable.Skip(query.StartIndex.Value);
  110. }
  111. if (query.Limit.HasValue)
  112. {
  113. allEnumerable = allEnumerable.Take(query.Limit.Value);
  114. }
  115. var returnChannels = allEnumerable
  116. .Select(i => _tvDtoService.GetChannelInfoDto(i, GetCurrentProgram(i.ChannelInfo.Id), user))
  117. .ToArray();
  118. var result = new QueryResult<ChannelInfoDto>
  119. {
  120. Items = returnChannels,
  121. TotalRecordCount = allChannels.Count
  122. };
  123. return Task.FromResult(result);
  124. }
  125. public LiveTvChannel GetInternalChannel(string id)
  126. {
  127. return GetInternalChannel(new Guid(id));
  128. }
  129. private LiveTvChannel GetInternalChannel(Guid id)
  130. {
  131. return _libraryManager.GetItemById(id) as LiveTvChannel;
  132. }
  133. public LiveTvProgram GetInternalProgram(string id)
  134. {
  135. var guid = new Guid(id);
  136. LiveTvProgram obj = null;
  137. _programs.TryGetValue(guid, out obj);
  138. return obj;
  139. }
  140. public async Task<ILiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken)
  141. {
  142. var service = ActiveService;
  143. var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
  144. var recording = recordings.FirstOrDefault(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id));
  145. return await GetRecording(recording, service.Name, cancellationToken).ConfigureAwait(false);
  146. }
  147. public async Task<LiveStreamInfo> GetRecordingStream(string id, CancellationToken cancellationToken)
  148. {
  149. var service = ActiveService;
  150. var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
  151. var recording = recordings.First(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id));
  152. var result = await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false);
  153. if (!string.IsNullOrEmpty(result.Id))
  154. {
  155. _openStreams.AddOrUpdate(result.Id, result, (key, info) => result);
  156. }
  157. return result;
  158. }
  159. public async Task<LiveStreamInfo> GetChannelStream(string id, CancellationToken cancellationToken)
  160. {
  161. var service = ActiveService;
  162. var channel = GetInternalChannel(id);
  163. var result = await service.GetChannelStream(channel.ChannelInfo.Id, cancellationToken).ConfigureAwait(false);
  164. if (!string.IsNullOrEmpty(result.Id))
  165. {
  166. _openStreams.AddOrUpdate(result.Id, result, (key, info) => result);
  167. }
  168. return result;
  169. }
  170. private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
  171. {
  172. var path = Path.Combine(_config.ApplicationPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(channelInfo.Name));
  173. var fileInfo = new DirectoryInfo(path);
  174. var isNew = false;
  175. if (!fileInfo.Exists)
  176. {
  177. Directory.CreateDirectory(path);
  178. fileInfo = new DirectoryInfo(path);
  179. if (!fileInfo.Exists)
  180. {
  181. throw new IOException("Path not created: " + path);
  182. }
  183. isNew = true;
  184. }
  185. var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id);
  186. var item = _itemRepo.RetrieveItem(id) as LiveTvChannel;
  187. if (item == null)
  188. {
  189. item = new LiveTvChannel
  190. {
  191. Name = channelInfo.Name,
  192. Id = id,
  193. DateCreated = _fileSystem.GetCreationTimeUtc(fileInfo),
  194. DateModified = _fileSystem.GetLastWriteTimeUtc(fileInfo),
  195. Path = path
  196. };
  197. isNew = true;
  198. }
  199. item.ChannelInfo = channelInfo;
  200. item.ServiceName = serviceName;
  201. // Set this now so we don't cause additional file system access during provider executions
  202. item.ResetResolveArgs(fileInfo);
  203. await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false);
  204. return item;
  205. }
  206. private async Task<LiveTvProgram> GetProgram(ProgramInfo info, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
  207. {
  208. var isNew = false;
  209. var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id);
  210. var item = _itemRepo.RetrieveItem(id) as LiveTvProgram;
  211. if (item == null)
  212. {
  213. item = new LiveTvProgram
  214. {
  215. Name = info.Name,
  216. Id = id,
  217. DateCreated = DateTime.UtcNow,
  218. DateModified = DateTime.UtcNow
  219. };
  220. isNew = true;
  221. }
  222. item.ChannelType = channelType;
  223. item.ProgramInfo = info;
  224. item.ServiceName = serviceName;
  225. await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false);
  226. return item;
  227. }
  228. private async Task<ILiveTvRecording> GetRecording(RecordingInfo info, string serviceName, CancellationToken cancellationToken)
  229. {
  230. var isNew = false;
  231. var id = _tvDtoService.GetInternalRecordingId(serviceName, info.Id);
  232. var item = _itemRepo.RetrieveItem(id) as ILiveTvRecording;
  233. if (item == null)
  234. {
  235. if (info.ChannelType == ChannelType.TV)
  236. {
  237. item = new LiveTvVideoRecording
  238. {
  239. Name = info.Name,
  240. Id = id,
  241. DateCreated = DateTime.UtcNow,
  242. DateModified = DateTime.UtcNow,
  243. VideoType = VideoType.VideoFile
  244. };
  245. }
  246. else
  247. {
  248. item = new LiveTvAudioRecording
  249. {
  250. Name = info.Name,
  251. Id = id,
  252. DateCreated = DateTime.UtcNow,
  253. DateModified = DateTime.UtcNow
  254. };
  255. }
  256. if (!string.IsNullOrEmpty(info.Path))
  257. {
  258. item.Path = info.Path;
  259. }
  260. else if (!string.IsNullOrEmpty(info.Url))
  261. {
  262. item.Path = info.Url;
  263. }
  264. isNew = true;
  265. }
  266. item.RecordingInfo = info;
  267. item.ServiceName = serviceName;
  268. await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false);
  269. _libraryManager.RegisterItem((BaseItem)item);
  270. return item;
  271. }
  272. private LiveTvChannel GetChannel(LiveTvProgram program)
  273. {
  274. var programChannelId = program.ProgramInfo.ChannelId;
  275. var internalProgramChannelId = _tvDtoService.GetInternalChannelId(program.ServiceName, programChannelId);
  276. return GetInternalChannel(internalProgramChannelId);
  277. }
  278. public async Task<ProgramInfoDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
  279. {
  280. var program = GetInternalProgram(id);
  281. var channel = GetChannel(program);
  282. var channelName = channel == null ? null : channel.ChannelInfo.Name;
  283. var dto = _tvDtoService.GetProgramInfoDto(program, channelName, user);
  284. await AddRecordingInfo(new[] { dto }, cancellationToken).ConfigureAwait(false);
  285. return dto;
  286. }
  287. public async Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken)
  288. {
  289. IEnumerable<LiveTvProgram> programs = _programs.Values;
  290. if (query.MinEndDate.HasValue)
  291. {
  292. var val = query.MinEndDate.Value;
  293. programs = programs.Where(i => i.ProgramInfo.EndDate >= val);
  294. }
  295. if (query.MinStartDate.HasValue)
  296. {
  297. var val = query.MinStartDate.Value;
  298. programs = programs.Where(i => i.ProgramInfo.StartDate >= val);
  299. }
  300. if (query.MaxEndDate.HasValue)
  301. {
  302. var val = query.MaxEndDate.Value;
  303. programs = programs.Where(i => i.ProgramInfo.EndDate <= val);
  304. }
  305. if (query.MaxStartDate.HasValue)
  306. {
  307. var val = query.MaxStartDate.Value;
  308. programs = programs.Where(i => i.ProgramInfo.StartDate <= val);
  309. }
  310. if (query.ChannelIdList.Length > 0)
  311. {
  312. var guids = query.ChannelIdList.Select(i => new Guid(i)).ToList();
  313. var serviceName = ActiveService.Name;
  314. programs = programs.Where(i =>
  315. {
  316. var programChannelId = i.ProgramInfo.ChannelId;
  317. var internalProgramChannelId = _tvDtoService.GetInternalChannelId(serviceName, programChannelId);
  318. return guids.Contains(internalProgramChannelId);
  319. });
  320. }
  321. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
  322. if (user != null)
  323. {
  324. // Avoid implicitly captured closure
  325. var currentUser = user;
  326. programs = programs.Where(i => i.IsParentalAllowed(currentUser));
  327. }
  328. var returnArray = programs
  329. .Select(i =>
  330. {
  331. var channel = GetChannel(i);
  332. var channelName = channel == null ? null : channel.ChannelInfo.Name;
  333. return _tvDtoService.GetProgramInfoDto(i, channelName, user);
  334. })
  335. .ToArray();
  336. await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false);
  337. var result = new QueryResult<ProgramInfoDto>
  338. {
  339. Items = returnArray,
  340. TotalRecordCount = returnArray.Length
  341. };
  342. return result;
  343. }
  344. public async Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query, CancellationToken cancellationToken)
  345. {
  346. IEnumerable<LiveTvProgram> programs = _programs.Values;
  347. var user = _userManager.GetUserById(new Guid(query.UserId));
  348. // Avoid implicitly captured closure
  349. var currentUser = user;
  350. programs = programs.Where(i => i.IsParentalAllowed(currentUser));
  351. if (query.IsAiring.HasValue)
  352. {
  353. var val = query.IsAiring.Value;
  354. programs = programs.Where(i => i.IsAiring == val);
  355. }
  356. if (query.HasAired.HasValue)
  357. {
  358. var val = query.HasAired.Value;
  359. programs = programs.Where(i => i.HasAired == val);
  360. }
  361. var serviceName = ActiveService.Name;
  362. var programList = programs.ToList();
  363. var genres = programList.SelectMany(i => i.Genres)
  364. .Distinct(StringComparer.OrdinalIgnoreCase)
  365. .Select(i => _libraryManager.GetGenre(i))
  366. .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
  367. programs = programList.OrderByDescending(i => GetRecommendationScore(i.ProgramInfo, user.Id, serviceName, genres))
  368. .ThenBy(i => i.ProgramInfo.StartDate);
  369. if (query.Limit.HasValue)
  370. {
  371. programs = programs.Take(query.Limit.Value)
  372. .OrderBy(i => i.ProgramInfo.StartDate);
  373. }
  374. var returnArray = programs
  375. .Select(i =>
  376. {
  377. var channel = GetChannel(i);
  378. var channelName = channel == null ? null : channel.ChannelInfo.Name;
  379. return _tvDtoService.GetProgramInfoDto(i, channelName, user);
  380. })
  381. .ToArray();
  382. await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false);
  383. var result = new QueryResult<ProgramInfoDto>
  384. {
  385. Items = returnArray,
  386. TotalRecordCount = returnArray.Length
  387. };
  388. return result;
  389. }
  390. private int GetRecommendationScore(ProgramInfo program, Guid userId, string serviceName, Dictionary<string, Genre> genres)
  391. {
  392. var score = 0;
  393. if (program.IsLive)
  394. {
  395. score++;
  396. }
  397. if (program.IsSeries && !program.IsRepeat)
  398. {
  399. score++;
  400. }
  401. var internalChannelId = _tvDtoService.GetInternalChannelId(serviceName, program.ChannelId);
  402. var channel = GetInternalChannel(internalChannelId);
  403. var channelUserdata = _userDataManager.GetUserData(userId, channel.GetUserDataKey());
  404. if ((channelUserdata.Likes ?? false))
  405. {
  406. score += 2;
  407. }
  408. else if (!(channelUserdata.Likes ?? true))
  409. {
  410. score -= 2;
  411. }
  412. if (channelUserdata.IsFavorite)
  413. {
  414. score += 3;
  415. }
  416. score += GetGenreScore(program.Genres, userId, genres);
  417. return score;
  418. }
  419. private int GetGenreScore(IEnumerable<string> programGenres, Guid userId, Dictionary<string, Genre> genres)
  420. {
  421. return programGenres.Select(i =>
  422. {
  423. var score = 0;
  424. Genre genre;
  425. if (genres.TryGetValue(i, out genre))
  426. {
  427. var genreUserdata = _userDataManager.GetUserData(userId, genre.GetUserDataKey());
  428. if ((genreUserdata.Likes ?? false))
  429. {
  430. score++;
  431. }
  432. else if (!(genreUserdata.Likes ?? true))
  433. {
  434. score--;
  435. }
  436. if (genreUserdata.IsFavorite)
  437. {
  438. score += 2;
  439. }
  440. }
  441. return score;
  442. }).Sum();
  443. }
  444. private async Task AddRecordingInfo(IEnumerable<ProgramInfoDto> programs, CancellationToken cancellationToken)
  445. {
  446. var timers = await ActiveService.GetTimersAsync(cancellationToken).ConfigureAwait(false);
  447. var timerList = timers.ToList();
  448. foreach (var program in programs)
  449. {
  450. var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, program.ExternalId, StringComparison.OrdinalIgnoreCase));
  451. if (timer != null)
  452. {
  453. program.TimerId = _tvDtoService.GetInternalTimerId(program.ServiceName, timer.Id)
  454. .ToString("N");
  455. if (!string.IsNullOrEmpty(timer.SeriesTimerId))
  456. {
  457. program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(program.ServiceName, timer.SeriesTimerId)
  458. .ToString("N");
  459. }
  460. }
  461. }
  462. }
  463. internal async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
  464. {
  465. // Avoid implicitly captured closure
  466. var service = ActiveService;
  467. if (service == null)
  468. {
  469. progress.Report(100);
  470. return;
  471. }
  472. progress.Report(10);
  473. var allChannels = await GetChannels(service, cancellationToken).ConfigureAwait(false);
  474. var allChannelsList = allChannels.ToList();
  475. var list = new List<LiveTvChannel>();
  476. var numComplete = 0;
  477. foreach (var channelInfo in allChannelsList)
  478. {
  479. try
  480. {
  481. var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, cancellationToken).ConfigureAwait(false);
  482. list.Add(item);
  483. _libraryManager.RegisterItem(item);
  484. }
  485. catch (OperationCanceledException)
  486. {
  487. throw;
  488. }
  489. catch (Exception ex)
  490. {
  491. _logger.ErrorException("Error getting channel information for {0}", ex, channelInfo.Item2.Name);
  492. }
  493. numComplete++;
  494. double percent = numComplete;
  495. percent /= allChannelsList.Count;
  496. progress.Report(5 * percent + 10);
  497. }
  498. _channelIdList = list.Select(i => i.Id).ToList();
  499. progress.Report(15);
  500. numComplete = 0;
  501. var programs = new List<LiveTvProgram>();
  502. var guideDays = GetGuideDays(list.Count);
  503. foreach (var item in list)
  504. {
  505. // Avoid implicitly captured closure
  506. var currentChannel = item;
  507. try
  508. {
  509. var start = DateTime.UtcNow.AddHours(-1);
  510. var end = start.AddDays(guideDays);
  511. var channelPrograms = await service.GetProgramsAsync(currentChannel.ChannelInfo.Id, start, end, cancellationToken).ConfigureAwait(false);
  512. var programTasks = channelPrograms.Select(program => GetProgram(program, currentChannel.ChannelInfo.ChannelType, service.Name, cancellationToken));
  513. var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false);
  514. programs.AddRange(programEntities);
  515. }
  516. catch (OperationCanceledException)
  517. {
  518. throw;
  519. }
  520. catch (Exception ex)
  521. {
  522. _logger.ErrorException("Error getting programs for channel {0}", ex, currentChannel.Name);
  523. }
  524. numComplete++;
  525. double percent = numComplete;
  526. percent /= allChannelsList.Count;
  527. progress.Report(90 * percent + 10);
  528. }
  529. _programs = programs.ToDictionary(i => i.Id);
  530. }
  531. private double GetGuideDays(int channelCount)
  532. {
  533. if (_config.Configuration.LiveTvOptions.GuideDays.HasValue)
  534. {
  535. return _config.Configuration.LiveTvOptions.GuideDays.Value;
  536. }
  537. var programsPerDay = channelCount * 48;
  538. const int maxPrograms = 32000;
  539. var days = Math.Round(((double)maxPrograms) / programsPerDay);
  540. // No less than 2, no more than 14
  541. return Math.Max(2, Math.Min(days, 14));
  542. }
  543. private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)
  544. {
  545. var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false);
  546. return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i));
  547. }
  548. public async Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken)
  549. {
  550. var service = ActiveService;
  551. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
  552. var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
  553. if (!string.IsNullOrEmpty(query.ChannelId))
  554. {
  555. var guid = new Guid(query.ChannelId);
  556. var currentServiceName = service.Name;
  557. recordings = recordings
  558. .Where(i => _tvDtoService.GetInternalChannelId(currentServiceName, i.ChannelId) == guid);
  559. }
  560. if (!string.IsNullOrEmpty(query.Id))
  561. {
  562. var guid = new Guid(query.Id);
  563. var currentServiceName = service.Name;
  564. recordings = recordings
  565. .Where(i => _tvDtoService.GetInternalRecordingId(currentServiceName, i.Id) == guid);
  566. }
  567. if (!string.IsNullOrEmpty(query.GroupId))
  568. {
  569. var guid = new Guid(query.GroupId);
  570. recordings = recordings.Where(i => GetRecordingGroupIds(i).Contains(guid));
  571. }
  572. if (query.IsRecording.HasValue)
  573. {
  574. var val = query.IsRecording.Value;
  575. recordings = recordings.Where(i => (i.Status == RecordingStatus.InProgress) == val);
  576. }
  577. if (!string.IsNullOrEmpty(query.SeriesTimerId))
  578. {
  579. var guid = new Guid(query.SeriesTimerId);
  580. var currentServiceName = service.Name;
  581. recordings = recordings
  582. .Where(i => _tvDtoService.GetInternalSeriesTimerId(currentServiceName, i.SeriesTimerId) == guid);
  583. }
  584. recordings = recordings.OrderByDescending(i => i.StartDate);
  585. IEnumerable<ILiveTvRecording> entities = await GetEntities(recordings, service.Name, cancellationToken).ConfigureAwait(false);
  586. if (user != null)
  587. {
  588. var currentUser = user;
  589. entities = entities.Where(i => i.IsParentalAllowed(currentUser));
  590. }
  591. if (query.StartIndex.HasValue)
  592. {
  593. entities = entities.Skip(query.StartIndex.Value);
  594. }
  595. if (query.Limit.HasValue)
  596. {
  597. entities = entities.Take(query.Limit.Value);
  598. }
  599. var returnArray = entities
  600. .Select(i =>
  601. {
  602. var channel = string.IsNullOrEmpty(i.RecordingInfo.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.RecordingInfo.ChannelId));
  603. return _tvDtoService.GetRecordingInfoDto(i, channel, service, user);
  604. })
  605. .ToArray();
  606. return new QueryResult<RecordingInfoDto>
  607. {
  608. Items = returnArray,
  609. TotalRecordCount = returnArray.Length
  610. };
  611. }
  612. private Task<ILiveTvRecording[]> GetEntities(IEnumerable<RecordingInfo> recordings, string serviceName, CancellationToken cancellationToken)
  613. {
  614. var tasks = recordings.Select(i => GetRecording(i, serviceName, cancellationToken));
  615. return Task.WhenAll(tasks);
  616. }
  617. private IEnumerable<ILiveTvService> GetServices(string serviceName, string channelId)
  618. {
  619. IEnumerable<ILiveTvService> services = _services;
  620. if (string.IsNullOrEmpty(serviceName) && !string.IsNullOrEmpty(channelId))
  621. {
  622. var channel = GetInternalChannel(channelId);
  623. if (channel != null)
  624. {
  625. serviceName = channel.ServiceName;
  626. }
  627. }
  628. if (!string.IsNullOrEmpty(serviceName))
  629. {
  630. services = services.Where(i => string.Equals(i.Name, serviceName, StringComparison.OrdinalIgnoreCase));
  631. }
  632. return services;
  633. }
  634. public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
  635. {
  636. var service = ActiveService;
  637. var timers = await service.GetTimersAsync(cancellationToken).ConfigureAwait(false);
  638. if (!string.IsNullOrEmpty(query.ChannelId))
  639. {
  640. var guid = new Guid(query.ChannelId);
  641. timers = timers.Where(i => guid == _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId));
  642. }
  643. if (!string.IsNullOrEmpty(query.SeriesTimerId))
  644. {
  645. var guid = new Guid(query.SeriesTimerId);
  646. var currentServiceName = service.Name;
  647. timers = timers
  648. .Where(i => _tvDtoService.GetInternalSeriesTimerId(currentServiceName, i.SeriesTimerId) == guid);
  649. }
  650. var returnArray = timers
  651. .Select(i =>
  652. {
  653. var program = string.IsNullOrEmpty(i.ProgramId) ? null : GetInternalProgram(_tvDtoService.GetInternalProgramId(service.Name, i.ProgramId).ToString("N"));
  654. var channel = string.IsNullOrEmpty(i.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.ChannelId));
  655. return _tvDtoService.GetTimerInfoDto(i, service, program, channel);
  656. })
  657. .OrderBy(i => i.StartDate)
  658. .ToArray();
  659. return new QueryResult<TimerInfoDto>
  660. {
  661. Items = returnArray,
  662. TotalRecordCount = returnArray.Length
  663. };
  664. }
  665. public async Task DeleteRecording(string recordingId)
  666. {
  667. var recording = await GetRecording(recordingId, CancellationToken.None).ConfigureAwait(false);
  668. if (recording == null)
  669. {
  670. throw new ResourceNotFoundException(string.Format("Recording with Id {0} not found", recordingId));
  671. }
  672. var service = GetServices(recording.ServiceName, null)
  673. .First();
  674. await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false);
  675. }
  676. public async Task CancelTimer(string id)
  677. {
  678. var timer = await GetTimer(id, CancellationToken.None).ConfigureAwait(false);
  679. if (timer == null)
  680. {
  681. throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
  682. }
  683. var service = GetServices(timer.ServiceName, null)
  684. .First();
  685. await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
  686. }
  687. public async Task CancelSeriesTimer(string id)
  688. {
  689. var timer = await GetSeriesTimer(id, CancellationToken.None).ConfigureAwait(false);
  690. if (timer == null)
  691. {
  692. throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
  693. }
  694. var service = GetServices(timer.ServiceName, null)
  695. .First();
  696. await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
  697. }
  698. public async Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken, User user = null)
  699. {
  700. var results = await GetRecordings(new RecordingQuery
  701. {
  702. UserId = user == null ? null : user.Id.ToString("N"),
  703. Id = id
  704. }, cancellationToken).ConfigureAwait(false);
  705. return results.Items.FirstOrDefault();
  706. }
  707. public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
  708. {
  709. var results = await GetTimers(new TimerQuery(), cancellationToken).ConfigureAwait(false);
  710. return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
  711. }
  712. public async Task<SeriesTimerInfoDto> GetSeriesTimer(string id, CancellationToken cancellationToken)
  713. {
  714. var results = await GetSeriesTimers(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false);
  715. return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
  716. }
  717. public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
  718. {
  719. var service = ActiveService;
  720. var timers = await service.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
  721. if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
  722. {
  723. timers = query.SortOrder == SortOrder.Descending ?
  724. timers.OrderBy(i => i.Priority).ThenByDescending(i => i.Name) :
  725. timers.OrderByDescending(i => i.Priority).ThenBy(i => i.Name);
  726. }
  727. else
  728. {
  729. timers = query.SortOrder == SortOrder.Descending ?
  730. timers.OrderByDescending(i => i.Name) :
  731. timers.OrderBy(i => i.Name);
  732. }
  733. var returnArray = timers
  734. .Select(i =>
  735. {
  736. string channelName = null;
  737. if (!string.IsNullOrEmpty(i.ChannelId))
  738. {
  739. var internalChannelId = _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId);
  740. var channel = GetInternalChannel(internalChannelId);
  741. channelName = channel == null ? null : channel.ChannelInfo.Name;
  742. }
  743. return _tvDtoService.GetSeriesTimerInfoDto(i, service, channelName);
  744. })
  745. .ToArray();
  746. return new QueryResult<SeriesTimerInfoDto>
  747. {
  748. Items = returnArray,
  749. TotalRecordCount = returnArray.Length
  750. };
  751. }
  752. public Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null)
  753. {
  754. var channel = GetInternalChannel(id);
  755. var dto = _tvDtoService.GetChannelInfoDto(channel, GetCurrentProgram(channel.ChannelInfo.Id), user);
  756. return Task.FromResult(dto);
  757. }
  758. private LiveTvProgram GetCurrentProgram(string externalChannelId)
  759. {
  760. var now = DateTime.UtcNow;
  761. return _programs.Values
  762. .Where(i => string.Equals(externalChannelId, i.ProgramInfo.ChannelId, StringComparison.OrdinalIgnoreCase))
  763. .OrderBy(i => i.ProgramInfo.StartDate)
  764. .SkipWhile(i => now >= i.ProgramInfo.EndDate)
  765. .FirstOrDefault();
  766. }
  767. public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
  768. {
  769. var service = ActiveService;
  770. var info = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
  771. var obj = _tvDtoService.GetSeriesTimerInfoDto(info, service, null);
  772. obj.Id = obj.ExternalId = string.Empty;
  773. return obj;
  774. }
  775. public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
  776. {
  777. var info = await GetNewTimerDefaults(cancellationToken).ConfigureAwait(false);
  778. var program = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
  779. info.Days = new List<DayOfWeek>
  780. {
  781. program.StartDate.ToLocalTime().DayOfWeek
  782. };
  783. info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
  784. info.Name = program.Name;
  785. info.ChannelId = program.ChannelId;
  786. info.ChannelName = program.ChannelName;
  787. info.EndDate = program.EndDate;
  788. info.StartDate = program.StartDate;
  789. info.Name = program.Name;
  790. info.Overview = program.Overview;
  791. info.ProgramId = program.Id;
  792. info.ExternalProgramId = program.ExternalId;
  793. return info;
  794. }
  795. public async Task CreateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
  796. {
  797. var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
  798. var info = await _tvDtoService.GetTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
  799. // Set priority from default values
  800. var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
  801. info.Priority = defaultValues.Priority;
  802. await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
  803. }
  804. public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
  805. {
  806. var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
  807. var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
  808. // Set priority from default values
  809. var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
  810. info.Priority = defaultValues.Priority;
  811. await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
  812. }
  813. public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
  814. {
  815. var info = await _tvDtoService.GetTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
  816. var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
  817. await service.UpdateTimerAsync(info, cancellationToken).ConfigureAwait(false);
  818. }
  819. public async Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
  820. {
  821. var info = await _tvDtoService.GetSeriesTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
  822. var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
  823. await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
  824. }
  825. private List<string> GetRecordingGroupNames(RecordingInfo recording)
  826. {
  827. var list = new List<string>();
  828. if (recording.IsSeries)
  829. {
  830. list.Add(recording.Name);
  831. }
  832. if (recording.IsKids)
  833. {
  834. list.Add("Kids");
  835. }
  836. if (recording.IsMovie)
  837. {
  838. list.Add("Movies");
  839. }
  840. if (recording.IsNews)
  841. {
  842. list.Add("News");
  843. }
  844. if (recording.IsSports)
  845. {
  846. list.Add("Sports");
  847. }
  848. if (!recording.IsSports && !recording.IsNews && !recording.IsMovie && !recording.IsKids && !recording.IsSeries)
  849. {
  850. list.Add("Others");
  851. }
  852. return list;
  853. }
  854. private List<Guid> GetRecordingGroupIds(RecordingInfo recording)
  855. {
  856. return GetRecordingGroupNames(recording).Select(i => i.ToLower()
  857. .GetMD5())
  858. .ToList();
  859. }
  860. public async Task<QueryResult<RecordingGroupDto>> GetRecordingGroups(RecordingGroupQuery query, CancellationToken cancellationToken)
  861. {
  862. var recordingResult = await GetRecordings(new RecordingQuery
  863. {
  864. UserId = query.UserId
  865. }, cancellationToken).ConfigureAwait(false);
  866. var recordings = recordingResult.Items;
  867. var groups = new List<RecordingGroupDto>();
  868. var series = recordings
  869. .Where(i => i.IsSeries)
  870. .ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase)
  871. .ToList();
  872. groups.AddRange(series.OrderBy(i => i.Key).Select(i => new RecordingGroupDto
  873. {
  874. Name = i.Key,
  875. RecordingCount = i.Count()
  876. }));
  877. groups.Add(new RecordingGroupDto
  878. {
  879. Name = "Kids",
  880. RecordingCount = recordings.Count(i => i.IsKids)
  881. });
  882. groups.Add(new RecordingGroupDto
  883. {
  884. Name = "Movies",
  885. RecordingCount = recordings.Count(i => i.IsMovie)
  886. });
  887. groups.Add(new RecordingGroupDto
  888. {
  889. Name = "News",
  890. RecordingCount = recordings.Count(i => i.IsNews)
  891. });
  892. groups.Add(new RecordingGroupDto
  893. {
  894. Name = "Sports",
  895. RecordingCount = recordings.Count(i => i.IsSports)
  896. });
  897. groups.Add(new RecordingGroupDto
  898. {
  899. Name = "Others",
  900. RecordingCount = recordings.Count(i => !i.IsSports && !i.IsNews && !i.IsMovie && !i.IsKids && !i.IsSeries)
  901. });
  902. groups = groups
  903. .Where(i => i.RecordingCount > 0)
  904. .ToList();
  905. foreach (var group in groups)
  906. {
  907. group.Id = group.Name.ToLower().GetMD5().ToString("N");
  908. }
  909. return new QueryResult<RecordingGroupDto>
  910. {
  911. Items = groups.ToArray(),
  912. TotalRecordCount = groups.Count
  913. };
  914. }
  915. public Task CloseLiveStream(string id, CancellationToken cancellationToken)
  916. {
  917. return ActiveService.CloseLiveStream(id, cancellationToken);
  918. }
  919. public GuideInfo GetGuideInfo()
  920. {
  921. var programs = _programs.ToList();
  922. var startDate = programs.Select(i => i.Value.ProgramInfo.StartDate).Min();
  923. var endDate = programs.Select(i => i.Value.ProgramInfo.StartDate).Max();
  924. return new GuideInfo
  925. {
  926. StartDate = startDate,
  927. EndDate = endDate
  928. };
  929. }
  930. /// <summary>
  931. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  932. /// </summary>
  933. public void Dispose()
  934. {
  935. Dispose(true);
  936. }
  937. private readonly object _disposeLock = new object();
  938. /// <summary>
  939. /// Releases unmanaged and - optionally - managed resources.
  940. /// </summary>
  941. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  942. protected virtual void Dispose(bool dispose)
  943. {
  944. if (dispose)
  945. {
  946. lock (_disposeLock)
  947. {
  948. foreach (var stream in _openStreams.Values.ToList())
  949. {
  950. var task = CloseLiveStream(stream.Id, CancellationToken.None);
  951. Task.WaitAll(task);
  952. }
  953. _openStreams.Clear();
  954. }
  955. }
  956. }
  957. }
  958. }