LiveTvManager.cs 106 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915
  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 MoreLinq;
  23. using System;
  24. using System.Collections.Concurrent;
  25. using System.Collections.Generic;
  26. using System.IO;
  27. using System.Linq;
  28. using System.Threading;
  29. using System.Threading.Tasks;
  30. using CommonIO;
  31. using IniParser;
  32. using IniParser.Model;
  33. using MediaBrowser.Common.Events;
  34. using MediaBrowser.Common.Security;
  35. using MediaBrowser.Controller.Entities.Movies;
  36. using MediaBrowser.Controller.Entities.TV;
  37. using MediaBrowser.Model.Events;
  38. using MediaBrowser.Server.Implementations.LiveTv.Listings;
  39. namespace MediaBrowser.Server.Implementations.LiveTv
  40. {
  41. /// <summary>
  42. /// Class LiveTvManager
  43. /// </summary>
  44. public class LiveTvManager : ILiveTvManager, IDisposable
  45. {
  46. private readonly IServerConfigurationManager _config;
  47. private readonly ILogger _logger;
  48. private readonly IItemRepository _itemRepo;
  49. private readonly IUserManager _userManager;
  50. private readonly IUserDataManager _userDataManager;
  51. private readonly ILibraryManager _libraryManager;
  52. private readonly ITaskManager _taskManager;
  53. private readonly IJsonSerializer _jsonSerializer;
  54. private readonly IProviderManager _providerManager;
  55. private readonly ISecurityManager _security;
  56. private readonly IDtoService _dtoService;
  57. private readonly ILocalizationManager _localization;
  58. private readonly LiveTvDtoService _tvDtoService;
  59. private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
  60. private readonly ConcurrentDictionary<string, LiveStreamData> _openStreams =
  61. new ConcurrentDictionary<string, LiveStreamData>();
  62. private readonly SemaphoreSlim _refreshRecordingsLock = new SemaphoreSlim(1, 1);
  63. private readonly List<ITunerHost> _tunerHosts = new List<ITunerHost>();
  64. private readonly List<IListingsProvider> _listingProviders = new List<IListingsProvider>();
  65. private readonly IFileSystem _fileSystem;
  66. public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
  67. public event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
  68. public event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;
  69. public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCreated;
  70. 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, IFileSystem fileSystem, ISecurityManager security)
  71. {
  72. _config = config;
  73. _logger = logger;
  74. _itemRepo = itemRepo;
  75. _userManager = userManager;
  76. _libraryManager = libraryManager;
  77. _taskManager = taskManager;
  78. _localization = localization;
  79. _jsonSerializer = jsonSerializer;
  80. _providerManager = providerManager;
  81. _fileSystem = fileSystem;
  82. _security = security;
  83. _dtoService = dtoService;
  84. _userDataManager = userDataManager;
  85. _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger, appHost, _libraryManager);
  86. }
  87. /// <summary>
  88. /// Gets the services.
  89. /// </summary>
  90. /// <value>The services.</value>
  91. public IReadOnlyList<ILiveTvService> Services
  92. {
  93. get { return _services; }
  94. }
  95. private LiveTvOptions GetConfiguration()
  96. {
  97. return _config.GetConfiguration<LiveTvOptions>("livetv");
  98. }
  99. /// <summary>
  100. /// Adds the parts.
  101. /// </summary>
  102. /// <param name="services">The services.</param>
  103. /// <param name="tunerHosts">The tuner hosts.</param>
  104. /// <param name="listingProviders">The listing providers.</param>
  105. public void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders)
  106. {
  107. _services.AddRange(services);
  108. _tunerHosts.AddRange(tunerHosts);
  109. _listingProviders.AddRange(listingProviders);
  110. foreach (var service in _services)
  111. {
  112. service.DataSourceChanged += service_DataSourceChanged;
  113. }
  114. }
  115. public List<ITunerHost> TunerHosts
  116. {
  117. get { return _tunerHosts; }
  118. }
  119. public List<IListingsProvider> ListingProviders
  120. {
  121. get { return _listingProviders; }
  122. }
  123. void service_DataSourceChanged(object sender, EventArgs e)
  124. {
  125. if (!_isDisposed)
  126. {
  127. _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
  128. }
  129. }
  130. public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, CancellationToken cancellationToken)
  131. {
  132. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
  133. var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
  134. var channels = _libraryManager.GetItemList(new InternalItemsQuery
  135. {
  136. IncludeItemTypes = new[] { typeof(LiveTvChannel).Name },
  137. SortBy = new[] { ItemSortBy.SortName },
  138. TopParentIds = new[] { topFolder.Id.ToString("N") }
  139. }).Cast<LiveTvChannel>();
  140. if (user != null)
  141. {
  142. // Avoid implicitly captured closure
  143. var currentUser = user;
  144. channels = channels
  145. .Where(i => i.IsVisible(currentUser))
  146. .OrderBy(i =>
  147. {
  148. double number = 0;
  149. if (!string.IsNullOrEmpty(i.Number))
  150. {
  151. double.TryParse(i.Number, out number);
  152. }
  153. return number;
  154. });
  155. if (query.IsFavorite.HasValue)
  156. {
  157. var val = query.IsFavorite.Value;
  158. channels = channels
  159. .Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val);
  160. }
  161. if (query.IsLiked.HasValue)
  162. {
  163. var val = query.IsLiked.Value;
  164. channels = channels
  165. .Where(i =>
  166. {
  167. var likes = _userDataManager.GetUserData(user, i).Likes;
  168. return likes.HasValue && likes.Value == val;
  169. });
  170. }
  171. if (query.IsDisliked.HasValue)
  172. {
  173. var val = query.IsDisliked.Value;
  174. channels = channels
  175. .Where(i =>
  176. {
  177. var likes = _userDataManager.GetUserData(user, i).Likes;
  178. return likes.HasValue && likes.Value != val;
  179. });
  180. }
  181. }
  182. var enableFavoriteSorting = query.EnableFavoriteSorting;
  183. channels = channels.OrderBy(i =>
  184. {
  185. if (enableFavoriteSorting)
  186. {
  187. var userData = _userDataManager.GetUserData(user, i);
  188. if (userData.IsFavorite)
  189. {
  190. return 0;
  191. }
  192. if (userData.Likes.HasValue)
  193. {
  194. if (!userData.Likes.Value)
  195. {
  196. return 3;
  197. }
  198. return 1;
  199. }
  200. }
  201. return 2;
  202. });
  203. var allChannels = channels.ToList();
  204. IEnumerable<LiveTvChannel> allEnumerable = allChannels;
  205. if (query.StartIndex.HasValue)
  206. {
  207. allEnumerable = allEnumerable.Skip(query.StartIndex.Value);
  208. }
  209. if (query.Limit.HasValue)
  210. {
  211. allEnumerable = allEnumerable.Take(query.Limit.Value);
  212. }
  213. var result = new QueryResult<LiveTvChannel>
  214. {
  215. Items = allEnumerable.ToArray(),
  216. TotalRecordCount = allChannels.Count
  217. };
  218. return result;
  219. }
  220. public LiveTvChannel GetInternalChannel(string id)
  221. {
  222. return GetInternalChannel(new Guid(id));
  223. }
  224. private LiveTvChannel GetInternalChannel(Guid id)
  225. {
  226. return _libraryManager.GetItemById(id) as LiveTvChannel;
  227. }
  228. internal LiveTvProgram GetInternalProgram(string id)
  229. {
  230. return _libraryManager.GetItemById(id) as LiveTvProgram;
  231. }
  232. internal LiveTvProgram GetInternalProgram(Guid id)
  233. {
  234. return _libraryManager.GetItemById(id) as LiveTvProgram;
  235. }
  236. public async Task<BaseItem> GetInternalRecording(string id, CancellationToken cancellationToken)
  237. {
  238. if (string.IsNullOrWhiteSpace(id))
  239. {
  240. throw new ArgumentNullException("id");
  241. }
  242. var result = await GetInternalRecordings(new RecordingQuery
  243. {
  244. Id = id
  245. }, cancellationToken).ConfigureAwait(false);
  246. return result.Items.FirstOrDefault();
  247. }
  248. private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
  249. public async Task<MediaSourceInfo> GetRecordingStream(string id, CancellationToken cancellationToken)
  250. {
  251. return await GetLiveStream(id, null, false, cancellationToken).ConfigureAwait(false);
  252. }
  253. public async Task<MediaSourceInfo> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken)
  254. {
  255. return await GetLiveStream(id, mediaSourceId, true, cancellationToken).ConfigureAwait(false);
  256. }
  257. public async Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(string id, CancellationToken cancellationToken)
  258. {
  259. var item = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
  260. var service = GetService(item);
  261. return await service.GetRecordingStreamMediaSources(id, cancellationToken).ConfigureAwait(false);
  262. }
  263. public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken)
  264. {
  265. var item = GetInternalChannel(id);
  266. var service = GetService(item);
  267. var sources = await service.GetChannelStreamMediaSources(item.ExternalId, cancellationToken).ConfigureAwait(false);
  268. if (sources.Count == 0)
  269. {
  270. throw new NotImplementedException();
  271. }
  272. var list = sources.ToList();
  273. foreach (var source in list)
  274. {
  275. Normalize(source, service, item.ChannelType == ChannelType.TV);
  276. }
  277. return list;
  278. }
  279. private ILiveTvService GetService(ILiveTvRecording item)
  280. {
  281. return GetService(item.ServiceName);
  282. }
  283. private ILiveTvService GetService(BaseItem item)
  284. {
  285. return GetService(item.ServiceName);
  286. }
  287. private ILiveTvService GetService(string name)
  288. {
  289. return _services.FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
  290. }
  291. private async Task<MediaSourceInfo> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken)
  292. {
  293. await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
  294. if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
  295. {
  296. mediaSourceId = null;
  297. }
  298. try
  299. {
  300. MediaSourceInfo info;
  301. bool isVideo;
  302. ILiveTvService service;
  303. if (isChannel)
  304. {
  305. var channel = GetInternalChannel(id);
  306. isVideo = channel.ChannelType == ChannelType.TV;
  307. service = GetService(channel);
  308. _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
  309. info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
  310. info.RequiresClosing = true;
  311. if (info.RequiresClosing)
  312. {
  313. var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
  314. info.LiveStreamId = idPrefix + info.Id;
  315. }
  316. }
  317. else
  318. {
  319. var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
  320. isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
  321. service = GetService(recording);
  322. _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId);
  323. info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false);
  324. info.RequiresClosing = true;
  325. if (info.RequiresClosing)
  326. {
  327. var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
  328. info.LiveStreamId = idPrefix + info.Id;
  329. }
  330. }
  331. _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
  332. Normalize(info, service, isVideo);
  333. var data = new LiveStreamData
  334. {
  335. Info = info,
  336. IsChannel = isChannel,
  337. ItemId = id
  338. };
  339. _openStreams.AddOrUpdate(info.Id, data, (key, i) => data);
  340. return info;
  341. }
  342. catch (Exception ex)
  343. {
  344. _logger.ErrorException("Error getting channel stream", ex);
  345. throw;
  346. }
  347. finally
  348. {
  349. _liveStreamSemaphore.Release();
  350. }
  351. }
  352. private void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo)
  353. {
  354. if (mediaSource.MediaStreams.Count == 0)
  355. {
  356. if (isVideo)
  357. {
  358. mediaSource.MediaStreams.AddRange(new List<MediaStream>
  359. {
  360. new MediaStream
  361. {
  362. Type = MediaStreamType.Video,
  363. // Set the index to -1 because we don't know the exact index of the video stream within the container
  364. Index = -1,
  365. // Set to true if unknown to enable deinterlacing
  366. IsInterlaced = true
  367. },
  368. new MediaStream
  369. {
  370. Type = MediaStreamType.Audio,
  371. // Set the index to -1 because we don't know the exact index of the audio stream within the container
  372. Index = -1
  373. }
  374. });
  375. }
  376. else
  377. {
  378. mediaSource.MediaStreams.AddRange(new List<MediaStream>
  379. {
  380. new MediaStream
  381. {
  382. Type = MediaStreamType.Audio,
  383. // Set the index to -1 because we don't know the exact index of the audio stream within the container
  384. Index = -1
  385. }
  386. });
  387. }
  388. }
  389. // Clean some bad data coming from providers
  390. foreach (var stream in mediaSource.MediaStreams)
  391. {
  392. if (stream.BitRate.HasValue && stream.BitRate <= 0)
  393. {
  394. stream.BitRate = null;
  395. }
  396. if (stream.Channels.HasValue && stream.Channels <= 0)
  397. {
  398. stream.Channels = null;
  399. }
  400. if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0)
  401. {
  402. stream.AverageFrameRate = null;
  403. }
  404. if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0)
  405. {
  406. stream.RealFrameRate = null;
  407. }
  408. if (stream.Width.HasValue && stream.Width <= 0)
  409. {
  410. stream.Width = null;
  411. }
  412. if (stream.Height.HasValue && stream.Height <= 0)
  413. {
  414. stream.Height = null;
  415. }
  416. if (stream.SampleRate.HasValue && stream.SampleRate <= 0)
  417. {
  418. stream.SampleRate = null;
  419. }
  420. if (stream.Level.HasValue && stream.Level <= 0)
  421. {
  422. stream.Level = null;
  423. }
  424. }
  425. var indexes = mediaSource.MediaStreams.Select(i => i.Index).Distinct().ToList();
  426. // If there are duplicate stream indexes, set them all to unknown
  427. if (indexes.Count != mediaSource.MediaStreams.Count)
  428. {
  429. foreach (var stream in mediaSource.MediaStreams)
  430. {
  431. stream.Index = -1;
  432. }
  433. }
  434. // Set the total bitrate if not already supplied
  435. if (!mediaSource.Bitrate.HasValue)
  436. {
  437. var total = mediaSource.MediaStreams.Select(i => i.BitRate ?? 0).Sum();
  438. if (total > 0)
  439. {
  440. mediaSource.Bitrate = total;
  441. }
  442. }
  443. if (!(service is EmbyTV.EmbyTV))
  444. {
  445. // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
  446. mediaSource.SupportsDirectStream = false;
  447. mediaSource.SupportsTranscoding = true;
  448. foreach (var stream in mediaSource.MediaStreams)
  449. {
  450. if (stream.Type == MediaStreamType.Video && string.IsNullOrWhiteSpace(stream.NalLengthSize))
  451. {
  452. stream.NalLengthSize = "0";
  453. }
  454. }
  455. }
  456. }
  457. private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, Guid parentFolderId, CancellationToken cancellationToken)
  458. {
  459. var isNew = false;
  460. var forceUpdate = false;
  461. var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id);
  462. var item = _itemRepo.RetrieveItem(id) as LiveTvChannel;
  463. if (item == null)
  464. {
  465. item = new LiveTvChannel
  466. {
  467. Name = channelInfo.Name,
  468. Id = id,
  469. DateCreated = DateTime.UtcNow,
  470. };
  471. isNew = true;
  472. }
  473. if (!string.Equals(channelInfo.Id, item.ExternalId))
  474. {
  475. isNew = true;
  476. }
  477. item.ExternalId = channelInfo.Id;
  478. if (!item.ParentId.Equals(parentFolderId))
  479. {
  480. isNew = true;
  481. }
  482. item.ParentId = parentFolderId;
  483. item.ChannelType = channelInfo.ChannelType;
  484. item.ServiceName = serviceName;
  485. item.Number = channelInfo.Number;
  486. //if (!string.Equals(item.ProviderImageUrl, channelInfo.ImageUrl, StringComparison.OrdinalIgnoreCase))
  487. //{
  488. // isNew = true;
  489. // replaceImages.Add(ImageType.Primary);
  490. //}
  491. //if (!string.Equals(item.ProviderImagePath, channelInfo.ImagePath, StringComparison.OrdinalIgnoreCase))
  492. //{
  493. // isNew = true;
  494. // replaceImages.Add(ImageType.Primary);
  495. //}
  496. if (!item.HasImage(ImageType.Primary))
  497. {
  498. if (!string.IsNullOrWhiteSpace(channelInfo.ImagePath))
  499. {
  500. item.SetImagePath(ImageType.Primary, channelInfo.ImagePath);
  501. forceUpdate = true;
  502. }
  503. else if (!string.IsNullOrWhiteSpace(channelInfo.ImageUrl))
  504. {
  505. item.SetImagePath(ImageType.Primary, channelInfo.ImageUrl);
  506. forceUpdate = true;
  507. }
  508. }
  509. if (string.IsNullOrEmpty(item.Name))
  510. {
  511. item.Name = channelInfo.Name;
  512. }
  513. if (isNew)
  514. {
  515. await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
  516. }
  517. else if (forceUpdate)
  518. {
  519. await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
  520. }
  521. await item.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
  522. {
  523. ForceSave = isNew || forceUpdate
  524. }, cancellationToken);
  525. return item;
  526. }
  527. private async Task<LiveTvProgram> GetProgram(ProgramInfo info, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
  528. {
  529. var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id);
  530. var item = _libraryManager.GetItemById(id) as LiveTvProgram;
  531. var isNew = false;
  532. var forceUpdate = false;
  533. if (item == null)
  534. {
  535. isNew = true;
  536. item = new LiveTvProgram
  537. {
  538. Name = info.Name,
  539. Id = id,
  540. DateCreated = DateTime.UtcNow,
  541. DateModified = DateTime.UtcNow,
  542. ExternalEtag = info.Etag
  543. };
  544. }
  545. if (!item.ParentId.Equals(channel.Id))
  546. {
  547. forceUpdate = true;
  548. }
  549. item.ParentId = channel.Id;
  550. //item.ChannelType = channelType;
  551. if (!string.Equals(item.ServiceName, serviceName, StringComparison.Ordinal))
  552. {
  553. forceUpdate = true;
  554. }
  555. item.ServiceName = serviceName;
  556. item.Audio = info.Audio;
  557. item.ChannelId = channel.Id.ToString("N");
  558. item.CommunityRating = item.CommunityRating ?? info.CommunityRating;
  559. item.EpisodeTitle = info.EpisodeTitle;
  560. item.ExternalId = info.Id;
  561. item.ExternalSeriesId = info.SeriesId;
  562. item.Genres = info.Genres;
  563. item.IsHD = info.IsHD;
  564. item.IsKids = info.IsKids;
  565. item.IsLive = info.IsLive;
  566. item.IsMovie = info.IsMovie;
  567. item.IsNews = info.IsNews;
  568. item.IsPremiere = info.IsPremiere;
  569. item.IsRepeat = info.IsRepeat;
  570. item.IsSeries = info.IsSeries;
  571. item.IsSports = info.IsSports;
  572. item.Name = info.Name;
  573. item.OfficialRating = item.OfficialRating ?? info.OfficialRating;
  574. item.Overview = item.Overview ?? info.Overview;
  575. item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
  576. if (item.StartDate != info.StartDate)
  577. {
  578. forceUpdate = true;
  579. }
  580. item.StartDate = info.StartDate;
  581. if (item.EndDate != info.EndDate)
  582. {
  583. forceUpdate = true;
  584. }
  585. item.EndDate = info.EndDate;
  586. item.HomePageUrl = info.HomePageUrl;
  587. item.ProductionYear = info.ProductionYear;
  588. if (!info.IsSeries || info.IsRepeat)
  589. {
  590. item.PremiereDate = info.OriginalAirDate;
  591. }
  592. item.IndexNumber = info.EpisodeNumber;
  593. item.ParentIndexNumber = info.SeasonNumber;
  594. if (!item.HasImage(ImageType.Primary))
  595. {
  596. if (!string.IsNullOrWhiteSpace(info.ImagePath))
  597. {
  598. item.SetImage(new ItemImageInfo
  599. {
  600. Path = info.ImagePath,
  601. Type = ImageType.Primary,
  602. IsPlaceholder = true
  603. }, 0);
  604. }
  605. else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
  606. {
  607. item.SetImage(new ItemImageInfo
  608. {
  609. Path = info.ImageUrl,
  610. Type = ImageType.Primary,
  611. IsPlaceholder = true
  612. }, 0);
  613. }
  614. }
  615. if (isNew)
  616. {
  617. await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
  618. }
  619. else if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag))
  620. {
  621. await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
  622. }
  623. else
  624. {
  625. // Increment this whenver some internal change deems it necessary
  626. var etag = info.Etag + "4";
  627. if (!string.Equals(etag, item.ExternalEtag, StringComparison.OrdinalIgnoreCase))
  628. {
  629. item.ExternalEtag = etag;
  630. await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
  631. }
  632. }
  633. _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem));
  634. return item;
  635. }
  636. private async Task<Guid> CreateRecordingRecord(RecordingInfo info, string serviceName, Guid parentFolderId, CancellationToken cancellationToken)
  637. {
  638. var isNew = false;
  639. var id = _tvDtoService.GetInternalRecordingId(serviceName, info.Id);
  640. var item = _itemRepo.RetrieveItem(id);
  641. if (item == null)
  642. {
  643. if (info.ChannelType == ChannelType.TV)
  644. {
  645. item = new LiveTvVideoRecording
  646. {
  647. Name = info.Name,
  648. Id = id,
  649. DateCreated = DateTime.UtcNow,
  650. DateModified = DateTime.UtcNow,
  651. VideoType = VideoType.VideoFile
  652. };
  653. }
  654. else
  655. {
  656. item = new LiveTvAudioRecording
  657. {
  658. Name = info.Name,
  659. Id = id,
  660. DateCreated = DateTime.UtcNow,
  661. DateModified = DateTime.UtcNow
  662. };
  663. }
  664. isNew = true;
  665. }
  666. item.ChannelId = _tvDtoService.GetInternalChannelId(serviceName, info.ChannelId).ToString("N");
  667. item.CommunityRating = info.CommunityRating;
  668. item.OfficialRating = info.OfficialRating;
  669. item.Overview = info.Overview;
  670. item.EndDate = info.EndDate;
  671. item.Genres = info.Genres;
  672. item.PremiereDate = info.OriginalAirDate;
  673. var recording = (ILiveTvRecording)item;
  674. recording.ExternalId = info.Id;
  675. var dataChanged = false;
  676. recording.Audio = info.Audio;
  677. recording.EndDate = info.EndDate;
  678. recording.EpisodeTitle = info.EpisodeTitle;
  679. recording.IsHD = info.IsHD;
  680. recording.IsKids = info.IsKids;
  681. recording.IsLive = info.IsLive;
  682. recording.IsMovie = info.IsMovie;
  683. recording.IsNews = info.IsNews;
  684. recording.IsPremiere = info.IsPremiere;
  685. recording.IsRepeat = info.IsRepeat;
  686. recording.IsSports = info.IsSports;
  687. recording.SeriesTimerId = info.SeriesTimerId;
  688. recording.StartDate = info.StartDate;
  689. if (!dataChanged)
  690. {
  691. dataChanged = recording.IsSeries != info.IsSeries;
  692. }
  693. recording.IsSeries = info.IsSeries;
  694. if (!item.ParentId.Equals(parentFolderId))
  695. {
  696. dataChanged = true;
  697. }
  698. item.ParentId = parentFolderId;
  699. if (!item.HasImage(ImageType.Primary))
  700. {
  701. if (!string.IsNullOrWhiteSpace(info.ImagePath))
  702. {
  703. item.SetImage(new ItemImageInfo
  704. {
  705. Path = info.ImagePath,
  706. Type = ImageType.Primary,
  707. IsPlaceholder = true
  708. }, 0);
  709. }
  710. else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
  711. {
  712. item.SetImage(new ItemImageInfo
  713. {
  714. Path = info.ImageUrl,
  715. Type = ImageType.Primary,
  716. IsPlaceholder = true
  717. }, 0);
  718. }
  719. }
  720. var statusChanged = info.Status != recording.Status;
  721. recording.Status = info.Status;
  722. recording.ServiceName = serviceName;
  723. if (!string.IsNullOrEmpty(info.Path))
  724. {
  725. if (!dataChanged)
  726. {
  727. dataChanged = !string.Equals(item.Path, info.Path);
  728. }
  729. var fileInfo = _fileSystem.GetFileInfo(info.Path);
  730. recording.DateCreated = _fileSystem.GetCreationTimeUtc(fileInfo);
  731. recording.DateModified = _fileSystem.GetLastWriteTimeUtc(fileInfo);
  732. item.Path = info.Path;
  733. }
  734. else if (!string.IsNullOrEmpty(info.Url))
  735. {
  736. if (!dataChanged)
  737. {
  738. dataChanged = !string.Equals(item.Path, info.Url);
  739. }
  740. item.Path = info.Url;
  741. }
  742. var metadataRefreshMode = MetadataRefreshMode.Default;
  743. if (isNew)
  744. {
  745. await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
  746. }
  747. else if (dataChanged || info.DateLastUpdated > recording.DateLastSaved || statusChanged)
  748. {
  749. metadataRefreshMode = MetadataRefreshMode.FullRefresh;
  750. await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
  751. }
  752. if (info.Status != RecordingStatus.InProgress)
  753. {
  754. _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem)
  755. {
  756. MetadataRefreshMode = metadataRefreshMode
  757. });
  758. }
  759. return item.Id;
  760. }
  761. public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
  762. {
  763. var program = GetInternalProgram(id);
  764. var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
  765. var list = new List<Tuple<BaseItemDto, string, string, string>>();
  766. list.Add(new Tuple<BaseItemDto, string, string, string>(dto, program.ServiceName, program.ExternalId, program.ExternalSeriesId));
  767. await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
  768. return dto;
  769. }
  770. public async Task<QueryResult<BaseItemDto>> GetPrograms(ProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
  771. {
  772. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
  773. var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
  774. if (query.SortBy.Length == 0)
  775. {
  776. // Unless something else was specified, order by start date to take advantage of a specialized index
  777. query.SortBy = new[] { ItemSortBy.StartDate };
  778. }
  779. var internalQuery = new InternalItemsQuery(user)
  780. {
  781. IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
  782. MinEndDate = query.MinEndDate,
  783. MinStartDate = query.MinStartDate,
  784. MaxEndDate = query.MaxEndDate,
  785. MaxStartDate = query.MaxStartDate,
  786. ChannelIds = query.ChannelIds,
  787. IsMovie = query.IsMovie,
  788. IsSports = query.IsSports,
  789. IsKids = query.IsKids,
  790. Genres = query.Genres,
  791. StartIndex = query.StartIndex,
  792. Limit = query.Limit,
  793. SortBy = query.SortBy,
  794. SortOrder = query.SortOrder ?? SortOrder.Ascending,
  795. EnableTotalRecordCount = query.EnableTotalRecordCount,
  796. TopParentIds = new[] { topFolder.Id.ToString("N") }
  797. };
  798. if (query.HasAired.HasValue)
  799. {
  800. if (query.HasAired.Value)
  801. {
  802. internalQuery.MaxEndDate = DateTime.UtcNow;
  803. }
  804. else
  805. {
  806. internalQuery.MinEndDate = DateTime.UtcNow;
  807. }
  808. }
  809. var queryResult = _libraryManager.QueryItems(internalQuery);
  810. RemoveFields(options);
  811. var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user).ConfigureAwait(false)).ToArray();
  812. var result = new QueryResult<BaseItemDto>
  813. {
  814. Items = returnArray,
  815. TotalRecordCount = queryResult.TotalRecordCount
  816. };
  817. return result;
  818. }
  819. public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken)
  820. {
  821. var user = _userManager.GetUserById(query.UserId);
  822. var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
  823. var internalQuery = new InternalItemsQuery(user)
  824. {
  825. IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
  826. IsAiring = query.IsAiring,
  827. IsMovie = query.IsMovie,
  828. IsSports = query.IsSports,
  829. IsKids = query.IsKids,
  830. EnableTotalRecordCount = query.EnableTotalRecordCount,
  831. SortBy = new[] { ItemSortBy.StartDate },
  832. TopParentIds = new[] { topFolder.Id.ToString("N") }
  833. };
  834. if (query.Limit.HasValue)
  835. {
  836. internalQuery.Limit = Math.Max(query.Limit.Value * 4, 200);
  837. }
  838. if (query.HasAired.HasValue)
  839. {
  840. if (query.HasAired.Value)
  841. {
  842. internalQuery.MaxEndDate = DateTime.UtcNow;
  843. }
  844. else
  845. {
  846. internalQuery.MinEndDate = DateTime.UtcNow;
  847. }
  848. }
  849. IEnumerable<LiveTvProgram> programs = _libraryManager.QueryItems(internalQuery).Items.Cast<LiveTvProgram>();
  850. var programList = programs.ToList();
  851. var factorChannelWatchCount = (query.IsAiring ?? false) || (query.IsKids ?? false) || (query.IsSports ?? false) || (query.IsMovie ?? false);
  852. programs = programList.OrderBy(i => i.StartDate.Date)
  853. .ThenByDescending(i => GetRecommendationScore(i, user.Id, factorChannelWatchCount))
  854. .ThenBy(i => i.StartDate);
  855. if (query.Limit.HasValue)
  856. {
  857. programs = programs.Take(query.Limit.Value);
  858. }
  859. programList = programs.ToList();
  860. var returnArray = programList.ToArray();
  861. var result = new QueryResult<LiveTvProgram>
  862. {
  863. Items = returnArray,
  864. TotalRecordCount = returnArray.Length
  865. };
  866. return result;
  867. }
  868. public async Task<QueryResult<BaseItemDto>> GetRecommendedPrograms(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
  869. {
  870. var internalResult = await GetRecommendedProgramsInternal(query, cancellationToken).ConfigureAwait(false);
  871. var user = _userManager.GetUserById(query.UserId);
  872. RemoveFields(options);
  873. var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
  874. var result = new QueryResult<BaseItemDto>
  875. {
  876. Items = returnArray,
  877. TotalRecordCount = internalResult.TotalRecordCount
  878. };
  879. return result;
  880. }
  881. private int GetRecommendationScore(LiveTvProgram program, Guid userId, bool factorChannelWatchCount)
  882. {
  883. var score = 0;
  884. if (program.IsLive)
  885. {
  886. score++;
  887. }
  888. if (program.IsSeries && !program.IsRepeat)
  889. {
  890. score++;
  891. }
  892. var channel = GetInternalChannel(program.ChannelId);
  893. var channelUserdata = _userDataManager.GetUserData(userId, channel);
  894. if (channelUserdata.Likes ?? false)
  895. {
  896. score += 2;
  897. }
  898. else if (!(channelUserdata.Likes ?? true))
  899. {
  900. score -= 2;
  901. }
  902. if (channelUserdata.IsFavorite)
  903. {
  904. score += 3;
  905. }
  906. if (factorChannelWatchCount)
  907. {
  908. score += channelUserdata.PlayCount;
  909. }
  910. return score;
  911. }
  912. private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string, string>> programs, CancellationToken cancellationToken)
  913. {
  914. var timers = new Dictionary<string, List<TimerInfo>>();
  915. var seriesTimers = new Dictionary<string, List<SeriesTimerInfo>>();
  916. foreach (var programTuple in programs)
  917. {
  918. var program = programTuple.Item1;
  919. var serviceName = programTuple.Item2;
  920. var externalProgramId = programTuple.Item3;
  921. string externalSeriesId = programTuple.Item4;
  922. if (string.IsNullOrWhiteSpace(serviceName))
  923. {
  924. continue;
  925. }
  926. List<TimerInfo> timerList;
  927. if (!timers.TryGetValue(serviceName, out timerList))
  928. {
  929. try
  930. {
  931. var tempTimers = await GetService(serviceName).GetTimersAsync(cancellationToken).ConfigureAwait(false);
  932. timers[serviceName] = timerList = tempTimers.ToList();
  933. }
  934. catch (Exception ex)
  935. {
  936. _logger.ErrorException("Error getting timer infos", ex);
  937. timers[serviceName] = timerList = new List<TimerInfo>();
  938. }
  939. }
  940. var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase));
  941. var foundSeriesTimer = false;
  942. if (timer != null)
  943. {
  944. program.TimerId = _tvDtoService.GetInternalTimerId(serviceName, timer.Id)
  945. .ToString("N");
  946. if (!string.IsNullOrEmpty(timer.SeriesTimerId))
  947. {
  948. program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, timer.SeriesTimerId)
  949. .ToString("N");
  950. foundSeriesTimer = true;
  951. }
  952. }
  953. if (foundSeriesTimer || string.IsNullOrWhiteSpace(externalSeriesId))
  954. {
  955. continue;
  956. }
  957. List<SeriesTimerInfo> seriesTimerList;
  958. if (!seriesTimers.TryGetValue(serviceName, out seriesTimerList))
  959. {
  960. try
  961. {
  962. var tempTimers = await GetService(serviceName).GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
  963. seriesTimers[serviceName] = seriesTimerList = tempTimers.ToList();
  964. }
  965. catch (Exception ex)
  966. {
  967. _logger.ErrorException("Error getting series timer infos", ex);
  968. seriesTimers[serviceName] = seriesTimerList = new List<SeriesTimerInfo>();
  969. }
  970. }
  971. var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase));
  972. if (seriesTimer != null)
  973. {
  974. program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, seriesTimer.Id)
  975. .ToString("N");
  976. }
  977. }
  978. }
  979. internal Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
  980. {
  981. return RefreshChannelsInternal(progress, cancellationToken);
  982. }
  983. private async Task RefreshChannelsInternal(IProgress<double> progress, CancellationToken cancellationToken)
  984. {
  985. EmbyTV.EmbyTV.Current.CreateRecordingFolders();
  986. var numComplete = 0;
  987. double progressPerService = _services.Count == 0
  988. ? 0
  989. : 1 / _services.Count;
  990. var newChannelIdList = new List<Guid>();
  991. var newProgramIdList = new List<Guid>();
  992. foreach (var service in _services)
  993. {
  994. cancellationToken.ThrowIfCancellationRequested();
  995. _logger.Debug("Refreshing guide from {0}", service.Name);
  996. try
  997. {
  998. var innerProgress = new ActionableProgress<double>();
  999. innerProgress.RegisterAction(p => progress.Report(p * progressPerService));
  1000. var idList = await RefreshChannelsInternal(service, innerProgress, cancellationToken).ConfigureAwait(false);
  1001. newChannelIdList.AddRange(idList.Item1);
  1002. newProgramIdList.AddRange(idList.Item2);
  1003. }
  1004. catch (OperationCanceledException)
  1005. {
  1006. throw;
  1007. }
  1008. catch (Exception ex)
  1009. {
  1010. _logger.ErrorException("Error refreshing channels for service", ex);
  1011. }
  1012. numComplete++;
  1013. double percent = numComplete;
  1014. percent /= _services.Count;
  1015. progress.Report(100 * percent);
  1016. }
  1017. await CleanDatabaseInternal(newChannelIdList, new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken).ConfigureAwait(false);
  1018. await CleanDatabaseInternal(newProgramIdList, new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken).ConfigureAwait(false);
  1019. var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
  1020. if (coreService != null)
  1021. {
  1022. await coreService.RefreshSeriesTimers(cancellationToken, new Progress<double>()).ConfigureAwait(false);
  1023. }
  1024. // Load these now which will prefetch metadata
  1025. var dtoOptions = new DtoOptions();
  1026. dtoOptions.Fields.Remove(ItemFields.SyncInfo);
  1027. dtoOptions.Fields.Remove(ItemFields.BasicSyncInfo);
  1028. await GetRecordings(new RecordingQuery(), dtoOptions, cancellationToken).ConfigureAwait(false);
  1029. progress.Report(100);
  1030. }
  1031. private async Task<Tuple<List<Guid>, List<Guid>>> RefreshChannelsInternal(ILiveTvService service, IProgress<double> progress, CancellationToken cancellationToken)
  1032. {
  1033. progress.Report(10);
  1034. var allChannels = await GetChannels(service, cancellationToken).ConfigureAwait(false);
  1035. var allChannelsList = allChannels.ToList();
  1036. var list = new List<LiveTvChannel>();
  1037. var numComplete = 0;
  1038. var parentFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
  1039. var parentFolderId = parentFolder.Id;
  1040. foreach (var channelInfo in allChannelsList)
  1041. {
  1042. cancellationToken.ThrowIfCancellationRequested();
  1043. try
  1044. {
  1045. var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolderId, cancellationToken).ConfigureAwait(false);
  1046. list.Add(item);
  1047. }
  1048. catch (OperationCanceledException)
  1049. {
  1050. throw;
  1051. }
  1052. catch (Exception ex)
  1053. {
  1054. _logger.ErrorException("Error getting channel information for {0}", ex, channelInfo.Item2.Name);
  1055. }
  1056. numComplete++;
  1057. double percent = numComplete;
  1058. percent /= allChannelsList.Count;
  1059. progress.Report(5 * percent + 10);
  1060. }
  1061. progress.Report(15);
  1062. numComplete = 0;
  1063. var programs = new List<Guid>();
  1064. var channels = new List<Guid>();
  1065. var guideDays = GetGuideDays();
  1066. _logger.Info("Refreshing guide with {0} days of guide data", guideDays);
  1067. cancellationToken.ThrowIfCancellationRequested();
  1068. foreach (var currentChannel in list)
  1069. {
  1070. channels.Add(currentChannel.Id);
  1071. cancellationToken.ThrowIfCancellationRequested();
  1072. try
  1073. {
  1074. var start = DateTime.UtcNow.AddHours(-1);
  1075. var end = start.AddDays(guideDays);
  1076. var channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false);
  1077. foreach (var program in channelPrograms)
  1078. {
  1079. var programItem = await GetProgram(program, currentChannel, currentChannel.ChannelType, service.Name, cancellationToken).ConfigureAwait(false);
  1080. programs.Add(programItem.Id);
  1081. }
  1082. }
  1083. catch (OperationCanceledException)
  1084. {
  1085. throw;
  1086. }
  1087. catch (Exception ex)
  1088. {
  1089. _logger.ErrorException("Error getting programs for channel {0}", ex, currentChannel.Name);
  1090. }
  1091. numComplete++;
  1092. double percent = numComplete;
  1093. percent /= allChannelsList.Count;
  1094. progress.Report(80 * percent + 10);
  1095. }
  1096. progress.Report(100);
  1097. return new Tuple<List<Guid>, List<Guid>>(channels, programs);
  1098. }
  1099. private async Task CleanDatabaseInternal(List<Guid> currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
  1100. {
  1101. var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
  1102. {
  1103. IncludeItemTypes = validTypes
  1104. }).ToList();
  1105. var numComplete = 0;
  1106. foreach (var itemId in list)
  1107. {
  1108. cancellationToken.ThrowIfCancellationRequested();
  1109. if (itemId == Guid.Empty)
  1110. {
  1111. // Somehow some invalid data got into the db. It probably predates the boundary checking
  1112. continue;
  1113. }
  1114. if (!currentIdList.Contains(itemId))
  1115. {
  1116. var item = _libraryManager.GetItemById(itemId);
  1117. if (item != null)
  1118. {
  1119. await _libraryManager.DeleteItem(item, new DeleteOptions
  1120. {
  1121. DeleteFileLocation = false
  1122. }).ConfigureAwait(false);
  1123. }
  1124. }
  1125. numComplete++;
  1126. double percent = numComplete;
  1127. percent /= list.Count;
  1128. progress.Report(100 * percent);
  1129. }
  1130. }
  1131. private const int MaxGuideDays = 14;
  1132. private double GetGuideDays()
  1133. {
  1134. var config = GetConfiguration();
  1135. if (config.GuideDays.HasValue)
  1136. {
  1137. return Math.Max(1, Math.Min(config.GuideDays.Value, MaxGuideDays));
  1138. }
  1139. return 7;
  1140. }
  1141. private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)
  1142. {
  1143. var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false);
  1144. return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i));
  1145. }
  1146. private DateTime _lastRecordingRefreshTime;
  1147. private async Task RefreshRecordings(CancellationToken cancellationToken)
  1148. {
  1149. const int cacheMinutes = 5;
  1150. if ((DateTime.UtcNow - _lastRecordingRefreshTime).TotalMinutes < cacheMinutes)
  1151. {
  1152. return;
  1153. }
  1154. await _refreshRecordingsLock.WaitAsync(cancellationToken).ConfigureAwait(false);
  1155. try
  1156. {
  1157. if ((DateTime.UtcNow - _lastRecordingRefreshTime).TotalMinutes < cacheMinutes)
  1158. {
  1159. return;
  1160. }
  1161. var tasks = _services.Select(async i =>
  1162. {
  1163. try
  1164. {
  1165. var recs = await i.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
  1166. return recs.Select(r => new Tuple<RecordingInfo, ILiveTvService>(r, i));
  1167. }
  1168. catch (Exception ex)
  1169. {
  1170. _logger.ErrorException("Error getting recordings", ex);
  1171. return new List<Tuple<RecordingInfo, ILiveTvService>>();
  1172. }
  1173. });
  1174. var results = await Task.WhenAll(tasks).ConfigureAwait(false);
  1175. var folder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
  1176. var parentFolderId = folder.Id;
  1177. var recordingTasks = results.SelectMany(i => i.ToList()).Select(i => CreateRecordingRecord(i.Item1, i.Item2.Name, parentFolderId, cancellationToken));
  1178. var idList = await Task.WhenAll(recordingTasks).ConfigureAwait(false);
  1179. await CleanDatabaseInternal(idList.ToList(), new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }, new Progress<double>(), cancellationToken).ConfigureAwait(false);
  1180. _lastRecordingRefreshTime = DateTime.UtcNow;
  1181. }
  1182. finally
  1183. {
  1184. _refreshRecordingsLock.Release();
  1185. }
  1186. }
  1187. private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, User user)
  1188. {
  1189. if (user == null || (query.IsInProgress ?? false))
  1190. {
  1191. return new QueryResult<BaseItem>();
  1192. }
  1193. var folders = EmbyTV.EmbyTV.Current.GetRecordingFolders()
  1194. .SelectMany(i => i.Locations)
  1195. .Distinct(StringComparer.OrdinalIgnoreCase)
  1196. .Select(i => _libraryManager.FindByPath(i, true))
  1197. .Where(i => i != null)
  1198. .Where(i => i.IsVisibleStandalone(user))
  1199. .ToList();
  1200. if (folders.Count == 0)
  1201. {
  1202. return new QueryResult<BaseItem>();
  1203. }
  1204. var includeItemTypes = new List<string>();
  1205. var excludeItemTypes = new List<string>();
  1206. var genres = new List<string>();
  1207. if (query.IsMovie.HasValue)
  1208. {
  1209. if (query.IsMovie.Value)
  1210. {
  1211. includeItemTypes.Add(typeof(Movie).Name);
  1212. }
  1213. else
  1214. {
  1215. excludeItemTypes.Add(typeof(Movie).Name);
  1216. }
  1217. }
  1218. if (query.IsSeries.HasValue)
  1219. {
  1220. if (query.IsSeries.Value)
  1221. {
  1222. includeItemTypes.Add(typeof(Episode).Name);
  1223. }
  1224. else
  1225. {
  1226. excludeItemTypes.Add(typeof(Episode).Name);
  1227. }
  1228. }
  1229. if (query.IsSports.HasValue)
  1230. {
  1231. if (query.IsSports.Value)
  1232. {
  1233. genres.Add("Sports");
  1234. }
  1235. }
  1236. if (query.IsKids.HasValue)
  1237. {
  1238. if (query.IsKids.Value)
  1239. {
  1240. genres.Add("Kids");
  1241. genres.Add("Children");
  1242. genres.Add("Family");
  1243. }
  1244. }
  1245. return _libraryManager.GetItemsResult(new InternalItemsQuery(user)
  1246. {
  1247. MediaTypes = new[] { MediaType.Video },
  1248. Recursive = true,
  1249. AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(),
  1250. IsFolder = false,
  1251. ExcludeLocationTypes = new[] { LocationType.Virtual },
  1252. Limit = query.Limit,
  1253. SortBy = new[] { ItemSortBy.DateCreated },
  1254. SortOrder = SortOrder.Descending,
  1255. EnableTotalRecordCount = query.EnableTotalRecordCount,
  1256. IncludeItemTypes = includeItemTypes.ToArray(),
  1257. ExcludeItemTypes = excludeItemTypes.ToArray(),
  1258. Genres = genres.ToArray()
  1259. });
  1260. }
  1261. public async Task<QueryResult<BaseItemDto>> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken)
  1262. {
  1263. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
  1264. if (user != null && !IsLiveTvEnabled(user))
  1265. {
  1266. return new QueryResult<BaseItemDto>();
  1267. }
  1268. if (_services.Count > 1)
  1269. {
  1270. return new QueryResult<BaseItemDto>();
  1271. }
  1272. if (user == null || (query.IsInProgress ?? false))
  1273. {
  1274. return new QueryResult<BaseItemDto>();
  1275. }
  1276. var folders = EmbyTV.EmbyTV.Current.GetRecordingFolders()
  1277. .SelectMany(i => i.Locations)
  1278. .Distinct(StringComparer.OrdinalIgnoreCase)
  1279. .Select(i => _libraryManager.FindByPath(i, true))
  1280. .Where(i => i != null)
  1281. .Where(i => i.IsVisibleStandalone(user))
  1282. .ToList();
  1283. if (folders.Count == 0)
  1284. {
  1285. return new QueryResult<BaseItemDto>();
  1286. }
  1287. var includeItemTypes = new List<string>();
  1288. var excludeItemTypes = new List<string>();
  1289. includeItemTypes.Add(typeof(Series).Name);
  1290. var internalResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
  1291. {
  1292. Recursive = true,
  1293. AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(),
  1294. Limit = query.Limit,
  1295. SortBy = new[] { ItemSortBy.DateCreated },
  1296. SortOrder = SortOrder.Descending,
  1297. EnableTotalRecordCount = query.EnableTotalRecordCount,
  1298. IncludeItemTypes = includeItemTypes.ToArray(),
  1299. ExcludeItemTypes = excludeItemTypes.ToArray()
  1300. });
  1301. RemoveFields(options);
  1302. var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
  1303. return new QueryResult<BaseItemDto>
  1304. {
  1305. Items = returnArray,
  1306. TotalRecordCount = internalResult.TotalRecordCount
  1307. };
  1308. }
  1309. public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken)
  1310. {
  1311. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
  1312. if (user != null && !IsLiveTvEnabled(user))
  1313. {
  1314. return new QueryResult<BaseItem>();
  1315. }
  1316. if (_services.Count == 1)
  1317. {
  1318. return GetEmbyRecordings(query, user);
  1319. }
  1320. await RefreshRecordings(cancellationToken).ConfigureAwait(false);
  1321. var internalQuery = new InternalItemsQuery(user)
  1322. {
  1323. IncludeItemTypes = new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }
  1324. };
  1325. if (!string.IsNullOrEmpty(query.ChannelId))
  1326. {
  1327. internalQuery.ChannelIds = new[] { query.ChannelId };
  1328. }
  1329. var queryResult = _libraryManager.GetItemList(internalQuery);
  1330. IEnumerable<ILiveTvRecording> recordings = queryResult.Cast<ILiveTvRecording>();
  1331. if (!string.IsNullOrWhiteSpace(query.Id))
  1332. {
  1333. var guid = new Guid(query.Id);
  1334. recordings = recordings
  1335. .Where(i => i.Id == guid);
  1336. }
  1337. if (!string.IsNullOrWhiteSpace(query.GroupId))
  1338. {
  1339. var guid = new Guid(query.GroupId);
  1340. recordings = recordings.Where(i => GetRecordingGroupIds(i).Contains(guid));
  1341. }
  1342. if (query.IsInProgress.HasValue)
  1343. {
  1344. var val = query.IsInProgress.Value;
  1345. recordings = recordings.Where(i => i.Status == RecordingStatus.InProgress == val);
  1346. }
  1347. if (query.Status.HasValue)
  1348. {
  1349. var val = query.Status.Value;
  1350. recordings = recordings.Where(i => i.Status == val);
  1351. }
  1352. if (query.IsMovie.HasValue)
  1353. {
  1354. var val = query.IsMovie.Value;
  1355. recordings = recordings.Where(i => i.IsMovie == val);
  1356. }
  1357. if (query.IsSeries.HasValue)
  1358. {
  1359. var val = query.IsSeries.Value;
  1360. recordings = recordings.Where(i => i.IsSeries == val);
  1361. }
  1362. if (query.IsKids.HasValue)
  1363. {
  1364. var val = query.IsKids.Value;
  1365. recordings = recordings.Where(i => i.IsKids == val);
  1366. }
  1367. if (query.IsSports.HasValue)
  1368. {
  1369. var val = query.IsSports.Value;
  1370. recordings = recordings.Where(i => i.IsSports == val);
  1371. }
  1372. if (!string.IsNullOrEmpty(query.SeriesTimerId))
  1373. {
  1374. var guid = new Guid(query.SeriesTimerId);
  1375. recordings = recordings
  1376. .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.ServiceName, i.SeriesTimerId) == guid);
  1377. }
  1378. recordings = recordings.OrderByDescending(i => i.StartDate);
  1379. var entityList = recordings.ToList();
  1380. IEnumerable<ILiveTvRecording> entities = entityList;
  1381. if (query.StartIndex.HasValue)
  1382. {
  1383. entities = entities.Skip(query.StartIndex.Value);
  1384. }
  1385. if (query.Limit.HasValue)
  1386. {
  1387. entities = entities.Take(query.Limit.Value);
  1388. }
  1389. return new QueryResult<BaseItem>
  1390. {
  1391. Items = entities.Cast<BaseItem>().ToArray(),
  1392. TotalRecordCount = entityList.Count
  1393. };
  1394. }
  1395. public async Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> tuples, List<ItemFields> fields, User user = null)
  1396. {
  1397. var recordingTuples = new List<Tuple<BaseItemDto, string, string, string>>();
  1398. foreach (var tuple in tuples)
  1399. {
  1400. var program = (LiveTvProgram)tuple.Item1;
  1401. var dto = tuple.Item2;
  1402. dto.StartDate = program.StartDate;
  1403. dto.EpisodeTitle = program.EpisodeTitle;
  1404. if (program.IsRepeat)
  1405. {
  1406. dto.IsRepeat = program.IsRepeat;
  1407. }
  1408. if (program.IsMovie)
  1409. {
  1410. dto.IsMovie = program.IsMovie;
  1411. }
  1412. if (program.IsSeries)
  1413. {
  1414. dto.IsSeries = program.IsSeries;
  1415. }
  1416. if (program.IsSports)
  1417. {
  1418. dto.IsSports = program.IsSports;
  1419. }
  1420. if (program.IsLive)
  1421. {
  1422. dto.IsLive = program.IsLive;
  1423. }
  1424. if (program.IsNews)
  1425. {
  1426. dto.IsNews = program.IsNews;
  1427. }
  1428. if (program.IsKids)
  1429. {
  1430. dto.IsKids = program.IsKids;
  1431. }
  1432. if (program.IsPremiere)
  1433. {
  1434. dto.IsPremiere = program.IsPremiere;
  1435. }
  1436. if (fields.Contains(ItemFields.ChannelInfo))
  1437. {
  1438. var channel = GetInternalChannel(program.ChannelId);
  1439. if (channel != null)
  1440. {
  1441. dto.ChannelName = channel.Name;
  1442. dto.MediaType = channel.MediaType;
  1443. dto.ChannelNumber = channel.Number;
  1444. if (channel.HasImage(ImageType.Primary))
  1445. {
  1446. dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
  1447. }
  1448. }
  1449. }
  1450. var service = GetService(program);
  1451. var serviceName = service == null ? null : service.Name;
  1452. if (fields.Contains(ItemFields.ServiceName))
  1453. {
  1454. dto.ServiceName = serviceName;
  1455. }
  1456. recordingTuples.Add(new Tuple<BaseItemDto, string, string, string>(dto, serviceName, program.ExternalId, program.ExternalSeriesId));
  1457. }
  1458. await AddRecordingInfo(recordingTuples, CancellationToken.None).ConfigureAwait(false);
  1459. }
  1460. public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, User user = null)
  1461. {
  1462. var recording = (ILiveTvRecording)item;
  1463. var service = GetService(recording);
  1464. var channel = string.IsNullOrWhiteSpace(recording.ChannelId) ? null : GetInternalChannel(recording.ChannelId);
  1465. var info = recording;
  1466. dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
  1467. ? null
  1468. : _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N");
  1469. dto.StartDate = info.StartDate;
  1470. dto.RecordingStatus = info.Status;
  1471. dto.IsRepeat = info.IsRepeat;
  1472. dto.EpisodeTitle = info.EpisodeTitle;
  1473. dto.IsMovie = info.IsMovie;
  1474. dto.IsSeries = info.IsSeries;
  1475. dto.IsSports = info.IsSports;
  1476. dto.IsLive = info.IsLive;
  1477. dto.IsNews = info.IsNews;
  1478. dto.IsKids = info.IsKids;
  1479. dto.IsPremiere = info.IsPremiere;
  1480. dto.CanDelete = user == null
  1481. ? recording.CanDelete()
  1482. : recording.CanDelete(user);
  1483. if (dto.MediaSources == null)
  1484. {
  1485. dto.MediaSources = recording.GetMediaSources(true).ToList();
  1486. }
  1487. if (dto.MediaStreams == null)
  1488. {
  1489. dto.MediaStreams = dto.MediaSources.SelectMany(i => i.MediaStreams).ToList();
  1490. }
  1491. if (info.Status == RecordingStatus.InProgress && info.EndDate.HasValue)
  1492. {
  1493. var now = DateTime.UtcNow.Ticks;
  1494. var start = info.StartDate.Ticks;
  1495. var end = info.EndDate.Value.Ticks;
  1496. var pct = now - start;
  1497. pct /= end;
  1498. pct *= 100;
  1499. dto.CompletionPercentage = pct;
  1500. }
  1501. if (channel != null)
  1502. {
  1503. dto.ChannelName = channel.Name;
  1504. if (channel.HasImage(ImageType.Primary))
  1505. {
  1506. dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
  1507. }
  1508. }
  1509. }
  1510. public async Task<QueryResult<BaseItemDto>> GetRecordings(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken)
  1511. {
  1512. var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
  1513. var internalResult = await GetInternalRecordings(query, cancellationToken).ConfigureAwait(false);
  1514. RemoveFields(options);
  1515. var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
  1516. return new QueryResult<BaseItemDto>
  1517. {
  1518. Items = returnArray,
  1519. TotalRecordCount = internalResult.TotalRecordCount
  1520. };
  1521. }
  1522. public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
  1523. {
  1524. var tasks = _services.Select(async i =>
  1525. {
  1526. try
  1527. {
  1528. var recs = await i.GetTimersAsync(cancellationToken).ConfigureAwait(false);
  1529. return recs.Select(r => new Tuple<TimerInfo, ILiveTvService>(r, i));
  1530. }
  1531. catch (Exception ex)
  1532. {
  1533. _logger.ErrorException("Error getting recordings", ex);
  1534. return new List<Tuple<TimerInfo, ILiveTvService>>();
  1535. }
  1536. });
  1537. var results = await Task.WhenAll(tasks).ConfigureAwait(false);
  1538. var timers = results.SelectMany(i => i.ToList());
  1539. if (query.IsActive.HasValue)
  1540. {
  1541. if (query.IsActive.Value)
  1542. {
  1543. timers = timers.Where(i => i.Item1.Status == RecordingStatus.InProgress);
  1544. }
  1545. else
  1546. {
  1547. timers = timers.Where(i => i.Item1.Status != RecordingStatus.InProgress);
  1548. }
  1549. }
  1550. if (!string.IsNullOrEmpty(query.ChannelId))
  1551. {
  1552. var guid = new Guid(query.ChannelId);
  1553. timers = timers.Where(i => guid == _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId));
  1554. }
  1555. if (!string.IsNullOrEmpty(query.SeriesTimerId))
  1556. {
  1557. var guid = new Guid(query.SeriesTimerId);
  1558. timers = timers
  1559. .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item2.Name, i.Item1.SeriesTimerId) == guid);
  1560. }
  1561. var returnList = new List<TimerInfoDto>();
  1562. foreach (var i in timers)
  1563. {
  1564. var program = string.IsNullOrEmpty(i.Item1.ProgramId) ?
  1565. null :
  1566. GetInternalProgram(_tvDtoService.GetInternalProgramId(i.Item2.Name, i.Item1.ProgramId).ToString("N"));
  1567. var channel = string.IsNullOrEmpty(i.Item1.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId));
  1568. returnList.Add(_tvDtoService.GetTimerInfoDto(i.Item1, i.Item2, program, channel));
  1569. }
  1570. var returnArray = returnList
  1571. .OrderBy(i => i.StartDate)
  1572. .ToArray();
  1573. return new QueryResult<TimerInfoDto>
  1574. {
  1575. Items = returnArray,
  1576. TotalRecordCount = returnArray.Length
  1577. };
  1578. }
  1579. public Task OnRecordingFileDeleted(BaseItem recording)
  1580. {
  1581. var service = GetService(recording);
  1582. if (service is EmbyTV.EmbyTV)
  1583. {
  1584. // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
  1585. return service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None);
  1586. }
  1587. return Task.FromResult(true);
  1588. }
  1589. public async Task DeleteRecording(string recordingId)
  1590. {
  1591. var recording = await GetInternalRecording(recordingId, CancellationToken.None).ConfigureAwait(false);
  1592. if (recording == null)
  1593. {
  1594. throw new ResourceNotFoundException(string.Format("Recording with Id {0} not found", recordingId));
  1595. }
  1596. await DeleteRecording((BaseItem)recording).ConfigureAwait(false);
  1597. }
  1598. public async Task DeleteRecording(BaseItem recording)
  1599. {
  1600. var service = GetService(recording.ServiceName);
  1601. try
  1602. {
  1603. await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false);
  1604. }
  1605. catch (ResourceNotFoundException)
  1606. {
  1607. }
  1608. _lastRecordingRefreshTime = DateTime.MinValue;
  1609. // This is the responsibility of the live tv service
  1610. await _libraryManager.DeleteItem((BaseItem)recording, new DeleteOptions
  1611. {
  1612. DeleteFileLocation = false
  1613. }).ConfigureAwait(false);
  1614. _lastRecordingRefreshTime = DateTime.MinValue;
  1615. }
  1616. public async Task CancelTimer(string id)
  1617. {
  1618. var timer = await GetTimer(id, CancellationToken.None).ConfigureAwait(false);
  1619. if (timer == null)
  1620. {
  1621. throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
  1622. }
  1623. var service = GetService(timer.ServiceName);
  1624. await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
  1625. _lastRecordingRefreshTime = DateTime.MinValue;
  1626. EventHelper.QueueEventIfNotNull(TimerCancelled, this, new GenericEventArgs<TimerEventInfo>
  1627. {
  1628. Argument = new TimerEventInfo
  1629. {
  1630. Id = id
  1631. }
  1632. }, _logger);
  1633. }
  1634. public async Task CancelSeriesTimer(string id)
  1635. {
  1636. var timer = await GetSeriesTimer(id, CancellationToken.None).ConfigureAwait(false);
  1637. if (timer == null)
  1638. {
  1639. throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
  1640. }
  1641. var service = GetService(timer.ServiceName);
  1642. await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
  1643. _lastRecordingRefreshTime = DateTime.MinValue;
  1644. EventHelper.QueueEventIfNotNull(SeriesTimerCancelled, this, new GenericEventArgs<TimerEventInfo>
  1645. {
  1646. Argument = new TimerEventInfo
  1647. {
  1648. Id = id
  1649. }
  1650. }, _logger);
  1651. }
  1652. public async Task<BaseItemDto> GetRecording(string id, DtoOptions options, CancellationToken cancellationToken, User user = null)
  1653. {
  1654. var item = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
  1655. if (item == null)
  1656. {
  1657. return null;
  1658. }
  1659. return _dtoService.GetBaseItemDto((BaseItem)item, options, user);
  1660. }
  1661. public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
  1662. {
  1663. var results = await GetTimers(new TimerQuery(), cancellationToken).ConfigureAwait(false);
  1664. return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
  1665. }
  1666. public async Task<SeriesTimerInfoDto> GetSeriesTimer(string id, CancellationToken cancellationToken)
  1667. {
  1668. var results = await GetSeriesTimers(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false);
  1669. return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
  1670. }
  1671. public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
  1672. {
  1673. var tasks = _services.Select(async i =>
  1674. {
  1675. try
  1676. {
  1677. var recs = await i.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
  1678. return recs.Select(r => new Tuple<SeriesTimerInfo, ILiveTvService>(r, i));
  1679. }
  1680. catch (Exception ex)
  1681. {
  1682. _logger.ErrorException("Error getting recordings", ex);
  1683. return new List<Tuple<SeriesTimerInfo, ILiveTvService>>();
  1684. }
  1685. });
  1686. var results = await Task.WhenAll(tasks).ConfigureAwait(false);
  1687. var timers = results.SelectMany(i => i.ToList());
  1688. if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
  1689. {
  1690. timers = query.SortOrder == SortOrder.Descending ?
  1691. timers.OrderBy(i => i.Item1.Priority).ThenByStringDescending(i => i.Item1.Name) :
  1692. timers.OrderByDescending(i => i.Item1.Priority).ThenByString(i => i.Item1.Name);
  1693. }
  1694. else
  1695. {
  1696. timers = query.SortOrder == SortOrder.Descending ?
  1697. timers.OrderByStringDescending(i => i.Item1.Name) :
  1698. timers.OrderByString(i => i.Item1.Name);
  1699. }
  1700. var returnArray = timers
  1701. .Select(i =>
  1702. {
  1703. string channelName = null;
  1704. if (!string.IsNullOrEmpty(i.Item1.ChannelId))
  1705. {
  1706. var internalChannelId = _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId);
  1707. var channel = GetInternalChannel(internalChannelId);
  1708. channelName = channel == null ? null : channel.Name;
  1709. }
  1710. return _tvDtoService.GetSeriesTimerInfoDto(i.Item1, i.Item2, channelName);
  1711. })
  1712. .ToArray();
  1713. return new QueryResult<SeriesTimerInfoDto>
  1714. {
  1715. Items = returnArray,
  1716. TotalRecordCount = returnArray.Length
  1717. };
  1718. }
  1719. public void AddChannelInfo(List<Tuple<BaseItemDto, LiveTvChannel>> tuples, DtoOptions options, User user)
  1720. {
  1721. var now = DateTime.UtcNow;
  1722. var channelIds = tuples.Select(i => i.Item2.Id.ToString("N")).Distinct().ToArray();
  1723. var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
  1724. {
  1725. IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
  1726. ChannelIds = channelIds,
  1727. MaxStartDate = now,
  1728. MinEndDate = now,
  1729. Limit = channelIds.Length,
  1730. SortBy = new[] { "StartDate" },
  1731. TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") }
  1732. }).ToList() : new List<BaseItem>();
  1733. RemoveFields(options);
  1734. foreach (var tuple in tuples)
  1735. {
  1736. var dto = tuple.Item1;
  1737. var channel = tuple.Item2;
  1738. dto.Number = channel.Number;
  1739. dto.ChannelNumber = channel.Number;
  1740. dto.ChannelType = channel.ChannelType;
  1741. dto.ServiceName = channel.ServiceName;
  1742. if (options.Fields.Contains(ItemFields.MediaSources))
  1743. {
  1744. dto.MediaSources = channel.GetMediaSources(true).ToList();
  1745. }
  1746. if (options.AddCurrentProgram)
  1747. {
  1748. var channelIdString = channel.Id.ToString("N");
  1749. var currentProgram = programs.FirstOrDefault(i => string.Equals(i.ChannelId, channelIdString));
  1750. if (currentProgram != null)
  1751. {
  1752. dto.CurrentProgram = _dtoService.GetBaseItemDto(currentProgram, options, user);
  1753. }
  1754. }
  1755. }
  1756. }
  1757. private async Task<Tuple<SeriesTimerInfo, ILiveTvService>> GetNewTimerDefaultsInternal(CancellationToken cancellationToken, LiveTvProgram program = null)
  1758. {
  1759. var service = program != null && !string.IsNullOrWhiteSpace(program.ServiceName) ?
  1760. GetService(program) :
  1761. _services.FirstOrDefault();
  1762. ProgramInfo programInfo = null;
  1763. if (program != null)
  1764. {
  1765. var channel = GetInternalChannel(program.ChannelId);
  1766. programInfo = new ProgramInfo
  1767. {
  1768. Audio = program.Audio,
  1769. ChannelId = channel.ExternalId,
  1770. CommunityRating = program.CommunityRating,
  1771. EndDate = program.EndDate ?? DateTime.MinValue,
  1772. EpisodeTitle = program.EpisodeTitle,
  1773. Genres = program.Genres,
  1774. Id = program.ExternalId,
  1775. IsHD = program.IsHD,
  1776. IsKids = program.IsKids,
  1777. IsLive = program.IsLive,
  1778. IsMovie = program.IsMovie,
  1779. IsNews = program.IsNews,
  1780. IsPremiere = program.IsPremiere,
  1781. IsRepeat = program.IsRepeat,
  1782. IsSeries = program.IsSeries,
  1783. IsSports = program.IsSports,
  1784. OriginalAirDate = program.PremiereDate,
  1785. Overview = program.Overview,
  1786. StartDate = program.StartDate,
  1787. //ImagePath = program.ExternalImagePath,
  1788. Name = program.Name,
  1789. OfficialRating = program.OfficialRating
  1790. };
  1791. }
  1792. var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);
  1793. info.Id = null;
  1794. return new Tuple<SeriesTimerInfo, ILiveTvService>(info, service);
  1795. }
  1796. public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
  1797. {
  1798. var info = await GetNewTimerDefaultsInternal(cancellationToken).ConfigureAwait(false);
  1799. var obj = _tvDtoService.GetSeriesTimerInfoDto(info.Item1, info.Item2, null);
  1800. return obj;
  1801. }
  1802. public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
  1803. {
  1804. var program = GetInternalProgram(programId);
  1805. var programDto = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
  1806. var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
  1807. var info = _tvDtoService.GetSeriesTimerInfoDto(defaults.Item1, defaults.Item2, null);
  1808. info.Days = defaults.Item1.Days;
  1809. info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
  1810. info.Name = program.Name;
  1811. info.ChannelId = programDto.ChannelId;
  1812. info.ChannelName = programDto.ChannelName;
  1813. info.StartDate = program.StartDate;
  1814. info.Name = program.Name;
  1815. info.Overview = program.Overview;
  1816. info.ProgramId = programDto.Id;
  1817. info.ExternalProgramId = program.ExternalId;
  1818. if (program.EndDate.HasValue)
  1819. {
  1820. info.EndDate = program.EndDate.Value;
  1821. }
  1822. return info;
  1823. }
  1824. public async Task CreateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
  1825. {
  1826. var service = GetService(timer.ServiceName);
  1827. var info = await _tvDtoService.GetTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
  1828. // Set priority from default values
  1829. var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
  1830. info.Priority = defaultValues.Priority;
  1831. string newTimerId = null;
  1832. var supportsNewTimerIds = service as ISupportsNewTimerIds;
  1833. if (supportsNewTimerIds != null)
  1834. {
  1835. newTimerId = await supportsNewTimerIds.CreateTimer(info, cancellationToken).ConfigureAwait(false);
  1836. newTimerId = _tvDtoService.GetInternalTimerId(timer.ServiceName, newTimerId).ToString("N");
  1837. }
  1838. else
  1839. {
  1840. await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
  1841. }
  1842. _lastRecordingRefreshTime = DateTime.MinValue;
  1843. _logger.Info("New recording scheduled");
  1844. EventHelper.QueueEventIfNotNull(TimerCreated, this, new GenericEventArgs<TimerEventInfo>
  1845. {
  1846. Argument = new TimerEventInfo
  1847. {
  1848. ProgramId = _tvDtoService.GetInternalProgramId(timer.ServiceName, info.ProgramId).ToString("N"),
  1849. Id = newTimerId
  1850. }
  1851. }, _logger);
  1852. }
  1853. public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
  1854. {
  1855. var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
  1856. if (!registration.IsValid)
  1857. {
  1858. _logger.Info("Creating series recordings requires an active Emby Premiere subscription.");
  1859. return;
  1860. }
  1861. var service = GetService(timer.ServiceName);
  1862. var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
  1863. // Set priority from default values
  1864. var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
  1865. info.Priority = defaultValues.Priority;
  1866. string newTimerId = null;
  1867. var supportsNewTimerIds = service as ISupportsNewTimerIds;
  1868. if (supportsNewTimerIds != null)
  1869. {
  1870. newTimerId = await supportsNewTimerIds.CreateSeriesTimer(info, cancellationToken).ConfigureAwait(false);
  1871. newTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.ServiceName, newTimerId).ToString("N");
  1872. }
  1873. else
  1874. {
  1875. await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
  1876. }
  1877. _lastRecordingRefreshTime = DateTime.MinValue;
  1878. EventHelper.QueueEventIfNotNull(SeriesTimerCreated, this, new GenericEventArgs<TimerEventInfo>
  1879. {
  1880. Argument = new TimerEventInfo
  1881. {
  1882. ProgramId = _tvDtoService.GetInternalProgramId(timer.ServiceName, info.ProgramId).ToString("N"),
  1883. Id = newTimerId
  1884. }
  1885. }, _logger);
  1886. }
  1887. public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
  1888. {
  1889. var info = await _tvDtoService.GetTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
  1890. var service = GetService(timer.ServiceName);
  1891. await service.UpdateTimerAsync(info, cancellationToken).ConfigureAwait(false);
  1892. _lastRecordingRefreshTime = DateTime.MinValue;
  1893. }
  1894. public async Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
  1895. {
  1896. var info = await _tvDtoService.GetSeriesTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
  1897. var service = GetService(timer.ServiceName);
  1898. await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
  1899. _lastRecordingRefreshTime = DateTime.MinValue;
  1900. }
  1901. private IEnumerable<string> GetRecordingGroupNames(ILiveTvRecording recording)
  1902. {
  1903. var list = new List<string>();
  1904. if (recording.IsSeries)
  1905. {
  1906. list.Add(recording.Name);
  1907. }
  1908. if (recording.IsKids)
  1909. {
  1910. list.Add("Kids");
  1911. }
  1912. if (recording.IsMovie)
  1913. {
  1914. list.Add("Movies");
  1915. }
  1916. if (recording.IsNews)
  1917. {
  1918. list.Add("News");
  1919. }
  1920. if (recording.IsSports)
  1921. {
  1922. list.Add("Sports");
  1923. }
  1924. if (!recording.IsSports && !recording.IsNews && !recording.IsMovie && !recording.IsKids && !recording.IsSeries)
  1925. {
  1926. list.Add("Others");
  1927. }
  1928. return list;
  1929. }
  1930. private List<Guid> GetRecordingGroupIds(ILiveTvRecording recording)
  1931. {
  1932. return GetRecordingGroupNames(recording).Select(i => i.ToLower()
  1933. .GetMD5())
  1934. .ToList();
  1935. }
  1936. public async Task<QueryResult<BaseItemDto>> GetRecordingGroups(RecordingGroupQuery query, CancellationToken cancellationToken)
  1937. {
  1938. var recordingResult = await GetInternalRecordings(new RecordingQuery
  1939. {
  1940. UserId = query.UserId
  1941. }, cancellationToken).ConfigureAwait(false);
  1942. var recordings = recordingResult.Items.OfType<ILiveTvRecording>().ToList();
  1943. var groups = new List<BaseItemDto>();
  1944. var series = recordings
  1945. .Where(i => i.IsSeries)
  1946. .ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase)
  1947. .ToList();
  1948. groups.AddRange(series.OrderByString(i => i.Key).Select(i => new BaseItemDto
  1949. {
  1950. Name = i.Key,
  1951. RecordingCount = i.Count()
  1952. }));
  1953. groups.Add(new BaseItemDto
  1954. {
  1955. Name = "Kids",
  1956. RecordingCount = recordings.Count(i => i.IsKids)
  1957. });
  1958. groups.Add(new BaseItemDto
  1959. {
  1960. Name = "Movies",
  1961. RecordingCount = recordings.Count(i => i.IsMovie)
  1962. });
  1963. groups.Add(new BaseItemDto
  1964. {
  1965. Name = "News",
  1966. RecordingCount = recordings.Count(i => i.IsNews)
  1967. });
  1968. groups.Add(new BaseItemDto
  1969. {
  1970. Name = "Sports",
  1971. RecordingCount = recordings.Count(i => i.IsSports)
  1972. });
  1973. groups.Add(new BaseItemDto
  1974. {
  1975. Name = "Others",
  1976. RecordingCount = recordings.Count(i => !i.IsSports && !i.IsNews && !i.IsMovie && !i.IsKids && !i.IsSeries)
  1977. });
  1978. groups = groups
  1979. .Where(i => i.RecordingCount > 0)
  1980. .ToList();
  1981. foreach (var group in groups)
  1982. {
  1983. group.Id = group.Name.ToLower().GetMD5().ToString("N");
  1984. }
  1985. return new QueryResult<BaseItemDto>
  1986. {
  1987. Items = groups.ToArray(),
  1988. TotalRecordCount = groups.Count
  1989. };
  1990. }
  1991. class LiveStreamData
  1992. {
  1993. internal MediaSourceInfo Info;
  1994. internal string ItemId;
  1995. internal bool IsChannel;
  1996. }
  1997. public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
  1998. {
  1999. await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
  2000. try
  2001. {
  2002. var parts = id.Split(new[] { '_' }, 2);
  2003. var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
  2004. if (service == null)
  2005. {
  2006. throw new ArgumentException("Service not found.");
  2007. }
  2008. id = parts[1];
  2009. LiveStreamData data;
  2010. _openStreams.TryRemove(id, out data);
  2011. _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id);
  2012. await service.CloseLiveStream(id, cancellationToken).ConfigureAwait(false);
  2013. }
  2014. catch (Exception ex)
  2015. {
  2016. _logger.ErrorException("Error closing live stream", ex);
  2017. throw;
  2018. }
  2019. finally
  2020. {
  2021. _liveStreamSemaphore.Release();
  2022. }
  2023. }
  2024. public GuideInfo GetGuideInfo()
  2025. {
  2026. var startDate = DateTime.UtcNow;
  2027. var endDate = startDate.AddDays(14);
  2028. return new GuideInfo
  2029. {
  2030. StartDate = startDate,
  2031. EndDate = endDate
  2032. };
  2033. }
  2034. /// <summary>
  2035. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  2036. /// </summary>
  2037. public void Dispose()
  2038. {
  2039. Dispose(true);
  2040. }
  2041. private readonly object _disposeLock = new object();
  2042. private bool _isDisposed = false;
  2043. /// <summary>
  2044. /// Releases unmanaged and - optionally - managed resources.
  2045. /// </summary>
  2046. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  2047. protected virtual void Dispose(bool dispose)
  2048. {
  2049. if (dispose)
  2050. {
  2051. _isDisposed = true;
  2052. lock (_disposeLock)
  2053. {
  2054. foreach (var stream in _openStreams.Values.ToList())
  2055. {
  2056. var task = CloseLiveStream(stream.Info.Id, CancellationToken.None);
  2057. Task.WaitAll(task);
  2058. }
  2059. _openStreams.Clear();
  2060. }
  2061. }
  2062. }
  2063. private async Task<IEnumerable<LiveTvServiceInfo>> GetServiceInfos(CancellationToken cancellationToken)
  2064. {
  2065. var tasks = Services.Select(i => GetServiceInfo(i, cancellationToken));
  2066. return await Task.WhenAll(tasks).ConfigureAwait(false);
  2067. }
  2068. private async Task<LiveTvServiceInfo> GetServiceInfo(ILiveTvService service, CancellationToken cancellationToken)
  2069. {
  2070. var info = new LiveTvServiceInfo
  2071. {
  2072. Name = service.Name
  2073. };
  2074. var tunerIdPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
  2075. try
  2076. {
  2077. var statusInfo = await service.GetStatusInfoAsync(cancellationToken).ConfigureAwait(false);
  2078. info.Status = statusInfo.Status;
  2079. info.StatusMessage = statusInfo.StatusMessage;
  2080. info.Version = statusInfo.Version;
  2081. info.HasUpdateAvailable = statusInfo.HasUpdateAvailable;
  2082. info.HomePageUrl = service.HomePageUrl;
  2083. info.IsVisible = statusInfo.IsVisible;
  2084. info.Tuners = statusInfo.Tuners.Select(i =>
  2085. {
  2086. string channelName = null;
  2087. if (!string.IsNullOrEmpty(i.ChannelId))
  2088. {
  2089. var internalChannelId = _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId);
  2090. var channel = GetInternalChannel(internalChannelId);
  2091. channelName = channel == null ? null : channel.Name;
  2092. }
  2093. var dto = _tvDtoService.GetTunerInfoDto(service.Name, i, channelName);
  2094. dto.Id = tunerIdPrefix + dto.Id;
  2095. return dto;
  2096. }).ToList();
  2097. }
  2098. catch (Exception ex)
  2099. {
  2100. _logger.ErrorException("Error getting service status info from {0}", ex, service.Name ?? string.Empty);
  2101. info.Status = LiveTvServiceStatus.Unavailable;
  2102. info.StatusMessage = ex.Message;
  2103. }
  2104. return info;
  2105. }
  2106. public async Task<LiveTvInfo> GetLiveTvInfo(CancellationToken cancellationToken)
  2107. {
  2108. var services = await GetServiceInfos(CancellationToken.None).ConfigureAwait(false);
  2109. var servicesList = services.ToList();
  2110. var info = new LiveTvInfo
  2111. {
  2112. Services = servicesList.ToList(),
  2113. IsEnabled = servicesList.Count > 0
  2114. };
  2115. info.EnabledUsers = _userManager.Users
  2116. .Where(IsLiveTvEnabled)
  2117. .Select(i => i.Id.ToString("N"))
  2118. .ToList();
  2119. return info;
  2120. }
  2121. private bool IsLiveTvEnabled(User user)
  2122. {
  2123. return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count(i => i.IsEnabled) > 0);
  2124. }
  2125. public IEnumerable<User> GetEnabledUsers()
  2126. {
  2127. return _userManager.Users
  2128. .Where(IsLiveTvEnabled);
  2129. }
  2130. /// <summary>
  2131. /// Resets the tuner.
  2132. /// </summary>
  2133. /// <param name="id">The identifier.</param>
  2134. /// <param name="cancellationToken">The cancellation token.</param>
  2135. /// <returns>Task.</returns>
  2136. public Task ResetTuner(string id, CancellationToken cancellationToken)
  2137. {
  2138. var parts = id.Split(new[] { '_' }, 2);
  2139. var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
  2140. if (service == null)
  2141. {
  2142. throw new ArgumentException("Service not found.");
  2143. }
  2144. return service.ResetTuner(parts[1], cancellationToken);
  2145. }
  2146. public async Task<BaseItemDto> GetLiveTvFolder(string userId, CancellationToken cancellationToken)
  2147. {
  2148. var user = string.IsNullOrEmpty(userId) ? null : _userManager.GetUserById(userId);
  2149. var folder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
  2150. return _dtoService.GetBaseItemDto(folder, new DtoOptions(), user);
  2151. }
  2152. private void RemoveFields(DtoOptions options)
  2153. {
  2154. options.Fields.Remove(ItemFields.CanDelete);
  2155. options.Fields.Remove(ItemFields.CanDownload);
  2156. options.Fields.Remove(ItemFields.DisplayPreferencesId);
  2157. options.Fields.Remove(ItemFields.Etag);
  2158. }
  2159. public async Task<Folder> GetInternalLiveTvFolder(CancellationToken cancellationToken)
  2160. {
  2161. var name = _localization.GetLocalizedString("ViewTypeLiveTV");
  2162. return await _libraryManager.GetNamedView(name, CollectionType.LiveTv, name, cancellationToken).ConfigureAwait(false);
  2163. }
  2164. public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
  2165. {
  2166. info = (TunerHostInfo)_jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(info), typeof(TunerHostInfo));
  2167. var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
  2168. if (provider == null)
  2169. {
  2170. throw new ResourceNotFoundException();
  2171. }
  2172. var configurable = provider as IConfigurableTunerHost;
  2173. if (configurable != null)
  2174. {
  2175. await configurable.Validate(info).ConfigureAwait(false);
  2176. }
  2177. var config = GetConfiguration();
  2178. var index = config.TunerHosts.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
  2179. if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
  2180. {
  2181. info.Id = Guid.NewGuid().ToString("N");
  2182. config.TunerHosts.Add(info);
  2183. }
  2184. else
  2185. {
  2186. config.TunerHosts[index] = info;
  2187. }
  2188. _config.SaveConfiguration("livetv", config);
  2189. if (dataSourceChanged)
  2190. {
  2191. _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
  2192. }
  2193. return info;
  2194. }
  2195. public async Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings)
  2196. {
  2197. info = (ListingsProviderInfo)_jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(info), typeof(ListingsProviderInfo));
  2198. var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
  2199. if (provider == null)
  2200. {
  2201. throw new ResourceNotFoundException();
  2202. }
  2203. await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false);
  2204. var config = GetConfiguration();
  2205. var index = config.ListingProviders.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
  2206. if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
  2207. {
  2208. info.Id = Guid.NewGuid().ToString("N");
  2209. config.ListingProviders.Add(info);
  2210. }
  2211. else
  2212. {
  2213. config.ListingProviders[index] = info;
  2214. }
  2215. _config.SaveConfiguration("livetv", config);
  2216. _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
  2217. return info;
  2218. }
  2219. public void DeleteListingsProvider(string id)
  2220. {
  2221. var config = GetConfiguration();
  2222. config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList();
  2223. _config.SaveConfiguration("livetv", config);
  2224. _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
  2225. }
  2226. public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber)
  2227. {
  2228. var config = GetConfiguration();
  2229. var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
  2230. listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray();
  2231. if (!string.Equals(tunerChannelNumber, providerChannelNumber, StringComparison.OrdinalIgnoreCase))
  2232. {
  2233. var list = listingsProviderInfo.ChannelMappings.ToList();
  2234. list.Add(new NameValuePair
  2235. {
  2236. Name = tunerChannelNumber,
  2237. Value = providerChannelNumber
  2238. });
  2239. listingsProviderInfo.ChannelMappings = list.ToArray();
  2240. }
  2241. _config.SaveConfiguration("livetv", config);
  2242. var tunerChannels = await GetChannelsForListingsProvider(providerId, CancellationToken.None)
  2243. .ConfigureAwait(false);
  2244. var providerChannels = await GetChannelsFromListingsProviderData(providerId, CancellationToken.None)
  2245. .ConfigureAwait(false);
  2246. var mappings = listingsProviderInfo.ChannelMappings.ToList();
  2247. var tunerChannelMappings =
  2248. tunerChannels.Select(i => GetTunerChannelMapping(i, mappings, providerChannels)).ToList();
  2249. _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
  2250. return tunerChannelMappings.First(i => string.Equals(i.Number, tunerChannelNumber, StringComparison.OrdinalIgnoreCase));
  2251. }
  2252. public TunerChannelMapping GetTunerChannelMapping(ChannelInfo channel, List<NameValuePair> mappings, List<ChannelInfo> providerChannels)
  2253. {
  2254. var result = new TunerChannelMapping
  2255. {
  2256. Name = channel.Number + " " + channel.Name,
  2257. Number = channel.Number
  2258. };
  2259. var mapping = mappings.FirstOrDefault(i => string.Equals(i.Name, channel.Number, StringComparison.OrdinalIgnoreCase));
  2260. var providerChannelNumber = channel.Number;
  2261. if (mapping != null)
  2262. {
  2263. providerChannelNumber = mapping.Value;
  2264. }
  2265. var providerChannel = providerChannels.FirstOrDefault(i => string.Equals(i.Number, providerChannelNumber, StringComparison.OrdinalIgnoreCase));
  2266. if (providerChannel != null)
  2267. {
  2268. result.ProviderChannelNumber = providerChannel.Number;
  2269. result.ProviderChannelName = providerChannel.Name;
  2270. }
  2271. return result;
  2272. }
  2273. public Task<List<NameIdPair>> GetLineups(string providerType, string providerId, string country, string location)
  2274. {
  2275. var config = GetConfiguration();
  2276. if (string.IsNullOrWhiteSpace(providerId))
  2277. {
  2278. var provider = _listingProviders.FirstOrDefault(i => string.Equals(providerType, i.Type, StringComparison.OrdinalIgnoreCase));
  2279. if (provider == null)
  2280. {
  2281. throw new ResourceNotFoundException();
  2282. }
  2283. return provider.GetLineups(null, country, location);
  2284. }
  2285. else
  2286. {
  2287. var info = config.ListingProviders.FirstOrDefault(i => string.Equals(i.Id, providerId, StringComparison.OrdinalIgnoreCase));
  2288. var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
  2289. if (provider == null)
  2290. {
  2291. throw new ResourceNotFoundException();
  2292. }
  2293. return provider.GetLineups(info, country, location);
  2294. }
  2295. }
  2296. public Task<MBRegistrationRecord> GetRegistrationInfo(string feature)
  2297. {
  2298. if (string.Equals(feature, "seriesrecordings", StringComparison.OrdinalIgnoreCase))
  2299. {
  2300. feature = "embytvseriesrecordings";
  2301. }
  2302. if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase))
  2303. {
  2304. var config = GetConfiguration();
  2305. if (config.TunerHosts.Count(i => i.IsEnabled) > 0 &&
  2306. config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0)
  2307. {
  2308. return Task.FromResult(new MBRegistrationRecord
  2309. {
  2310. IsRegistered = true,
  2311. IsValid = true
  2312. });
  2313. }
  2314. }
  2315. return _security.GetRegistrationStatus(feature);
  2316. }
  2317. public List<NameValuePair> GetSatIniMappings()
  2318. {
  2319. var names = GetType().Assembly.GetManifestResourceNames().Where(i => i.IndexOf("SatIp.ini", StringComparison.OrdinalIgnoreCase) != -1).ToList();
  2320. return names.Select(GetSatIniMappings).Where(i => i != null).DistinctBy(i => i.Value.Split('|')[0]).ToList();
  2321. }
  2322. public NameValuePair GetSatIniMappings(string resource)
  2323. {
  2324. using (var stream = GetType().Assembly.GetManifestResourceStream(resource))
  2325. {
  2326. using (var reader = new StreamReader(stream))
  2327. {
  2328. var parser = new StreamIniDataParser();
  2329. IniData data = parser.ReadData(reader);
  2330. var satType1 = data["SATTYPE"]["1"];
  2331. var satType2 = data["SATTYPE"]["2"];
  2332. if (string.IsNullOrWhiteSpace(satType2))
  2333. {
  2334. return null;
  2335. }
  2336. var srch = "SatIp.ini.";
  2337. var filename = Path.GetFileName(resource);
  2338. return new NameValuePair
  2339. {
  2340. Name = satType1 + " " + satType2,
  2341. Value = satType2 + "|" + filename.Substring(filename.IndexOf(srch) + srch.Length)
  2342. };
  2343. }
  2344. }
  2345. }
  2346. public Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken)
  2347. {
  2348. return new TunerHosts.SatIp.ChannelScan(_logger).Scan(info, cancellationToken);
  2349. }
  2350. public Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken)
  2351. {
  2352. var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
  2353. return EmbyTV.EmbyTV.Current.GetChannelsForListingsProvider(info, cancellationToken);
  2354. }
  2355. public Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken)
  2356. {
  2357. var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
  2358. var provider = _listingProviders.First(i => string.Equals(i.Type, info.Type, StringComparison.OrdinalIgnoreCase));
  2359. return provider.GetChannels(info, cancellationToken);
  2360. }
  2361. }
  2362. }