diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Program.cs b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Program.cs index ff8fd8b..66f0378 100644 --- a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Program.cs +++ b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Program.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using NLog.Extensions.Logging; using System; +using System.IO; using System.Threading.Tasks; namespace JT1078.Gateway.TestNormalHosting @@ -33,6 +34,8 @@ namespace JT1078.Gateway.TestNormalHosting .ConfigureServices((hostContext, services) => { services.AddMemoryCache(); + services.AddScoped(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); services.AddSingleton(); diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/appsettings.json b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/appsettings.json index d58cb25..65f5cbd 100644 --- a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/appsettings.json +++ b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/appsettings.json @@ -19,6 +19,6 @@ "TsFileCapacity": 10, "TsFileMaxSecond": 10, "M3U8FileName": "live.m3u8", - "HlsFileDirectory":"www/root/demo" + "HlsFileDirectory":"wwwroot/demo" } } diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/10.ts b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/10.ts new file mode 100644 index 0000000..81215fd Binary files /dev/null and b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/10.ts differ diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/11.ts b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/11.ts new file mode 100644 index 0000000..7d475bf Binary files /dev/null and b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/11.ts differ diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/12.ts b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/12.ts new file mode 100644 index 0000000..f4fee46 Binary files /dev/null and b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/12.ts differ diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/13.ts b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/13.ts new file mode 100644 index 0000000..fc05541 Binary files /dev/null and b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/13.ts differ diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/14.ts b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/14.ts new file mode 100644 index 0000000..75774a9 Binary files /dev/null and b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/14.ts differ diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/15.ts b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/15.ts new file mode 100644 index 0000000..b9f4df6 Binary files /dev/null and b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/15.ts differ diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/16.ts b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/16.ts new file mode 100644 index 0000000..cbebc97 Binary files /dev/null and b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/16.ts differ diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/17.ts b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/17.ts new file mode 100644 index 0000000..02f6185 Binary files /dev/null and b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/17.ts differ diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/7.ts b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/7.ts new file mode 100644 index 0000000..7189220 Binary files /dev/null and b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/7.ts differ diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/8.ts b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/8.ts new file mode 100644 index 0000000..f442d44 Binary files /dev/null and b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/8.ts differ diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/9.ts b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/9.ts new file mode 100644 index 0000000..5746179 Binary files /dev/null and b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/9.ts differ diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/demo.m3u8 b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/demo.m3u8 index de79cca..aa5fea3 100644 --- a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/demo.m3u8 +++ b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/demo.m3u8 @@ -1 +1,7 @@ -m3u8 demo \ No newline at end of file +#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-TARGETDURATION:7 +#EXT-X-MEDIA-SEQUENCE:0 +#EXTINF:7.200667, +demo0.ts +#EXT-X-ENDLIST diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/demo0.ts b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/demo0.ts new file mode 100644 index 0000000..e3cc2ab Binary files /dev/null and b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/demo0.ts differ diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/index.html b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/index.html new file mode 100644 index 0000000..554e735 --- /dev/null +++ b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/index.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/live.m3u8 b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/live.m3u8 new file mode 100644 index 0000000..3876896 --- /dev/null +++ b/src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/live.m3u8 @@ -0,0 +1,26 @@ +#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-ALLOW-CACHE:NO +#EXT-X-TARGETDURATION:10 +#EXT-X-MEDIA-SEQUENCE:7 + +#EXTINF:10.04, +7.ts +#EXTINF:10.041, +8.ts +#EXTINF:10.08, +9.ts +#EXTINF:10.001, +10.ts +#EXTINF:10.12, +11.ts +#EXTINF:10.04, +12.ts +#EXTINF:10.001, +13.ts +#EXTINF:10.04, +14.ts +#EXTINF:10.04, +15.ts +#EXTINF:10.041, +16.ts \ No newline at end of file diff --git a/src/JT1078.Gateway/HLSRequestManager.cs b/src/JT1078.Gateway/HLSRequestManager.cs new file mode 100644 index 0000000..a83f63b --- /dev/null +++ b/src/JT1078.Gateway/HLSRequestManager.cs @@ -0,0 +1,155 @@ +using JT1078.Gateway.Configurations; +using JT1078.Gateway.Extensions; +using JT1078.Gateway.Metadata; +using JT1078.Gateway.Sessions; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Security.Principal; +using System.Text; + +namespace JT1078.Gateway +{ + /// + /// Hls请求管理 + /// + public class HLSRequestManager + { + private const string m3u8Mime = "application/x-mpegURL"; + private const string tsMime = "video/MP2T"; + private readonly JT1078Configuration Configuration; + private readonly JT1078HttpSessionManager HttpSessionManager; + private readonly JT1078SessionManager SessionManager; + private readonly ILogger Logger; + private IMemoryCache memoryCache; + private FileSystemWatcher fileSystemWatcher; + + public HLSRequestManager( + IMemoryCache memoryCache, + IOptions jT1078ConfigurationAccessor, + JT1078HttpSessionManager httpSessionManager, + JT1078SessionManager sessionManager, + FileSystemWatcher fileSystemWatcher, + ILoggerFactory loggerFactory) + { + this.memoryCache = memoryCache; + this.fileSystemWatcher = fileSystemWatcher; + HttpSessionManager = httpSessionManager; + SessionManager = sessionManager; + Configuration = jT1078ConfigurationAccessor.Value; + Logger = loggerFactory.CreateLogger(); + } + /// + /// 处理hls实时视频请求 + /// + /// + /// + public async void HandleHlsRequest(HttpListenerContext context, IPrincipal principal) { + if (context.Request.QueryString.Count < 2) + { + context.Http404(); + return; + } + string sim = context.Request.QueryString.Get("sim");//终端sim卡号 + string channelNo = context.Request.QueryString.Get("channelNo");//通道号 + string key = $"{sim}_{channelNo}"; + string filename = Path.GetFileName(context.Request.Url.AbsolutePath.ToString()); + string filepath = Path.Combine(Configuration.HlsRootDirectory, key, filename); + if (!File.Exists(filepath)) + { + if (filename.ToLower().Contains("m3u8")) + { + fileSystemWatcher = new FileSystemWatcher(); + fileSystemWatcher.Path = Path.Combine(Configuration.HlsRootDirectory, key); + fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite; //NotifyFilters.CreateTime + fileSystemWatcher.Filter = "*.m3u8"; // Only watch text files. + fileSystemWatcher.Changed += (sender, arg) => + { + if (context.Response.ContentLength64 != 0) return; + //wwwroot\1234_2\live.m3u8 + var key = arg.FullPath.Replace(arg.Name, "").Substring(arg.FullPath.Replace(arg.Name, "").IndexOf("\\")).Replace("\\", ""); + var sim = key.Split("_")[0]; + var channel = int.Parse(key.Split("_")[1]); + try + { + using (FileStream sr = new FileStream(arg.FullPath, FileMode.Open)) + { + context.Response.ContentType = m3u8Mime; + context.Response.StatusCode = (int)HttpStatusCode.OK; + context.Response.ContentLength64 = sr.Length; + sr.CopyTo(context.Response.OutputStream); + } + } + catch (Exception ex) + { + Logger.LogError(ex, $"{context.Request.Url}"); + } + finally + { + context.Response.OutputStream.Close(); + context.Response.Close(); + } + }; + fileSystemWatcher.EnableRaisingEvents = true; // Begin watching. + } + else + { + context.Http404(); + return; + } + } + else + { + try + { + using (FileStream sr = new FileStream(filepath, FileMode.Open)) + { + if (filename.ToLower().Contains("m3u8")) + { + context.Response.ContentType = m3u8Mime; + } + else + { + context.Response.ContentType = tsMime; + } + context.Response.StatusCode = (int)HttpStatusCode.OK; + context.Response.ContentLength64 = sr.Length; + await sr.CopyToAsync(context.Response.OutputStream); + } + } + catch (Exception ex) + { + Logger.LogError(ex, $"{context.Request.Url}"); + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + } + finally + { + context.Response.OutputStream.Close(); + context.Response.Close(); + } + } + var jT1078HttpContext = new JT1078HttpContext(context, principal); + jT1078HttpContext.Sim = sim; + jT1078HttpContext.ChannelNo = int.Parse(channelNo); + HttpSessionManager.TryAdd(jT1078HttpContext); + //如果过了30s,还未收到浏览器请求,则移除掉session + memoryCache.Set(key, DateTime.Now, new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10) + }.RegisterPostEvictionCallback((key, value, reason, state) => + { + if (reason != EvictionReason.Expired) return; + //当清空httpssion时,同时清除tcpsseion + var removeSim = key.ToString().Split('_')[0]; + //移除httpsession + HttpSessionManager.TryRemoveBySim(removeSim); + //移除tcpsession + SessionManager.RemoveByTerminalPhoneNo(removeSim); + })); + } + } +} diff --git a/src/JT1078.Gateway/JT1078.Gateway.xml b/src/JT1078.Gateway/JT1078.Gateway.xml index 1093ec8..48f60c9 100644 --- a/src/JT1078.Gateway/JT1078.Gateway.xml +++ b/src/JT1078.Gateway/JT1078.Gateway.xml @@ -53,6 +53,18 @@ 协调器Coordinator主机登录密码 + + + Hls请求管理 + + + + + 处理hls实时视频请求 + + + + 协调器客户端 diff --git a/src/JT1078.Gateway/JT1078HttpServer.cs b/src/JT1078.Gateway/JT1078HttpServer.cs index 792fa80..e9cec5b 100644 --- a/src/JT1078.Gateway/JT1078HttpServer.cs +++ b/src/JT1078.Gateway/JT1078HttpServer.cs @@ -28,24 +28,24 @@ namespace JT1078.Gateway private readonly IJT1078Authorization authorization; - private IMemoryCache memoryCache; - private HttpListener listener; private JT1078HttpSessionManager SessionManager; + private readonly HLSRequestManager hLSRequestManager; + private FileSystemWatcher watcher; public JT1078HttpServer( - IMemoryCache memoryCache, IOptions jT1078ConfigurationAccessor, IJT1078Authorization authorization, JT1078HttpSessionManager sessionManager, + HLSRequestManager hLSRequestManager, ILoggerFactory loggerFactory) { Logger = loggerFactory.CreateLogger(); Configuration = jT1078ConfigurationAccessor.Value; this.authorization = authorization; this.SessionManager = sessionManager; - this.memoryCache = memoryCache; + this.hLSRequestManager = hLSRequestManager; } public Task StartAsync(CancellationToken cancellationToken) @@ -74,14 +74,18 @@ namespace JT1078.Gateway var context = await listener.GetContextAsync(); try { - if (authorization.Authorization(context,out var principal)) - { - await ProcessRequestAsync(context, principal); - } - else + await Task.Run(async () => { - await context.Http401(); - } + IPrincipal principal=null; + if (context.Request.RawUrl.Contains(".ts")||authorization.Authorization(context, out principal)) + { + await ProcessRequestAsync(context, principal); + } + else + { + await context.Http401(); + } + }); } catch (Exception ex) { @@ -93,9 +97,6 @@ namespace JT1078.Gateway return Task.CompletedTask; } - private const string m3u8Mime = "application/x-mpegURL"; - private const string tsMime = "video/MP2T"; - private async ValueTask ProcessRequestAsync(HttpListenerContext context, IPrincipal principal) { if(context.Request.RawUrl.StartsWith("/favicon.ico")) @@ -103,65 +104,19 @@ namespace JT1078.Gateway context.Http404(); return; } - if (context.Request.RawUrl.EndsWith(".m3u8") || context.Request.RawUrl.EndsWith(".ts")) + if (context.Request.RawUrl.Contains(".m3u8") || context.Request.RawUrl.Contains(".ts")) { - var uri = new Uri(context.Request.RawUrl); - string url = uri.AbsolutePath; - var queryParams = uri.Query.Substring(1, uri.Query.Length - 1).Split('&'); - if (queryParams.Length < 2) - { - context.Http404(); - return; - } - string key = $"{queryParams[0].Split('=')[1]}_{queryParams[1].Split('=')[1]}";//默认queryParams第一个参数是终端号,第二个参数是通道号 - memoryCache.GetOrCreate(key, (cacheEntry) => { - cacheEntry.SetSlidingExpiration(TimeSpan.FromSeconds(20)); - cacheEntry.RegisterPostEvictionCallback((key, value, reason, state) => { - //当清空httpssion时,同时清除tcpsseion - }); - return DateTime.Now; - }); - string filename = Path.GetFileName(url); - string filepath = Path.Combine(Configuration.HlsRootDirectory, key, filename); - if (!File.Exists(filepath)) - { - context.Http404(); - return; - } - try - { - using (FileStream sr = new FileStream(filepath, FileMode.Open)) - { - context.Response.ContentLength64 = sr.Length; - await sr.CopyToAsync(context.Response.OutputStream); - } - string ext = Path.GetExtension(filename); - if (ext == ".m3u8") - { - context.Response.ContentType = m3u8Mime; - } - else if (ext == ".ts") - { - context.Response.ContentType = tsMime; - } - context.Response.StatusCode = (int)HttpStatusCode.OK; - } - catch (Exception ex) - { - Logger.LogError(ex, $"{context.Request.RawUrl}"); - context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - } - finally - { - context.Response.OutputStream.Close(); - context.Response.Close(); - } + hLSRequestManager.HandleHlsRequest(context, principal); return; } if (Logger.IsEnabled(LogLevel.Trace)) { Logger.LogTrace($"[http RequestTraceIdentifier]:{context.Request.RequestTraceIdentifier.ToString()}-{context.Request.RemoteEndPoint.ToString()}"); } + if (Logger.IsEnabled(LogLevel.Trace)) + { + Logger.LogTrace($"[http RequestTraceIdentifier]:{context.Request.RequestTraceIdentifier.ToString()}-{context.Request.RemoteEndPoint.ToString()}"); + } string sim = context.Request.QueryString.Get("sim"); string channel = context.Request.QueryString.Get("channel"); if(string.IsNullOrEmpty(sim) || string.IsNullOrEmpty(channel))