浏览代码

hls请求处理

master
waterliu99 4 年前
父节点
当前提交
93fc860d28
共有 20 个文件被更改,包括 259 次插入68 次删除
  1. +3
    -0
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/Program.cs
  2. +1
    -1
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/appsettings.json
  3. 二进制
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/10.ts
  4. 二进制
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/11.ts
  5. 二进制
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/12.ts
  6. 二进制
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/13.ts
  7. 二进制
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/14.ts
  8. 二进制
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/15.ts
  9. 二进制
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/16.ts
  10. 二进制
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/17.ts
  11. 二进制
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/7.ts
  12. 二进制
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/8.ts
  13. 二进制
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/9.ts
  14. +7
    -1
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/demo.m3u8
  15. 二进制
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/demo0.ts
  16. +34
    -0
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/index.html
  17. +26
    -0
      src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/live.m3u8
  18. +155
    -0
      src/JT1078.Gateway/HLSRequestManager.cs
  19. +12
    -0
      src/JT1078.Gateway/JT1078.Gateway.xml
  20. +21
    -66
      src/JT1078.Gateway/JT1078HttpServer.cs

+ 3
- 0
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<FileSystemWatcher>();
services.AddSingleton<HLSRequestManager>();
services.AddSingleton<ILoggerFactory, LoggerFactory>();
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
services.AddSingleton<FlvEncoder>();


+ 1
- 1
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"
}
}

二进制
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/10.ts 查看文件


二进制
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/11.ts 查看文件


二进制
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/12.ts 查看文件


二进制
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/13.ts 查看文件


二进制
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/14.ts 查看文件


二进制
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/15.ts 查看文件


二进制
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/16.ts 查看文件


二进制
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/17.ts 查看文件


二进制
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/7.ts 查看文件


二进制
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/8.ts 查看文件


二进制
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/9.ts 查看文件


+ 7
- 1
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/demo.m3u8 查看文件

@@ -1 +1,7 @@
m3u8 demo
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:7
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:7.200667,
demo0.ts
#EXT-X-ENDLIST

二进制
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/demo0.ts 查看文件


+ 34
- 0
src/JT1078.Gateway.Tests/JT1078.Gateway.TestNormalHosting/wwwroot/demo/index.html 查看文件

@@ -0,0 +1,34 @@
<html>
<head></head>
<body>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<!-- Or if you want a more recent alpha version -->
<!-- <script src="https://cdn.jsdelivr.net/npm/hls.js@alpha"></script> -->
<!-- video:play() failed because the user didn't interact with the document first //https://www.jianshu.com/p/06179ca12dfa -->
<video autoplay muted id="video"></video>
<script>
var video = document.getElementById('video');
var videoSrc = 'demo.m3u8';
//
// First check for native browser HLS support
//
if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = videoSrc;
video.addEventListener('loadedmetadata', function() {
video.play();
});
//
// If no native HLS support, check if hls.js is supported
//
} else if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(videoSrc);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.play();
});
}
</script>
</body>
</html>


+ 26
- 0
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

+ 155
- 0
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
{
/// <summary>
/// Hls请求管理
/// </summary>
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<JT1078Configuration> 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<HLSRequestManager>();
}
/// <summary>
/// 处理hls实时视频请求
/// </summary>
/// <param name="context"></param>
/// <param name="principal"></param>
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);
}));
}
}
}

+ 12
- 0
src/JT1078.Gateway/JT1078.Gateway.xml 查看文件

@@ -53,6 +53,18 @@
协调器Coordinator主机登录密码
</summary>
</member>
<member name="T:JT1078.Gateway.HLSRequestManager">
<summary>
Hls请求管理
</summary>
</member>
<member name="M:JT1078.Gateway.HLSRequestManager.HandleHlsRequest(System.Net.HttpListenerContext,System.Security.Principal.IPrincipal)">
<summary>
处理hls实时视频请求
</summary>
<param name="context"></param>
<param name="principal"></param>
</member>
<member name="T:JT1078.Gateway.JT1078CoordinatorHttpClient">
<summary>
协调器客户端


+ 21
- 66
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<JT1078Configuration> jT1078ConfigurationAccessor,
IJT1078Authorization authorization,
JT1078HttpSessionManager sessionManager,
HLSRequestManager hLSRequestManager,
ILoggerFactory loggerFactory)
{
Logger = loggerFactory.CreateLogger<JT1078TcpServer>();
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))


正在加载...
取消
保存