HdHomerunUdpStream.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #nullable disable
  2. #pragma warning disable CS1591
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Net.NetworkInformation;
  9. using System.Net.Sockets;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. using MediaBrowser.Common.Configuration;
  13. using MediaBrowser.Controller;
  14. using MediaBrowser.Controller.Library;
  15. using MediaBrowser.Model.Dto;
  16. using MediaBrowser.Model.IO;
  17. using MediaBrowser.Model.LiveTv;
  18. using MediaBrowser.Model.MediaInfo;
  19. using Microsoft.Extensions.Logging;
  20. namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
  21. {
  22. public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
  23. {
  24. private const int RtpHeaderBytes = 12;
  25. private readonly IServerApplicationHost _appHost;
  26. private readonly IHdHomerunChannelCommands _channelCommands;
  27. private readonly int _numTuners;
  28. public HdHomerunUdpStream(
  29. MediaSourceInfo mediaSource,
  30. TunerHostInfo tunerHostInfo,
  31. string originalStreamId,
  32. IHdHomerunChannelCommands channelCommands,
  33. int numTuners,
  34. IFileSystem fileSystem,
  35. ILogger logger,
  36. IConfigurationManager configurationManager,
  37. IServerApplicationHost appHost,
  38. IStreamHelper streamHelper)
  39. : base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
  40. {
  41. _appHost = appHost;
  42. OriginalStreamId = originalStreamId;
  43. _channelCommands = channelCommands;
  44. _numTuners = numTuners;
  45. EnableStreamSharing = true;
  46. }
  47. /// <summary>
  48. /// Returns an unused UDP port number in the range specified.
  49. /// Temporarily placed here until future network PR merged.
  50. /// </summary>
  51. /// <param name="range">Upper and Lower boundary of ports to select.</param>
  52. /// <returns>System.Int32.</returns>
  53. private static int GetUdpPortFromRange((int Min, int Max) range)
  54. {
  55. var properties = IPGlobalProperties.GetIPGlobalProperties();
  56. // Get active udp listeners.
  57. var udpListenerPorts = properties.GetActiveUdpListeners()
  58. .Where(n => n.Port >= range.Min && n.Port <= range.Max)
  59. .Select(n => n.Port);
  60. return Enumerable
  61. .Range(range.Min, range.Max)
  62. .FirstOrDefault(i => !udpListenerPorts.Contains(i));
  63. }
  64. public override async Task Open(CancellationToken openCancellationToken)
  65. {
  66. LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested();
  67. var mediaSource = OriginalMediaSource;
  68. var uri = new Uri(mediaSource.Path);
  69. // Temporary code to reduce PR size. This will be updated by a future network pr.
  70. var localPort = GetUdpPortFromRange((49152, 65535));
  71. Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath));
  72. Logger.LogInformation("Opening HDHR UDP Live stream from {host}", uri.Host);
  73. var remoteAddress = IPAddress.Parse(uri.Host);
  74. IPAddress localAddress = null;
  75. using (var tcpClient = new TcpClient())
  76. {
  77. try
  78. {
  79. await tcpClient.ConnectAsync(remoteAddress, HdHomerunManager.HdHomeRunPort, openCancellationToken).ConfigureAwait(false);
  80. localAddress = ((IPEndPoint)tcpClient.Client.LocalEndPoint).Address;
  81. tcpClient.Close();
  82. }
  83. catch (Exception ex)
  84. {
  85. Logger.LogError(ex, "Unable to determine local ip address for Legacy HDHomerun stream.");
  86. return;
  87. }
  88. }
  89. if (localAddress.IsIPv4MappedToIPv6) {
  90. localAddress = localAddress.MapToIPv4();
  91. }
  92. var udpClient = new UdpClient(localPort, AddressFamily.InterNetwork);
  93. var hdHomerunManager = new HdHomerunManager();
  94. try
  95. {
  96. // send url to start streaming
  97. await hdHomerunManager.StartStreaming(
  98. remoteAddress,
  99. localAddress,
  100. localPort,
  101. _channelCommands,
  102. _numTuners,
  103. openCancellationToken).ConfigureAwait(false);
  104. }
  105. catch (Exception ex)
  106. {
  107. using (udpClient)
  108. using (hdHomerunManager)
  109. {
  110. if (ex is not OperationCanceledException)
  111. {
  112. Logger.LogError(ex, "Error opening live stream:");
  113. }
  114. throw;
  115. }
  116. }
  117. var taskCompletionSource = new TaskCompletionSource<bool>();
  118. _ = StartStreaming(
  119. udpClient,
  120. hdHomerunManager,
  121. remoteAddress,
  122. taskCompletionSource,
  123. LiveStreamCancellationTokenSource.Token);
  124. // OpenedMediaSource.Protocol = MediaProtocol.File;
  125. // OpenedMediaSource.Path = tempFile;
  126. // OpenedMediaSource.ReadAtNativeFramerate = true;
  127. MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
  128. MediaSource.Protocol = MediaProtocol.Http;
  129. // OpenedMediaSource.SupportsDirectPlay = false;
  130. // OpenedMediaSource.SupportsDirectStream = true;
  131. // OpenedMediaSource.SupportsTranscoding = true;
  132. // await Task.Delay(5000).ConfigureAwait(false);
  133. await taskCompletionSource.Task.ConfigureAwait(false);
  134. }
  135. public string GetFilePath()
  136. {
  137. return TempFilePath;
  138. }
  139. private async Task StartStreaming(UdpClient udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddress, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
  140. {
  141. using (udpClient)
  142. using (hdHomerunManager)
  143. {
  144. try
  145. {
  146. await CopyTo(udpClient, TempFilePath, openTaskCompletionSource, cancellationToken).ConfigureAwait(false);
  147. }
  148. catch (OperationCanceledException ex)
  149. {
  150. Logger.LogInformation("HDHR UDP stream cancelled or timed out from {0}", remoteAddress);
  151. openTaskCompletionSource.TrySetException(ex);
  152. }
  153. catch (Exception ex)
  154. {
  155. Logger.LogError(ex, "Error opening live stream:");
  156. openTaskCompletionSource.TrySetException(ex);
  157. }
  158. EnableStreamSharing = false;
  159. }
  160. await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
  161. }
  162. private async Task CopyTo(UdpClient udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
  163. {
  164. var resolved = false;
  165. using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
  166. {
  167. while (true)
  168. {
  169. cancellationToken.ThrowIfCancellationRequested();
  170. using (var timeOutSource = new CancellationTokenSource())
  171. using (var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(
  172. cancellationToken,
  173. timeOutSource.Token))
  174. {
  175. var resTask = udpClient.ReceiveAsync();
  176. if (await Task.WhenAny(resTask, Task.Delay(30000, linkedSource.Token)).ConfigureAwait(false) != resTask)
  177. {
  178. resTask.Dispose();
  179. break;
  180. }
  181. // We don't want all these delay tasks to keep running
  182. timeOutSource.Cancel();
  183. var res = await resTask.ConfigureAwait(false);
  184. var buffer = res.Buffer;
  185. var read = buffer.Length - RtpHeaderBytes;
  186. if (read > 0)
  187. {
  188. fileStream.Write(buffer, RtpHeaderBytes, read);
  189. }
  190. if (!resolved)
  191. {
  192. resolved = true;
  193. DateOpened = DateTime.UtcNow;
  194. openTaskCompletionSource.TrySetResult(true);
  195. }
  196. }
  197. }
  198. }
  199. }
  200. }
  201. }