BaseTunerHost.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. #pragma warning disable CS1591
  2. using System;
  3. using System.Collections.Concurrent;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using MediaBrowser.Common.Configuration;
  10. using MediaBrowser.Controller.Configuration;
  11. using MediaBrowser.Controller.Library;
  12. using MediaBrowser.Controller.LiveTv;
  13. using MediaBrowser.Model.Dto;
  14. using MediaBrowser.Model.IO;
  15. using MediaBrowser.Model.LiveTv;
  16. using MediaBrowser.Model.Serialization;
  17. using Microsoft.Extensions.Logging;
  18. namespace Emby.Server.Implementations.LiveTv.TunerHosts
  19. {
  20. public abstract class BaseTunerHost
  21. {
  22. protected readonly IServerConfigurationManager Config;
  23. protected readonly ILogger<BaseTunerHost> Logger;
  24. protected IJsonSerializer JsonSerializer;
  25. protected readonly IFileSystem FileSystem;
  26. private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
  27. new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase);
  28. protected BaseTunerHost(IServerConfigurationManager config, ILogger<BaseTunerHost> logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem)
  29. {
  30. Config = config;
  31. Logger = logger;
  32. JsonSerializer = jsonSerializer;
  33. FileSystem = fileSystem;
  34. }
  35. public virtual bool IsSupported => true;
  36. protected abstract Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken);
  37. public abstract string Type { get; }
  38. public async Task<List<ChannelInfo>> GetChannels(TunerHostInfo tuner, bool enableCache, CancellationToken cancellationToken)
  39. {
  40. ChannelCache cache = null;
  41. var key = tuner.Id;
  42. if (enableCache && !string.IsNullOrEmpty(key) && _channelCache.TryGetValue(key, out cache))
  43. {
  44. return cache.Channels.ToList();
  45. }
  46. var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
  47. var list = result.ToList();
  48. //logger.LogInformation("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
  49. if (!string.IsNullOrEmpty(key) && list.Count > 0)
  50. {
  51. cache = cache ?? new ChannelCache();
  52. cache.Channels = list;
  53. _channelCache.AddOrUpdate(key, cache, (k, v) => cache);
  54. }
  55. return list;
  56. }
  57. protected virtual List<TunerHostInfo> GetTunerHosts()
  58. {
  59. return GetConfiguration().TunerHosts
  60. .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
  61. .ToList();
  62. }
  63. public async Task<List<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken)
  64. {
  65. var list = new List<ChannelInfo>();
  66. var hosts = GetTunerHosts();
  67. foreach (var host in hosts)
  68. {
  69. var channelCacheFile = Path.Combine(Config.ApplicationPaths.CachePath, host.Id + "_channels");
  70. try
  71. {
  72. var channels = await GetChannels(host, enableCache, cancellationToken).ConfigureAwait(false);
  73. var newChannels = channels.Where(i => !list.Any(l => string.Equals(i.Id, l.Id, StringComparison.OrdinalIgnoreCase))).ToList();
  74. list.AddRange(newChannels);
  75. if (!enableCache)
  76. {
  77. try
  78. {
  79. Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile));
  80. JsonSerializer.SerializeToFile(channels, channelCacheFile);
  81. }
  82. catch (IOException)
  83. {
  84. }
  85. }
  86. }
  87. catch (Exception ex)
  88. {
  89. Logger.LogError(ex, "Error getting channel list");
  90. if (enableCache)
  91. {
  92. try
  93. {
  94. var channels = JsonSerializer.DeserializeFromFile<List<ChannelInfo>>(channelCacheFile);
  95. list.AddRange(channels);
  96. }
  97. catch (IOException)
  98. {
  99. }
  100. }
  101. }
  102. }
  103. return list;
  104. }
  105. protected abstract Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo channel, CancellationToken cancellationToken);
  106. public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
  107. {
  108. if (string.IsNullOrEmpty(channelId))
  109. {
  110. throw new ArgumentNullException(nameof(channelId));
  111. }
  112. if (IsValidChannelId(channelId))
  113. {
  114. var hosts = GetTunerHosts();
  115. foreach (var host in hosts)
  116. {
  117. try
  118. {
  119. var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
  120. var channelInfo = channels.FirstOrDefault(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase));
  121. if (channelInfo != null)
  122. {
  123. return await GetChannelStreamMediaSources(host, channelInfo, cancellationToken).ConfigureAwait(false);
  124. }
  125. }
  126. catch (Exception ex)
  127. {
  128. Logger.LogError(ex, "Error getting channels");
  129. }
  130. }
  131. }
  132. return new List<MediaSourceInfo>();
  133. }
  134. protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tuner, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
  135. public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
  136. {
  137. if (string.IsNullOrEmpty(channelId))
  138. {
  139. throw new ArgumentNullException(nameof(channelId));
  140. }
  141. if (!IsValidChannelId(channelId))
  142. {
  143. throw new FileNotFoundException();
  144. }
  145. var hosts = GetTunerHosts();
  146. var hostsWithChannel = new List<Tuple<TunerHostInfo, ChannelInfo>>();
  147. foreach (var host in hosts)
  148. {
  149. try
  150. {
  151. var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
  152. var channelInfo = channels.FirstOrDefault(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase));
  153. if (channelInfo != null)
  154. {
  155. hostsWithChannel.Add(new Tuple<TunerHostInfo, ChannelInfo>(host, channelInfo));
  156. }
  157. }
  158. catch (Exception ex)
  159. {
  160. Logger.LogError(ex, "Error getting channels");
  161. }
  162. }
  163. foreach (var hostTuple in hostsWithChannel)
  164. {
  165. var host = hostTuple.Item1;
  166. var channelInfo = hostTuple.Item2;
  167. try
  168. {
  169. var liveStream = await GetChannelStream(host, channelInfo, streamId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
  170. var startTime = DateTime.UtcNow;
  171. await liveStream.Open(cancellationToken).ConfigureAwait(false);
  172. var endTime = DateTime.UtcNow;
  173. Logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
  174. return liveStream;
  175. }
  176. catch (Exception ex)
  177. {
  178. Logger.LogError(ex, "Error opening tuner");
  179. }
  180. }
  181. throw new LiveTvConflictException();
  182. }
  183. protected virtual string ChannelIdPrefix => Type + "_";
  184. protected virtual bool IsValidChannelId(string channelId)
  185. {
  186. if (string.IsNullOrEmpty(channelId))
  187. {
  188. throw new ArgumentNullException(nameof(channelId));
  189. }
  190. return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
  191. }
  192. protected LiveTvOptions GetConfiguration()
  193. {
  194. return Config.GetConfiguration<LiveTvOptions>("livetv");
  195. }
  196. private class ChannelCache
  197. {
  198. public List<ChannelInfo> Channels;
  199. }
  200. }
  201. }