LiveTvManager.cs 115 KB

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